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';
...@@ -44,7 +45,7 @@ sub new { ...@@ -44,7 +45,7 @@ sub new {
#pod #pod
#pod =cut #pod =cut
sub identifier { $_[0]{identifier} } sub identifier { $_[0]{identifier} }
#pod =method description #pod =method description
#pod #pod
...@@ -61,7 +62,7 @@ sub description { $_[0]{description} } ...@@ -61,7 +62,7 @@ sub description { $_[0]{description} }
#pod #pod
#pod =cut #pod =cut
sub prereqs { $_[0]{prereqs} } sub prereqs { $_[0]{prereqs} }
1; 1;
......
...@@ -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,22 +100,26 @@ sub _optional_features { ...@@ -98,22 +100,26 @@ 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;
} }
} }
return $left; return $left;
...@@ -131,21 +137,19 @@ my %default = ( ...@@ -131,21 +137,19 @@ my %default = (
my ($left, $right) = @_; my ($left, $right) = @_;
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, name => \&_identical,
url => \&_identical release_status => \&_identical,
}, version => \&_identical,
name => \&_identical, description => \&_identical,
release_status => \&_identical, keywords => \&_set_addition,
version => \&_identical, no_index =>
description => \&_identical, {map { ($_ => \&_set_addition) } qw/file directory package namespace/},
keywords => \&_set_addition,
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};
...@@ -174,7 +178,7 @@ sub new { ...@@ -174,7 +178,7 @@ sub new {
} }
return bless { return bless {
default_version => $arguments{default_version}, default_version => $arguments{default_version},
mapping => _coerce_mapping(\%mapping, []), mapping => _coerce_mapping(\%mapping, []),
}, $class; }, $class;
} }
...@@ -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;
} }
} }
...@@ -174,8 +174,8 @@ sub with_merged_prereqs { ...@@ -174,8 +174,8 @@ sub with_merged_prereqs {
sub merged_requirements { sub merged_requirements {
my ($self, $phases, $types) = @_; my ($self, $phases, $types) = @_;
$phases = [qw/runtime build test/] unless defined $phases; $phases = [qw/runtime build test/] unless defined $phases;
$types = [qw/requires recommends/] unless defined $types; $types = [qw/requires recommends/] unless defined $types;
confess "merged_requirements phases argument must be an arrayref" confess "merged_requirements phases argument must be an arrayref"
unless ref $phases eq 'ARRAY'; unless ref $phases eq 'ARRAY';
...@@ -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';
...@@ -8,7 +10,7 @@ our $VERSION = '1.4417'; ...@@ -8,7 +10,7 @@ our $VERSION = '1.4417';
use Exporter; use Exporter;
use Carp 'croak'; use Carp 'croak';
our @ISA = qw/Exporter/; our @ISA = qw/Exporter/;
our @EXPORT_OK = qw/Load LoadFile/; our @EXPORT_OK = qw/Load LoadFile/;
sub load_file { sub load_file {
...@@ -23,19 +25,19 @@ sub load_file { ...@@ -23,19 +25,19 @@ sub load_file {
return $class->load_json_string($meta); return $class->load_json_string($meta);
} }
else { else {
$class->load_string($meta); # try to detect yaml/json $class->load_string($meta); # try to detect yaml/json
} }
} }
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
return $class->load_yaml_string($string); return $class->load_yaml_string($string);
} }
} }
...@@ -45,7 +47,7 @@ sub load_yaml_string { ...@@ -45,7 +47,7 @@ sub load_yaml_string {
my $backend = $class->yaml_backend(); my $backend = $class->yaml_backend();
my $data = eval { no strict 'refs'; &{"$backend\::Load"}($string) }; my $data = eval { no strict 'refs'; &{"$backend\::Load"}($string) };
croak $@ if $@; croak $@ if $@;
return $data || {}; # in case document was valid but empty return $data || {}; # in case document was valid but empty
} }
sub load_json_string { sub load_json_string {
...@@ -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,51 +73,48 @@ sub yaml_backend { ...@@ -72,51 +73,48 @@ 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";
} }
} }
sub _slurp { sub _slurp {
require Encode; require Encode;
open my $fh, "<:raw", "$_[0]" ## no critic open my $fh, "<:raw", "$_[0]" ## no critic
or die "can't open $_[0] for reading: $!"; or die "can't open $_[0] for reading: $!";
my $content = do { local $/; <$fh> }; my $content = do { local $/; <$fh> };
$content = Encode::decode('UTF-8', $content, Encode::PERLQQ()); $content = Encode::decode('UTF-8', $content, Encode::PERLQQ());
return $content; return $content;
} }
sub _can_load { sub _can_load {
my ($module, $version) = @_; my ($module, $version) = @_;
(my $file = $module) =~ s{::}{/}g; (my $file = $module) =~ s{::}{/}g;
$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;
} }
# Kept for backwards compatibility only # Kept for backwards compatibility only
# Create an object from a file # Create an object from a file
sub LoadFile ($) { ## no critic sub LoadFile ($) { ## no critic
return Load(_slurp(shift)); return Load(_slurp(shift));
} }
# Parse a document from a string. # Parse a document from a string.
sub Load ($) { ## no critic sub Load ($) { ## no critic
require CPAN::Meta::YAML; require CPAN::Meta::YAML;
my $object = eval { CPAN::Meta::YAML::Load(shift) }; my $object = eval { CPAN::Meta::YAML::Load(shift) };
croak $@ if $@; croak $@ if $@;
......
...@@ -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.
...@@ -46,8 +46,8 @@ use constant REST_RESOURCES => []; ...@@ -46,8 +46,8 @@ use constant REST_RESOURCES => [];
################## ##################
sub login_exempt { sub login_exempt {
my ($class, $method) = @_; my ($class, $method) = @_;
return $class->LOGIN_EXEMPT->{$method}; return $class->LOGIN_EXEMPT->{$method};
} }
1; 1;
......
...@@ -26,33 +26,34 @@ extends 'Bugzilla::API::1_0::Resource'; ...@@ -26,33 +26,34 @@ extends 'Bugzilla::API::1_0::Resource';
############## ##############
use constant READ_ONLY => qw( use constant READ_ONLY => qw(
get get
); );
use constant PUBLIC_METHODS => qw( use constant PUBLIC_METHODS => qw(
get get
update update
); );
sub REST_RESOURCES { 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 => { {
method => 'get', GET => {
params => sub { method => 'get',
return { ids => [$_[0]] }; params => sub {
}, return {ids => [$_[0]]};
},
POST => {
method => 'update',
params => sub {
return { ids => [$_[0]] };
},
},
}, },
]; },
POST => {
method => 'update',
params => sub {
return {ids => [$_[0]]};
},
},
},
];
} }
############ ############
...@@ -60,75 +61,75 @@ sub REST_RESOURCES { ...@@ -60,75 +61,75 @@ sub REST_RESOURCES {
############ ############
sub update { sub update {
my ($self, $params) = validate(@_, 'ids'); my ($self, $params) = validate(@_, 'ids');
my $user = Bugzilla->user; my $user = Bugzilla->user;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
$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
# aliases. # aliases.
$user->visible_bugs([grep /^[0-9]+$/, @$ids]); $user->visible_bugs([grep /^[0-9]+$/, @$ids]);
$dbh->bz_start_transaction(); $dbh->bz_start_transaction();
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();
return \@results; return \@results;
} }
sub get { sub get {
my ($self, $params) = validate(@_, 'ids'); my ($self, $params) = validate(@_, 'ids');
my $user = Bugzilla->user; my $user = Bugzilla->user;
my $ids = $params->{ids}; my $ids = $params->{ids};
$user->login(LOGIN_REQUIRED); $user->login(LOGIN_REQUIRED);
my @last_visits; my @last_visits;
if ($ids) { if ($ids) {
# 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 # Cache permissions for bugs. This highly reduces the number of calls to
# skip aliases. # the DB. visible_bugs() is only able to handle bug IDs, so we have to
$user->visible_bugs([grep /^[0-9]+$/, @$ids]); # skip aliases.
$user->visible_bugs([grep /^[0-9]+$/, @$ids]);
my %last_visit = map { $_->bug_id => $_->last_visit_ts } @{ $user->last_visited($ids) };
@last_visits = map { _bug_user_last_visit_to_hash($_, $last_visit{$_}, $params) } @$ids; my %last_visit
} = map { $_->bug_id => $_->last_visit_ts } @{$user->last_visited($ids)};
else { @last_visits
@last_visits = map { = map { _bug_user_last_visit_to_hash($_, $last_visit{$_}, $params) } @$ids;
_bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, $params) }
} @{ $user->last_visited }; else {
} @last_visits
= map { _bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, $params) }
return \@last_visits; @{$user->last_visited};
}
return \@last_visits;
} }
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);
} }
1; 1;
......
...@@ -26,104 +26,76 @@ extends 'Bugzilla::API::1_0::Resource'; ...@@ -26,104 +26,76 @@ 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
last_audit_time last_audit_time
parameters parameters
timezone timezone
time time
version version
); );
use constant PUBLIC_METHODS => qw( use constant PUBLIC_METHODS => qw(
extensions extensions
last_audit_time last_audit_time
parameters parameters
time time
timezone timezone
version version
); );
# Logged-out users do not need to know more than that. # Logged-out users do not need to know more than that.
use constant PARAMETERS_LOGGED_OUT => qw( use constant PARAMETERS_LOGGED_OUT => qw(
maintainer maintainer
requirelogin requirelogin
); );
# These parameters are guessable from the web UI when the user # These parameters are guessable from the web UI when the user
# is logged in. So it's safe to access them. # is logged in. So it's safe to access them.
use constant PARAMETERS_LOGGED_IN => qw( use constant PARAMETERS_LOGGED_IN => qw(
allowemailchange allowemailchange
attachment_base attachment_base
commentonchange_resolution commentonchange_resolution
commentonduplicate commentonduplicate
defaultopsys defaultopsys
defaultplatform defaultplatform
defaultpriority defaultpriority
defaultseverity defaultseverity
duplicate_or_move_bug_status duplicate_or_move_bug_status
emailregexpdesc emailregexpdesc
letsubmitterchoosemilestone letsubmitterchoosemilestone
letsubmitterchoosepriority letsubmitterchoosepriority
mailfrom mailfrom
maintainer maintainer
maxattachmentsize maxattachmentsize
maxlocalattachment maxlocalattachment
noresolveonopenblockers noresolveonopenblockers
password_complexity password_complexity
rememberlogin rememberlogin
requirelogin requirelogin
search_allow_no_criteria search_allow_no_criteria
urlbase urlbase
use_email_as_login use_email_as_login
use_see_also use_see_also
useclassification useclassification
usemenuforusers usemenuforusers
useqacontact useqacontact
usestatuswhiteboard usestatuswhiteboard
usetargetmilestone usetargetmilestone
); );
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' return $rest_resources;
}
},
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;
} }
############ ############
...@@ -131,88 +103,86 @@ sub REST_RESOURCES { ...@@ -131,88 +103,86 @@ 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.
return { timezone => as_string("+0000") }; # All Webservices return times in UTC; Use UTC here for backwards compat.
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.
# Hardcode values where appropriate # All Webservices return times in UTC; Use UTC here for backwards compat.
my $dbh = Bugzilla->dbh; # Hardcode values where appropriate
my $dbh = Bugzilla->dbh;
my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
$db_time = datetime_from($db_time, 'UTC'); my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
my $now_utc = DateTime->now(); $db_time = datetime_from($db_time, 'UTC');
my $now_utc = DateTime->now();
return {
db_time => as_datetime($db_time), return {db_time => as_datetime($db_time), web_time => as_datetime($now_utc),};
web_time => as_datetime($now_utc),
};
} }
sub last_audit_time { sub last_audit_time {
my ($self, $params) = validate(@_, 'class'); my ($self, $params) = validate(@_, 'class');
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $sql_statement = "SELECT MAX(at_time) FROM audit_log"; my $sql_statement = "SELECT MAX(at_time) FROM audit_log";
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_]+)*$/;
} }
if (@class_values_quoted) { if (@class_values_quoted) {
$sql_statement .= " WHERE " . $dbh->sql_in('class', \@class_values_quoted); $sql_statement .= " WHERE " . $dbh->sql_in('class', \@class_values_quoted);
} }
my $last_audit_time = $dbh->selectrow_array("$sql_statement"); my $last_audit_time = $dbh->selectrow_array("$sql_statement");
# 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
$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 {
my ($self, $args) = @_; my ($self, $args) = @_;
my $user = Bugzilla->login(LOGIN_OPTIONAL); my $user = Bugzilla->login(LOGIN_OPTIONAL);
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;
foreach my $param (@params_list) { my %parameters;
next unless filter_wants($args, $param); foreach my $param (@params_list) {
$parameters{$param} = as_string($params->{$param}); next unless filter_wants($args, $param);
} $parameters{$param} = as_string($params->{$param});
}
return { parameters => \%parameters };
return {parameters => \%parameters};
} }
1; 1;
......
...@@ -25,26 +25,27 @@ extends 'Bugzilla::API::1_0::Resource'; ...@@ -25,26 +25,27 @@ extends 'Bugzilla::API::1_0::Resource';
############## ##############
use constant READ_ONLY => qw( use constant READ_ONLY => qw(
get get
); );
use constant PUBLIC_METHODS => qw( use constant PUBLIC_METHODS => qw(
get get
); );
sub REST_RESOURCES { sub REST_RESOURCES {
my $rest_resources = [ my $rest_resources = [
qr{^/classification/([^/]+)$}, { qr{^/classification/([^/]+)$},
GET => { {
method => 'get', GET => {
params => sub { method => 'get',
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names'; params => sub {
return { $param => [ $_[0] ] }; my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
} return {$param => [$_[0]]};
}
} }
]; }
return $rest_resources; }
];
return $rest_resources;
} }
############ ############
...@@ -52,57 +53,68 @@ sub REST_RESOURCES { ...@@ -52,57 +53,68 @@ 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;
Bugzilla->params->{'useclassification'} Bugzilla->params->{'useclassification'}
|| $user->in_group('editclassifications') || $user->in_group('editclassifications')
|| ThrowUserError('auth_classification_not_enabled'); || ThrowUserError('auth_classification_not_enabled');
Bugzilla->switch_to_shadow_db; Bugzilla->switch_to_shadow_db;
my @classification_objs = @{ params_to_objects($params, 'Bugzilla::Classification') }; my @classification_objs
unless ($user->in_group('editclassifications')) { = @{params_to_objects($params, 'Bugzilla::Classification')};
my %selectable_class = map { $_->id => 1 } @{$user->get_selectable_classifications}; unless ($user->in_group('editclassifications')) {
@classification_objs = grep { $selectable_class{$_->id} } @classification_objs; my %selectable_class
} = map { $_->id => 1 } @{$user->get_selectable_classifications};
@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'}
my $products = $user->in_group('editclassifications') ? || $user->in_group('editclassifications'));
$classification->products : $user->get_selectable_products($classification->id);
my $products
return filter $params, { = $user->in_group('editclassifications')
id => as_int($classification->id), ? $classification->products
name => as_string($classification->name), : $user->get_selectable_products($classification->id);
description => as_string($classification->description),
sort_key => as_int($classification->sortkey), return filter $params,
products => [ map { $self->_product_to_hash($_, $params) } @$products ], {
id => as_int($classification->id),
name => as_string($classification->name),
description => as_string($classification->description),
sort_key => as_int($classification->sortkey),
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), {
name => as_string($product->name), id => as_int($product->id),
description => as_string($product->description), name => as_string($product->name),
}, undef, 'products'; description => as_string($product->description),
},
undef, 'products';
} }
1; 1;
......
...@@ -16,18 +16,18 @@ use fields qw(); ...@@ -16,18 +16,18 @@ use fields qw();
# Determines whether or not a user can logout. It's really a subroutine, # Determines whether or not a user can logout. It's really a subroutine,
# but we implement it here as a constant. Override it in subclasses if # but we implement it here as a constant. Override it in subclasses if
# that particular type of login method cannot log out. # that particular type of login method cannot log out.
use constant can_logout => 1; use constant can_logout => 1;
use constant can_login => 1; use constant can_login => 1;
use constant requires_persistence => 1; use constant requires_persistence => 1;
use constant requires_verification => 1; use constant requires_verification => 1;
use constant user_can_create_account => 0; use constant user_can_create_account => 0;
use constant is_automatic => 0; use constant is_automatic => 0;
use constant extern_id_used => 0; use constant extern_id_used => 0;
sub new { sub new {
my ($class) = @_; my ($class) = @_;
my $self = fields::new($class); my $self = fields::new($class);
return $self; return $self;
} }
1; 1;
......
...@@ -26,28 +26,29 @@ use constant can_logout => 0; ...@@ -26,28 +26,29 @@ use constant can_logout => 0;
# This method is only available to web services. An API key can never # This method is only available to web services. An API key can never
# be used to authenticate a Web request. # be used to authenticate a Web request.
sub get_login_info { sub get_login_info {
my ($self) = @_; my ($self) = @_;
my $params = Bugzilla->input_params; my $params = Bugzilla->input_params;
my ($user_id, $login_cookie); my ($user_id, $login_cookie);
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
ThrowUserError("api_key_not_valid");
}
elsif ($api_key->revoked) {
ThrowUserError('api_key_revoked');
}
$api_key->update_last_used(); # The second part checks the correct capitalisation. Silly MySQL
ThrowUserError("api_key_not_valid");
}
elsif ($api_key->revoked) {
ThrowUserError('api_key_revoked');
}
return { user_id => $api_key->user_id }; $api_key->update_last_used();
return {user_id => $api_key->user_id};
} }
1; 1;
...@@ -21,65 +21,71 @@ use Bugzilla::Error; ...@@ -21,65 +21,71 @@ use Bugzilla::Error;
use Bugzilla::Token; use Bugzilla::Token;
sub get_login_info { sub get_login_info {
my ($self) = @_; my ($self) = @_;
my $params = Bugzilla->input_params; my $params = Bugzilla->input_params;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
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.
my $login_token = delete $params->{'Bugzilla_login_token'}; # The token must match the cookie to authenticate the request.
my $login_cookie = $cgi->cookie('Bugzilla_login_request_cookie'); my $login_token = delete $params->{'Bugzilla_login_token'};
my $login_cookie = $cgi->cookie('Bugzilla_login_request_cookie');
my $valid = 0;
# If the web browser accepts cookies, use them. my $valid = 0;
if ($login_token && $login_cookie) {
my ($time, undef) = split(/-/, $login_token); # If the web browser accepts cookies, use them.
# Regenerate the token based on the information we have. if ($login_token && $login_cookie) {
my $expected_token = issue_hash_token(['login_request', $login_cookie], $time); my ($time, undef) = split(/-/, $login_token);
$valid = 1 if $expected_token eq $login_token;
$cgi->remove_cookie('Bugzilla_login_request_cookie'); # Regenerate the token based on the information we have.
} my $expected_token = issue_hash_token(['login_request', $login_cookie], $time);
# WebServices and other local scripts can bypass this check. $valid = 1 if $expected_token eq $login_token;
# This is safe because we won't store a login cookie in this case. $cgi->remove_cookie('Bugzilla_login_request_cookie');
elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) { }
$valid = 1;
} # WebServices and other local scripts can bypass this check.
# Else falls back to the Referer header and accept local URLs. # This is safe because we won't store a login cookie in this case.
# Attachments are served from a separate host (ideally), and so elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
# an evil attachment cannot abuse this check with a redirect. $valid = 1;
elsif (my $referer = $cgi->referer) { }
my $urlbase = correct_urlbase();
$valid = 1 if $referer =~ /^\Q$urlbase\E/; # Else falls back to the Referer header and accept local URLs.
} # Attachments are served from a separate host (ideally), and so
# If the web browser doesn't accept cookies and the Referer header # an evil attachment cannot abuse this check with a redirect.
# is missing, we have no way to make sure that the authentication elsif (my $referer = $cgi->referer) {
# request comes from the user. my $urlbase = correct_urlbase();
elsif ($login && $password) { $valid = 1 if $referer =~ /^\Q$urlbase\E/;
ThrowUserError('auth_untrusted_request', { login => $login }); }
}
# If the web browser doesn't accept cookies and the Referer header
if (!defined($login) || !defined($password) || !$valid) { # is missing, we have no way to make sure that the authentication
return { failure => AUTH_NODATA }; # request comes from the user.
} elsif ($login && $password) {
ThrowUserError('auth_untrusted_request', {login => $login});
return { username => $login, password => $password }; }
if (!defined($login) || !defined($password) || !$valid) {
return {failure => AUTH_NODATA};
}
return {username => $login, password => $password};
} }
sub fail_nodata { sub fail_nodata {
my ($self) = @_; my ($self) = @_;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template; my $template = Bugzilla->template;
if (Bugzilla->usage_mode != USAGE_MODE_BROWSER) { if (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
ThrowUserError('login_required'); ThrowUserError('login_required');
} }
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;
} }
1; 1;
...@@ -23,128 +23,132 @@ use List::Util qw(first); ...@@ -23,128 +23,132 @@ use List::Util qw(first);
use constant requires_persistence => 0; use constant requires_persistence => 0;
use constant requires_verification => 0; use constant requires_verification => 0;
use constant can_login => 0; use constant can_login => 0;
sub is_automatic { return $_[0]->login_token ? 0 : 1; } sub is_automatic { return $_[0]->login_token ? 0 : 1; }
# Note that Cookie never consults the Verifier, it always assumes # Note that Cookie never consults the Verifier, it always assumes
# it has a valid DB account or it fails. # it has a valid DB account or it fails.
sub get_login_info { sub get_login_info {
my ($self) = @_; my ($self) = @_;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my ($user_id, $login_cookie); my ($user_id, $login_cookie);
if (!Bugzilla->request_cache->{auth_no_automatic_login}) { if (!Bugzilla->request_cache->{auth_no_automatic_login}) {
$login_cookie = $cgi->cookie("Bugzilla_logincookie"); $login_cookie = $cgi->cookie("Bugzilla_logincookie");
$user_id = $cgi->cookie("Bugzilla_login"); $user_id = $cgi->cookie("Bugzilla_login");
# 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;
} }
# If the call is for a web service, and an api token is provided, check # If the call is for a web service, and an api token is provided, check
# it is valid. # it is valid.
if (i_am_webservice()) { if (i_am_webservice()) {
if (exists Bugzilla->input_params->{Bugzilla_api_token}) { if (exists Bugzilla->input_params->{Bugzilla_api_token}) {
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) {
# REST requires an api-token when using cookie authentication
# fall back to a non-authenticated request
$login_cookie = '';
}
} }
} }
elsif ($login_cookie && Bugzilla->usage_mode == USAGE_MODE_REST) {
# If no cookies were provided, we also look for a login token # REST requires an api-token when using cookie authentication
# passed in the parameters of a webservice # fall back to a non-authenticated request
my $token = $self->login_token; $login_cookie = '';
if ($token && (!$login_cookie || !$user_id)) { }
($user_id, $login_cookie) = ($token->{'user_id'}, $token->{'login_token'});
} }
}
# If no cookies were provided, we also look for a login token
# passed in the parameters of a webservice
my $token = $self->login_token;
if ($token && (!$login_cookie || !$user_id)) {
($user_id, $login_cookie) = ($token->{'user_id'}, $token->{'login_token'});
}
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 they were not valid and we are using a webservice, then # If the cookie or token is valid, return a valid username.
# throw an error notifying the client. # If they were not valid and we are using a webservice, then
if (defined $db_cookie && $login_cookie eq $db_cookie) { # throw an error notifying the client.
# If we logged in successfully, then update the lastused if (defined $db_cookie && $login_cookie eq $db_cookie) {
# time on the login cookie
$dbh->do("UPDATE logincookies SET lastused = NOW() # If we logged in successfully, then update the lastused
WHERE cookie = ?", undef, $login_cookie); # time on the login cookie
return { user_id => $user_id }; $dbh->do(
} "UPDATE logincookies SET lastused = NOW()
elsif (i_am_webservice()) { WHERE cookie = ?", undef, $login_cookie
ThrowUserError('invalid_cookies_or_token'); );
} return {user_id => $user_id};
} }
elsif (i_am_webservice()) {
# Either the cookie or token is invalid and we are not authenticating ThrowUserError('invalid_cookies_or_token');
# via a webservice, or we did not receive a cookie or token. We don't }
# 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
# look like there was no cookie or token to begin with. # Either the cookie or token is invalid and we are not authenticating
return { failure => AUTH_NODATA }; # via a webservice, or we did not receive a cookie or token. We don't
# 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
# look like there was no cookie or token to begin with.
return {failure => AUTH_NODATA};
} }
sub login_token { sub login_token {
my ($self) = @_; my ($self) = @_;
my $input = Bugzilla->input_params; my $input = Bugzilla->input_params;
my $usage_mode = Bugzilla->usage_mode; my $usage_mode = Bugzilla->usage_mode;
return $self->{'_login_token'} if exists $self->{'_login_token'}; return $self->{'_login_token'} if exists $self->{'_login_token'};
if (!i_am_webservice()) { if (!i_am_webservice()) {
return $self->{'_login_token'} = undef; return $self->{'_login_token'} = undef;
} }
# Check if a token was passed in via requests for WebServices # Check if a token was passed in via requests for WebServices
my $token = trim(delete $input->{'Bugzilla_token'}); my $token = trim(delete $input->{'Bugzilla_token'});
return $self->{'_login_token'} = undef if !$token; return $self->{'_login_token'} = undef if !$token;
my ($user_id, $login_token) = split('-', $token, 2); my ($user_id, $login_token) = split('-', $token, 2);
if (!detaint_natural($user_id) || !$login_token) { if (!detaint_natural($user_id) || !$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;
...@@ -16,28 +16,31 @@ use parent qw(Bugzilla::Auth::Login); ...@@ -16,28 +16,31 @@ use parent qw(Bugzilla::Auth::Login);
use Bugzilla::Constants; use Bugzilla::Constants;
use Bugzilla::Error; use Bugzilla::Error;
use constant can_logout => 0; use constant can_logout => 0;
use constant can_login => 0; use constant can_login => 0;
use constant requires_persistence => 0; use constant requires_persistence => 0;
use constant requires_verification => 0; use constant requires_verification => 0;
use constant is_automatic => 1; use constant is_automatic => 1;
use constant extern_id_used => 1; use constant extern_id_used => 1;
sub get_login_info { sub get_login_info {
my ($self) = @_; my ($self) = @_;
my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || ''; my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || '';
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 {
ThrowCodeError('env_no_email'); ThrowCodeError('env_no_email');
} }
1; 1;
...@@ -13,8 +13,8 @@ use warnings; ...@@ -13,8 +13,8 @@ use warnings;
use base qw(Bugzilla::Auth::Login); use base qw(Bugzilla::Auth::Login);
use fields qw( use fields qw(
_stack _stack
successful successful
); );
use Hash::Util qw(lock_keys); use Hash::Util qw(lock_keys);
use Bugzilla::Hook; use Bugzilla::Hook;
...@@ -22,81 +22,87 @@ use Bugzilla::Constants; ...@@ -22,81 +22,87 @@ use Bugzilla::Constants;
use List::MoreUtils qw(any); use List::MoreUtils qw(any);
sub new { sub new {
my $class = shift; my $class = shift;
my $self = $class->SUPER::new(@_); my $self = $class->SUPER::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)) {
my $module = $methods{$login_method}; my $module = $methods{$login_method};
require $module; require $module;
$module =~ s|/|::|g; $module =~ s|/|::|g;
$module =~ s/.pm$//; $module =~ s/.pm$//;
push(@{$self->{_stack}}, $module->new(@_)); push(@{$self->{_stack}}, $module->new(@_));
} }
return $self; return $self;
} }
sub get_login_info { 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
# auth_no_automatic_login is used. # See Bugzilla::WebService::Server::JSONRPC for where and why
if (Bugzilla->request_cache->{auth_no_automatic_login}) { # auth_no_automatic_login is used.
next if $object->is_automatic; if (Bugzilla->request_cache->{auth_no_automatic_login}) {
} next if $object->is_automatic;
$result = $object->get_login_info(@_);
$self->{successful} = $object;
# We only carry on down the stack if this method denied all knowledge.
last unless ($result->{failure}
&& ($result->{failure} eq AUTH_NODATA
|| $result->{failure} eq AUTH_NO_SUCH_USER));
# If none of the methods succeed, it's undef.
$self->{successful} = undef;
} }
return $result; $result = $object->get_login_info(@_);
$self->{successful} = $object;
# We only carry on down the stack if this method denied all knowledge.
last
unless ($result->{failure}
&& ( $result->{failure} eq AUTH_NODATA
|| $result->{failure} eq AUTH_NO_SUCH_USER));
# If none of the methods succeed, it's undef.
$self->{successful} = undef;
}
return $result;
} }
sub fail_nodata { sub fail_nodata {
my $self = shift; my $self = shift;
# We fail from the bottom of the stack.
my @reverse_stack = reverse @{$self->{_stack}}; # We fail from the bottom of the stack.
foreach my $object (@reverse_stack) { my @reverse_stack = reverse @{$self->{_stack}};
# We pick the first object that actually has the method foreach my $object (@reverse_stack) {
# implemented.
if ($object->can('fail_nodata')) { # We pick the first object that actually has the method
$object->fail_nodata(@_); # implemented.
} if ($object->can('fail_nodata')) {
$object->fail_nodata(@_);
} }
}
} }
sub can_login { sub can_login {
my ($self) = @_; my ($self) = @_;
# We return true if any method can log in.
foreach my $object (@{$self->{_stack}}) { # We return true if any method can log in.
return 1 if $object->can_login; foreach my $object (@{$self->{_stack}}) {
} return 1 if $object->can_login;
return 0; }
return 0;
} }
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.
foreach my $object (@{$self->{_stack}}) { # We return true if any method allows users to create accounts.
return 1 if $object->user_can_create_account; foreach my $object (@{$self->{_stack}}) {
} return 1 if $object->user_can_create_account;
return 0; }
return 0;
} }
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;
...@@ -19,119 +19,131 @@ use Bugzilla::User; ...@@ -19,119 +19,131 @@ use Bugzilla::User;
use Bugzilla::Util; use Bugzilla::Util;
use constant user_can_create_account => 1; use constant user_can_create_account => 1;
use constant extern_id_used => 0; use constant extern_id_used => 0;
sub new { sub new {
my ($class, $login_type) = @_; my ($class, $login_type) = @_;
my $self = fields::new($class); my $self = fields::new($class);
return $self; return $self;
} }
sub can_change_password { sub can_change_password {
return $_[0]->can('change_password'); return $_[0]->can('change_password');
} }
sub create_or_update_user { sub create_or_update_user {
my ($self, $params) = @_; my ($self, $params) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $extern_id = $params->{extern_id}; my $extern_id = $params->{extern_id};
my $login = $params->{bz_username} || $params->{username}; my $login = $params->{bz_username} || $params->{username};
my $email = Bugzilla->params->{use_email_as_login} ? $login : $params->{email}; my $email = Bugzilla->params->{use_email_as_login} ? $login : $params->{email};
my $password = $params->{password} || '*'; my $password = $params->{password} || '*';
my $real_name = $params->{realname} || ''; my $real_name = $params->{realname} || '';
my $user_id = $params->{user_id}; my $user_id = $params->{user_id};
# A passed-in user_id always overrides anything else, for determining # A passed-in user_id always overrides anything else, for determining
# what account we should return. # what account we should return.
if (!$user_id) { if (!$user_id) {
my $login_user_id = login_to_id($login || ''); my $login_user_id = login_to_id($login || '');
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
# not the same id, then we have a conflict.
if ($login_user_id && $extern_user_id
&& $login_user_id ne $extern_user_id)
{
my $extern_name = Bugzilla::User->new($extern_user_id)->login;
return { failure => AUTH_ERROR, error => "extern_id_conflict",
details => {extern_id => $extern_id,
extern_user => $extern_name,
username => $login} };
}
# If we have a valid login, but no valid id,
# then we have to create the user. This happens when we're
# passed only a login, and that login doesn't exist already.
if ($login && !$login_user_id && !$extern_user_id) {
validate_email_syntax($email)
|| return { failure => AUTH_ERROR,
error => 'auth_invalid_email',
details => {addr => $email} };
# Usually we'd call validate_password, but external authentication
# systems might follow different standards than ours. So in this
# place here, we call trick_taint without checks.
trick_taint($password);
# XXX Theoretically this could fail with an error, but the fix for
# that is too involved to be done right now.
my $user = Bugzilla::User->create({
login_name => $login,
email => $email,
cryptpassword => $password,
realname => $real_name});
$login_user_id = $user->id;
}
# If we have a valid login id and an extern_id, but no valid
# extern_user_id, then we have to set the user's extern_id.
if ($extern_id && $login_user_id && !$extern_user_id) {
$dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
undef, $extern_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.
$user_id = $extern_user_id || $login_user_id;
} }
# If we still don't have a valid user_id, then we weren't passed # If we have both a valid extern_id and a valid login, and they are
# enough information in $params, and we should die right here. # not the same id, then we have a conflict.
ThrowCodeError('bad_arg', {argument => 'params', function => if ($login_user_id && $extern_user_id && $login_user_id ne $extern_user_id) {
'Bugzilla::Auth::Verify::create_or_update_user'}) my $extern_name = Bugzilla::User->new($extern_user_id)->login;
unless $user_id; return {
failure => AUTH_ERROR,
my $user = new Bugzilla::User($user_id); error => "extern_id_conflict",
details =>
# Now that we have a valid User, we need to see if any data has to be updated. {extern_id => $extern_id, extern_user => $extern_name, username => $login}
my $changed = 0; };
if ($email && lc($user->email) ne lc($email)) {
validate_email_syntax($email)
|| return { failure => AUTH_ERROR, error => 'auth_invalid_email',
details => {addr => $email} };
$user->set_email($email);
$changed = 1;
} }
if ($login && lc($user->login) ne lc($login)) {
$user->set_login($login); # If we have a valid login, but no valid id,
$changed = 1; # then we have to create the user. This happens when we're
# passed only a login, and that login doesn't exist already.
if ($login && !$login_user_id && !$extern_user_id) {
validate_email_syntax($email) || return {
failure => AUTH_ERROR,
error => 'auth_invalid_email',
details => {addr => $email}
};
# Usually we'd call validate_password, but external authentication
# systems might follow different standards than ours. So in this
# place here, we call trick_taint without checks.
trick_taint($password);
# XXX Theoretically this could fail with an error, but the fix for
# that is too involved to be done right now.
my $user = Bugzilla::User->create({
login_name => $login,
email => $email,
cryptpassword => $password,
realname => $real_name
});
$login_user_id = $user->id;
} }
if ($real_name && $user->name ne $real_name) {
# $real_name is more than likely tainted, but we only use it # If we have a valid login id and an extern_id, but no valid
# in a placeholder and we never use it after this. # extern_user_id, then we have to set the user's extern_id.
trick_taint($real_name); if ($extern_id && $login_user_id && !$extern_user_id) {
$user->set_name($real_name); $dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
$changed = 1; undef, $extern_id, $login_user_id);
Bugzilla->memcached->clear({table => 'profiles', id => $login_user_id});
} }
$user->update() if $changed;
return { user => $user }; # Finally, at this point, one of these will give us a valid user id.
$user_id = $extern_user_id || $login_user_id;
}
# 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.
ThrowCodeError(
'bad_arg',
{
argument => 'params',
function => 'Bugzilla::Auth::Verify::create_or_update_user'
}
) unless $user_id;
my $user = new Bugzilla::User($user_id);
# Now that we have a valid User, we need to see if any data has to be updated.
my $changed = 0;
if ($email && lc($user->email) ne lc($email)) {
validate_email_syntax($email) || return {
failure => AUTH_ERROR,
error => 'auth_invalid_email',
details => {addr => $email}
};
$user->set_email($email);
$changed = 1;
}
if ($login && lc($user->login) ne lc($login)) {
$user->set_login($login);
$changed = 1;
}
if ($real_name && $user->name ne $real_name) {
# $real_name is more than likely tainted, but we only use it
# in a placeholder and we never use it after this.
trick_taint($real_name);
$user->set_name($real_name);
$changed = 1;
}
$user->update() if $changed;
return {user => $user};
} }
1; 1;
......
...@@ -19,95 +19,97 @@ use Bugzilla::Util; ...@@ -19,95 +19,97 @@ use Bugzilla::Util;
use Bugzilla::User; use Bugzilla::User;
sub check_credentials { sub check_credentials {
my ($self, $login_data) = @_; my ($self, $login_data) = @_;
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) {
return {failure => AUTH_LOCKOUT, user => $user};
}
my $password = $login_data->{password};
my $real_password_crypted = $user->cryptpassword;
# Using the internal crypted password as the salt,
# crypt the password the user entered.
my $entered_password_crypted = bz_crypt($password, $real_password_crypted);
if ($entered_password_crypted ne $real_password_crypted) {
# Record the login failure
$user->note_login_failure();
# 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};
} }
my $password = $login_data->{password}; return {
my $real_password_crypted = $user->cryptpassword; failure => AUTH_LOGINFAILED,
failure_count => scalar(@{$user->account_ip_login_failures}),
# Using the internal crypted password as the salt, };
# crypt the password the user entered. }
my $entered_password_crypted = bz_crypt($password, $real_password_crypted);
# Force the user to change their password if it does not meet the current
if ($entered_password_crypted ne $real_password_crypted) { # criteria. This should usually only happen if the criteria has changed.
# Record the login failure if ( Bugzilla->usage_mode == USAGE_MODE_BROWSER
$user->note_login_failure(); && Bugzilla->params->{password_check_on_login})
{
# Immediately check if we are locked out my $check = validate_password_check($password);
if ($user->account_is_locked_out) { if ($check) {
return { failure => AUTH_LOCKOUT, user => $user, return {
just_locked_out => 1 }; failure => AUTH_ERROR,
} user_error => $check,
details => {locked_user => $user}
return { 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
# criteria. This should usually only happen if the criteria has changed.
if (Bugzilla->usage_mode == USAGE_MODE_BROWSER &&
Bugzilla->params->{password_check_on_login})
{
my $check = validate_password_check($password);
if ($check) {
return {
failure => AUTH_ERROR,
user_error => $check,
details => { locked_user => $user }
}
}
} }
}
# The user's credentials are okay, so delete any outstanding # The user's credentials are okay, so delete any outstanding
# password tokens or login failures they may have generated. # password tokens or login failures they may have generated.
Bugzilla::Token::DeletePasswordTokens($user->id, "user_logged_in"); Bugzilla::Token::DeletePasswordTokens($user->id, "user_logged_in");
$user->clear_login_failures(); $user->clear_login_failures();
my $update_password = 0; my $update_password = 0;
# If their old password was using crypt() or some different hash # If their old password was using crypt() or some different hash
# than we're using now, convert the stored password to using # than we're using now, convert the stored password to using
# whatever hashing system we're using now. # whatever hashing system we're using now.
my $current_algorithm = PASSWORD_DIGEST_ALGORITHM; my $current_algorithm = PASSWORD_DIGEST_ALGORITHM;
$update_password = 1 if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/); $update_password = 1 if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/);
# If their old password was using a different length salt than what # If their old password was using a different length salt than what
# we're using now, update the password to use the new salt length. # we're using now, update the password to use the new salt length.
if ($real_password_crypted =~ /^([^,]+),/) { if ($real_password_crypted =~ /^([^,]+),/) {
$update_password = 1 if (length($1) != PASSWORD_SALT_LENGTH); $update_password = 1 if (length($1) != PASSWORD_SALT_LENGTH);
} }
# 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
# complexity rules to apply here. # We can't call $user->set_password because we don't want the password
$user->{cryptpassword} = bz_crypt($password); # complexity rules to apply here.
$user->update(); $user->{cryptpassword} = bz_crypt($password);
} $user->update();
}
return $login_data; return $login_data;
} }
sub change_password { sub change_password {
my ($self, $user, $password) = @_; my ($self, $user, $password) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
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;
...@@ -23,35 +23,39 @@ use constant admin_can_create_account => 0; ...@@ -23,35 +23,39 @@ use constant admin_can_create_account => 0;
use constant user_can_create_account => 0; use constant user_can_create_account => 0;
sub check_credentials { sub check_credentials {
my ($self, $params) = @_; my ($self, $params) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'}; my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'};
my $username = $params->{username}; my $username = $params->{username};
# If we're using RADIUS_email_suffix, we may need to cut it off from # If we're using RADIUS_email_suffix, we may need to cut it off from
# the login name. # the login name.
if ($address_suffix) { if ($address_suffix) {
$username =~ s/\Q$address_suffix\E$//i; $username =~ s/\Q$address_suffix\E$//i;
} }
# 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,
# Check the password. error => 'radius_preparation_error',
$radius->check_pwd($username, $params->{password}, details => {errstr => Authen::Radius::strerror()}
Bugzilla->params->{'RADIUS_NAS_IP'} || undef) };
|| return { failure => AUTH_LOGINFAILED };
# Check the password.
$params->{bz_username} = $username; $radius->check_pwd($username, $params->{password},
Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
# Build the user account's e-mail address. || return {failure => AUTH_LOGINFAILED};
$params->{email} = $username . $address_suffix;
$params->{bz_username} = $username;
return $params;
# Build the user account's e-mail address.
$params->{email} = $username . $address_suffix;
return $params;
} }
1; 1;
...@@ -13,8 +13,8 @@ use warnings; ...@@ -13,8 +13,8 @@ use warnings;
use base qw(Bugzilla::Auth::Verify); use base qw(Bugzilla::Auth::Verify);
use fields qw( use fields qw(
_stack _stack
successful successful
); );
use Bugzilla::Hook; use Bugzilla::Hook;
...@@ -23,70 +23,75 @@ use Hash::Util qw(lock_keys); ...@@ -23,70 +23,75 @@ use Hash::Util qw(lock_keys);
use List::MoreUtils qw(any); use List::MoreUtils qw(any);
sub new { sub new {
my $class = shift; my $class = shift;
my $list = shift; my $list = shift;
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)) {
my $module = $methods{$verify_method}; my $module = $methods{$verify_method};
require $module; require $module;
$module =~ s|/|::|g; $module =~ s|/|::|g;
$module =~ s/.pm$//; $module =~ s/.pm$//;
push(@{$self->{_stack}}, $module->new(@_)); push(@{$self->{_stack}}, $module->new(@_));
} }
return $self; return $self;
} }
sub can_change_password { sub can_change_password {
my ($self) = @_; my ($self) = @_;
# We return true if any method can change passwords.
foreach my $object (@{$self->{_stack}}) { # We return true if any method can change passwords.
return 1 if $object->can_change_password; foreach my $object (@{$self->{_stack}}) {
} return 1 if $object->can_change_password;
return 0; }
return 0;
} }
sub check_credentials { sub check_credentials {
my $self = shift; my $self = shift;
my $result; my $result;
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
$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.
$self->{successful} = undef; # So that if none of them succeed, it's undef.
} $self->{successful} = undef;
# Returns the result at the bottom of the stack if they all fail. }
return $result;
# Returns the result at the bottom of the stack if they all fail.
return $result;
} }
sub create_or_update_user { sub create_or_update_user {
my $self = shift; my $self = shift;
my $result; my $result;
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
$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.
return $result; # Returns the result at the bottom of the stack if they all fail.
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.
foreach my $object (@{$self->{_stack}}) { # We return true if any method allows the user to create an account.
return 1 if $object->user_can_create_account; foreach my $object (@{$self->{_stack}}) {
} return 1 if $object->user_can_create_account;
return 0; }
return 0;
} }
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,48 +28,49 @@ use URI::QueryParam; ...@@ -28,48 +28,49 @@ 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;
use constant AUDIT_REMOVES => 0; use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw( use constant DB_COLUMNS => qw(
id id
bug_id bug_id
value value
class class
); );
# This must be strings with the names of the validations, # This must be strings with the names of the validations,
# instead of coderefs, because subclasses override these # instead of coderefs, because subclasses override these
# validators with their own. # validators with their own.
use constant VALIDATORS => { use constant VALIDATORS => {
value => '_check_value', value => '_check_value',
bug_id => '_check_bug_id', bug_id => '_check_bug_id',
class => \&_check_class, class => \&_check_class,
}; };
# This is the order we go through all of subclasses and # This is the order we go through all of subclasses and
# pick the first one that should handle the url. New # pick the first one that should handle the url. New
# subclasses should be added at the end of the list. # subclasses should be added at the end of the list.
use constant SUB_CLASSES => qw( use constant SUB_CLASSES => qw(
Bugzilla::BugUrl::Bugzilla::Local Bugzilla::BugUrl::Bugzilla::Local
Bugzilla::BugUrl::Bugzilla Bugzilla::BugUrl::Bugzilla
Bugzilla::BugUrl::Launchpad Bugzilla::BugUrl::Launchpad
Bugzilla::BugUrl::Google Bugzilla::BugUrl::Google
Bugzilla::BugUrl::Debian Bugzilla::BugUrl::Debian
Bugzilla::BugUrl::JIRA Bugzilla::BugUrl::JIRA
Bugzilla::BugUrl::Trac Bugzilla::BugUrl::Trac
Bugzilla::BugUrl::MantisBT Bugzilla::BugUrl::MantisBT
Bugzilla::BugUrl::SourceForge Bugzilla::BugUrl::SourceForge
Bugzilla::BugUrl::GitHub Bugzilla::BugUrl::GitHub
); );
############################### ###############################
#### Accessors ###### #### Accessors ######
############################### ###############################
sub class { return $_[0]->{class} } sub class { return $_[0]->{class} }
sub bug_id { return $_[0]->{bug_id} } sub bug_id { return $_[0]->{bug_id} }
############################### ###############################
...@@ -77,130 +78,127 @@ sub bug_id { return $_[0]->{bug_id} } ...@@ -77,130 +78,127 @@ sub bug_id { return $_[0]->{bug_id} }
############################### ###############################
sub new { sub new {
my $class = shift; my $class = shift;
my $param = shift; my $param = shift;
if (ref $param) { if (ref $param) {
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) {
} ThrowCodeError('bad_arg', {argument => 'name', function => "${class}::new"});
if (!defined $name) {
ThrowCodeError('bad_arg',
{ argument => 'name',
function => "${class}::new" });
}
my $condition = 'bug_id = ? AND value = ?';
my @values = ($bug_id, $name);
$param = { condition => $condition, values => \@values };
} }
unshift @_, $param; my $condition = 'bug_id = ? AND value = ?';
return $class->SUPER::new(@_); my @values = ($bug_id, $name);
$param = {condition => $condition, values => \@values};
}
unshift @_, $param;
return $class->SUPER::new(@_);
} }
sub _do_list_select { sub _do_list_select {
my $class = shift; my $class = shift;
my $objects = $class->SUPER::_do_list_select(@_); my $objects = $class->SUPER::_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.
bless $object, ($@ ? 'Bugzilla::BugUrl' : $object->class); # If the class cannot be loaded, then we build a generic object.
} 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;
Bugzilla::Hook::process("bug_url_sub_classes",
{ sub_classes => \@sub_classes });
my $uri = URI->new($value);
foreach my $subclass (@sub_classes) {
eval "use $subclass";
die $@ if $@;
return wantarray ? ($subclass, $uri) : $subclass
if $subclass->should_handle($uri);
}
ThrowUserError('bug_url_invalid', { url => $value }); my @sub_classes = $class->SUB_CLASSES;
Bugzilla::Hook::process("bug_url_sub_classes", {sub_classes => \@sub_classes});
my $uri = URI->new($value);
foreach my $subclass (@sub_classes) {
eval "use $subclass";
die $@ if $@;
return wantarray ? ($subclass, $uri) : $subclass
if $subclass->should_handle($uri);
}
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";
return $subclass; die $@ if $@;
return $subclass;
} }
sub _check_bug_id { sub _check_bug_id {
my ($class, $bug_id) = @_; my ($class, $bug_id) = @_;
my $bug; my $bug;
if (blessed $bug_id) { if (blessed $bug_id) {
# We got a bug object passed in, use it
$bug = $bug_id; # We got a bug object passed in, use it
$bug->check_is_visible; $bug = $bug_id;
} $bug->check_is_visible;
else { }
# We got a bug id passed in, check it and get the bug object else {
$bug = Bugzilla::Bug->check({ id => $bug_id }); # We got a bug id passed in, check it and get the bug object
} $bug = Bugzilla::Bug->check({id => $bug_id});
}
return $bug->id; return $bug->id;
} }
sub _check_value { sub _check_value {
my ($class, $uri) = @_; my ($class, $uri) = @_;
my $value = $uri->as_string; my $value = $uri->as_string;
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
# that creates URLs like "http:domain.com" and doesn't properly # This works better than setting $uri->scheme('http'), because
# differentiate the path from the domain. # that creates URLs like "http:domain.com" and doesn't properly
$uri = new URI("http://$value"); # differentiate the path from the domain.
} $uri = new URI("http://$value");
elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') { }
ThrowUserError('bug_url_invalid', { url => $value, reason => 'http' }); elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
} ThrowUserError('bug_url_invalid', {url => $value, reason => 'http'});
}
# This stops the following edge cases from being accepted:
# * show_bug.cgi?id=1 # This stops the following edge cases from being accepted:
# * /show_bug.cgi?id=1 # * show_bug.cgi?id=1
# * http:///show_bug.cgi?id=1 # * /show_bug.cgi?id=1
if (!$uri->authority or $uri->path !~ m{/}) { # * http:///show_bug.cgi?id=1
ThrowUserError('bug_url_invalid', if (!$uri->authority or $uri->path !~ m{/}) {
{ url => $value, reason => 'path_only' }); ThrowUserError('bug_url_invalid', {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;
} }
1; 1;
......
...@@ -21,37 +21,39 @@ use Bugzilla::Util; ...@@ -21,37 +21,39 @@ use Bugzilla::Util;
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0; return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my ($class, $uri) = @_; my ($class, $uri) = @_;
$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
# if somebody's putting both an alias link and a numeric ID link. # We don't currently allow aliases, because we can't check to see
# When we start validating the URL by accessing the other Bugzilla, # if somebody's putting both an alias link and a numeric ID link.
# we can allow aliases. # When we start validating the URL by accessing the other Bugzilla,
detaint_natural($bug_id); # we can allow aliases.
if (!$bug_id) { detaint_natural($bug_id);
my $value = $uri->as_string; if (!$bug_id) {
ThrowUserError('bug_url_invalid', { url => $value, reason => 'id' }); my $value = $uri->as_string;
} ThrowUserError('bug_url_invalid', {url => $value, reason => 'id'});
}
# Make sure that "id" is the only query parameter.
$uri->query("id=$bug_id"); # Make sure that "id" is the only query parameter.
# And remove any # part if there is one. $uri->query("id=$bug_id");
$uri->fragment(undef);
# And remove any # part if there is one.
return $uri; $uri->fragment(undef);
return $uri;
} }
sub target_bug_id { sub target_bug_id {
my ($self) = @_; my ($self) = @_;
return new URI($self->name)->query_param('id'); return new URI($self->name)->query_param('id');
} }
1; 1;
...@@ -20,77 +20,75 @@ use Bugzilla::Util; ...@@ -20,77 +20,75 @@ use Bugzilla::Util;
#### Initialization #### #### Initialization ####
############################### ###############################
use constant VALIDATOR_DEPENDENCIES => { use constant VALIDATOR_DEPENDENCIES => {value => ['bug_id'],};
value => ['bug_id'],
};
############################### ###############################
#### Methods #### #### Methods ####
############################### ###############################
sub ref_bug_url { sub ref_bug_url {
my $self = shift; my $self = shift;
if (!exists $self->{ref_bug_url}) { if (!exists $self->{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};
} }
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Check if it is either a bug id number or an alias. # Check if it is either a bug id number or an alias.
return 1 if $uri->as_string =~ m/^\w+$/; return 1 if $uri->as_string =~ m/^\w+$/;
# Check if it is a local Bugzilla uri and call # Check if it is a local Bugzilla uri and call
# 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);
} }
return 0; return 0;
} }
sub _check_value { sub _check_value {
my ($class, $uri, undef, $params) = @_; my ($class, $uri, undef, $params) = @_;
# At this point we are going to treat any word as a # At this point we are going to treat any word as a
# bug id/alias to the local Bugzilla. # bug id/alias to the local Bugzilla.
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 { }
# It's not a word, then we have to check else {
# if it's a valid Bugzilla url. # It's not a word, then we have to check
$uri = $class->SUPER::_check_value($uri); # if it's a valid Bugzilla url.
} $uri = $class->SUPER::_check_value($uri);
}
my $ref_bug_id = $uri->query_param('id');
my $ref_bug = Bugzilla::Bug->check($ref_bug_id); my $ref_bug_id = $uri->query_param('id');
my $self_bug_id = $params->{bug_id}; my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
$params->{ref_bug} = $ref_bug; my $self_bug_id = $params->{bug_id};
$params->{ref_bug} = $ref_bug;
if ($ref_bug->id == $self_bug_id) {
ThrowUserError('see_also_self_reference'); if ($ref_bug->id == $self_bug_id) {
} ThrowUserError('see_also_self_reference');
}
return $uri;
return $uri;
} }
sub local_uri { sub local_uri {
my ($self, $bug_id) = @_; my ($self, $bug_id) = @_;
$bug_id ||= ''; $bug_id ||= '';
return correct_urlbase() . "show_bug.cgi?id=$bug_id"; return correct_urlbase() . "show_bug.cgi?id=$bug_id";
} }
1; 1;
...@@ -18,31 +18,35 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,31 +18,35 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Debian BTS URLs can look like various things: # Debian BTS URLs can look like various things:
# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234 # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
# 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 {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_check_value(@_);
# This is the shortest standard URL form for Debian BTS URLs, # This is the shortest standard URL form for Debian BTS URLs,
# and so we reduce all URLs to this. # and so we reduce all URLs to this.
$uri->path =~ m|^/(\d+)$|a || $uri->query_param('bug') =~ m|^(\d+)$|a; $uri->path =~ m|^/(\d+)$|a || $uri->query_param('bug') =~ m|^(\d+)$|a;
$uri = new URI('https://' . $uri->authority . '/' . $1); $uri = new URI('https://' . $uri->authority . '/' . $1);
return $uri; return $uri;
} }
1; 1;
...@@ -18,25 +18,25 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,25 +18,25 @@ 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;
} }
sub _check_value { sub _check_value {
my ($class, $uri) = @_; my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri); $uri = $class->SUPER::_check_value($uri);
# GitHub HTTP URLs redirect to HTTPS, so just use the HTTPS scheme. # GitHub HTTP URLs redirect to HTTPS, so just use the HTTPS scheme.
$uri->scheme('https'); $uri->scheme('https');
return $uri; return $uri;
} }
1; 1;
...@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Google Code URLs only have one form: # Google Code URLs only have one form:
# http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234 # http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
return (lc($uri->authority) eq 'code.google.com' return (lc($uri->authority) eq 'code.google.com'
and $uri->path =~ m|^/p/[^/]+/issues/detail$| and $uri->path =~ m|^/p/[^/]+/issues/detail$|
and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0; and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my ($class, $uri) = @_; my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri);
# While Google Code URLs can be either HTTP or HTTPS, $uri = $class->SUPER::_check_value($uri);
# always go with the HTTP scheme, as that's the default.
if ($uri->scheme eq 'https') {
$uri->scheme('http');
}
return $uri; # While Google Code URLs can be either HTTP or HTTPS,
# always go with the HTTP scheme, as that's the default.
if ($uri->scheme eq 'https') {
$uri->scheme('http');
}
return $uri;
} }
1; 1;
...@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# JIRA URLs have only one basic form (but the jira is optional): # JIRA URLs have only one basic form (but the jira is optional):
# https://issues.apache.org/jira/browse/KEY-1234 # https://issues.apache.org/jira/browse/KEY-1234
# http://issues.example.com/browse/KEY-1234 # http://issues.example.com/browse/KEY-1234
return ($uri->path =~ m|/browse/[A-Z][A-Z]+-\d+$|) ? 1 : 0; return ($uri->path =~ m|/browse/[A-Z][A-Z]+-\d+$|) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_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.
$uri->fragment(undef);
return $uri; # And remove any # part if there is one.
$uri->fragment(undef);
return $uri;
} }
1; 1;
...@@ -18,27 +18,28 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,27 +18,28 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Launchpad bug URLs can look like various things: # Launchpad bug URLs can look like various things:
# 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 {
my ($class, $uri) = @_; my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri); $uri = $class->SUPER::_check_value($uri);
# This is the shortest standard URL form for Launchpad bugs, # This is the shortest standard URL form for Launchpad bugs,
# and so we reduce all URLs to this. # and so we reduce all URLs to this.
$uri->path =~ m|bugs?/(\d+)$|a; $uri->path =~ m|bugs?/(\d+)$|a;
$uri = new URI("https://launchpad.net/bugs/$1"); $uri = new URI("https://launchpad.net/bugs/$1");
return $uri; return $uri;
} }
1; 1;
...@@ -18,22 +18,22 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,22 +18,22 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# MantisBT URLs look like the following ('bugs' directory is optional): # MantisBT URLs look like the following ('bugs' directory is optional):
# http://www.mantisbt.org/bugs/view.php?id=1234 # http://www.mantisbt.org/bugs/view.php?id=1234
return ($uri->path_query =~ m|view\.php\?id=\d+$|) ? 1 : 0; return ($uri->path_query =~ m|view\.php\?id=\d+$|) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_check_value(@_);
# Remove any # part if there is one. # Remove any # part if there is one.
$uri->fragment(undef); $uri->fragment(undef);
return $uri; return $uri;
} }
1; 1;
...@@ -18,38 +18,45 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,38 +18,45 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# SourceForge tracker URLs have only one form: # SourceForge tracker URLs have only one form:
# http://sourceforge.net/tracker/?func=detail&aid=111&group_id=111&atid=111 # http://sourceforge.net/tracker/?func=detail&aid=111&group_id=111&atid=111
# SourceForge Allura ticket URLs have several forms: # SourceForge Allura ticket URLs have several forms:
# http://sourceforge.net/p/project/bugs/12345/ # http://sourceforge.net/p/project/bugs/12345/
# 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->query_param('func') eq 'detail' and (
and $uri->query_param('aid') (
and $uri->query_param('group_id') $uri->path eq '/tracker/'
and $uri->query_param('atid')) and $uri->query_param('func') eq 'detail'
or $uri->path =~ m!^/p/[^/]+/(?:bugs|feature-requests|patches|support-requests)/\d+/?$!)) ? 1 : 0; and $uri->query_param('aid')
and $uri->query_param('group_id')
and $uri->query_param('atid')
)
or $uri->path
=~ m!^/p/[^/]+/(?:bugs|feature-requests|patches|support-requests)/\d+/?$!
)
) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_check_value(@_);
# Remove any # part if there is one. # Remove any # part if there is one.
$uri->fragment(undef); $uri->fragment(undef);
# Make sure the trailing slash is present # Make sure the trailing slash is present
my $path = $uri->path; my $path = $uri->path;
$path =~ s!/*$!/!; $path =~ s!/*$!/!;
$uri->path($path); $uri->path($path);
return $uri; return $uri;
} }
1; 1;
...@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Trac URLs can look like various things: # Trac URLs can look like various things:
# http://dev.mutt.org/trac/ticket/1234 # http://dev.mutt.org/trac/ticket/1234
# http://trac.roundcube.net/ticket/1484130 # http://trac.roundcube.net/ticket/1484130
return ($uri->path =~ m|/ticket/\d+$|) ? 1 : 0; return ($uri->path =~ m|/ticket/\d+$|) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_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.
$uri->fragment(undef);
return $uri; # And remove any # part if there is one.
$uri->fragment(undef);
return $uri;
} }
1; 1;
...@@ -25,25 +25,27 @@ use constant LIST_ORDER => 'id'; ...@@ -25,25 +25,27 @@ 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_UPDATES => 0, AUDIT_CREATES => 0,
AUDIT_REMOVES => 0, AUDIT_UPDATES => 0,
USE_MEMCACHED => 0 }; AUDIT_REMOVES => 0,
USE_MEMCACHED => 0
};
##################################################################### #####################################################################
# Provide accessors for our columns # Provide accessors for our columns
##################################################################### #####################################################################
sub id { return $_[0]->{id} } sub id { return $_[0]->{id} }
sub bug_id { return $_[0]->{bug_id} } sub bug_id { return $_[0]->{bug_id} }
sub user_id { return $_[0]->{user_id} } sub user_id { return $_[0]->{user_id} }
sub last_visit_ts { return $_[0]->{last_visit_ts} } 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};
} }
1; 1;
......
...@@ -17,122 +17,125 @@ use Type::Utils; ...@@ -17,122 +17,125 @@ 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://
[*A-Za-z0-9.-]+ # hostname including wildcards. Possibly too permissive. [*A-Za-z0-9.-]+ # hostname including wildcards. Possibly too permissive.
(?: :[0-9]+ )? # optional port (?: :[0-9]+ )? # optional port
}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(
default_src child_src connect_src default_src child_src connect_src
font_src img_src media_src font_src img_src media_src
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) = @_;
my $method = 'has_' . $directive; my $method = 'has_' . $directive;
return $self->$method; return $self->$method;
} }
sub header_names { sub header_names {
my ($self) = @_; my ($self) = @_;
my @names = ('Content-Security-Policy', 'X-Content-Security-Policy', 'X-WebKit-CSP'); my @names
if ($self->report_only) { = ('Content-Security-Policy', 'X-Content-Security-Policy', 'X-WebKit-CSP');
return map { $_ . '-Report-Only' } @names; if ($self->report_only) {
} return map { $_ . '-Report-Only' } @names;
else { }
return @names; else {
} return @names;
}
} }
sub add_cgi_headers { sub add_cgi_headers {
my ($self, $headers) = @_; my ($self, $headers) = @_;
return if $self->disable; return if $self->disable;
foreach my $name ($self->header_names) { foreach my $name ($self->header_names) {
$headers->{"-$name"} = $self->value; $headers->{"-$name"} = $self->value;
} }
} }
sub _build_value { sub _build_value {
my $self = shift; my $self = shift;
my @result; my @result;
my @list_directives = (@ALL_SRC); my @list_directives = (@ALL_SRC);
my @boolean_directives = (@ALL_BOOL); my @boolean_directives = (@ALL_BOOL);
my @single_directives = qw(report_uri base_uri); my @single_directives = qw(report_uri base_uri);
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);
}
} }
}
foreach my $directive (@single_directives) { foreach my $directive (@single_directives) {
next unless $self->_has_directive($directive); next unless $self->_has_directive($directive);
my $value = $self->$directive; my $value = $self->$directive;
if (defined $value) { if (defined $value) {
push @result, _name($directive) . ' ' . $value; push @result, _name($directive) . ' ' . $value;
}
} }
}
foreach my $directive (@boolean_directives) { foreach my $directive (@boolean_directives) {
if ($self->$directive) { if ($self->$directive) {
push @result, _name($directive); push @result, _name($directive);
}
} }
}
return join('; ', @result); return join('; ', @result);
} }
sub _build_nonce { sub _build_nonce {
return generate_random_password(48); return generate_random_password(48);
} }
sub _name { sub _name {
my $name = shift; my $name = shift;
$name =~ tr/_/-/; $name =~ tr/_/-/;
return $name; return $name;
} }
sub _quote { sub _quote {
my ($self, $val) = @_; my ($self, $val) = @_;
if ($val eq 'nonce') { if ($val eq 'nonce') {
return q{'nonce-} . $self->nonce . q{'}; return q{'nonce-} . $self->nonce . q{'};
} }
elsif ($SRC_KEYWORD->check($val)) { elsif ($SRC_KEYWORD->check($val)) {
return qq{'$val'}; return qq{'$val'};
} }
else { else {
return $val; return $val;
} }
} }
1; 1;
__END__ __END__
......
...@@ -26,26 +26,26 @@ use parent qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object Exporter); ...@@ -26,26 +26,26 @@ use parent qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object Exporter);
use constant IS_CONFIG => 1; use constant IS_CONFIG => 1;
use constant DB_TABLE => 'classifications'; use constant DB_TABLE => 'classifications';
use constant LIST_ORDER => 'sortkey, name'; use constant LIST_ORDER => 'sortkey, name';
use constant DB_COLUMNS => qw( use constant DB_COLUMNS => qw(
id id
name name
description description
sortkey sortkey
); );
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
name name
description description
sortkey sortkey
); );
use constant VALIDATORS => { use constant VALIDATORS => {
name => \&_check_name, name => \&_check_name,
description => \&_check_description, description => \&_check_description,
sortkey => \&_check_sortkey, sortkey => \&_check_sortkey,
}; };
############################### ###############################
...@@ -53,29 +53,31 @@ use constant VALIDATORS => { ...@@ -53,29 +53,31 @@ use constant VALIDATORS => {
############################### ###############################
sub remove_from_db { sub remove_from_db {
my $self = shift; my $self = shift;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
ThrowUserError("classification_not_deletable") if ($self->id == 1); ThrowUserError("classification_not_deletable") if ($self->id == 1);
$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 = ?',
if (@$product_ids) { undef, $self->id);
$dbh->do('UPDATE products SET classification_id = 1 WHERE '
. $dbh->sql_in('id', $product_ids)); if (@$product_ids) {
foreach my $id (@$product_ids) { $dbh->do('UPDATE products SET classification_id = 1 WHERE '
Bugzilla->memcached->clear({ table => 'products', id => $id }); . $dbh->sql_in('id', $product_ids));
} foreach my $id (@$product_ids) {
Bugzilla->memcached->clear_config(); Bugzilla->memcached->clear({table => 'products', id => $id});
} }
Bugzilla->memcached->clear_config();
}
$self->SUPER::remove_from_db(); $self->SUPER::remove_from_db();
$dbh->bz_commit_transaction(); $dbh->bz_commit_transaction();
} }
...@@ -84,38 +86,41 @@ sub remove_from_db { ...@@ -84,38 +86,41 @@ sub remove_from_db {
############################### ###############################
sub _check_name { sub _check_name {
my ($invocant, $name) = @_; my ($invocant, $name) = @_;
$name = trim($name); $name = trim($name);
$name || ThrowUserError('classification_not_specified'); $name || ThrowUserError('classification_not_specified');
if (length($name) > MAX_CLASSIFICATION_SIZE) { if (length($name) > MAX_CLASSIFICATION_SIZE) {
ThrowUserError('classification_name_too_long', {'name' => $name}); ThrowUserError('classification_name_too_long', {'name' => $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",
return $name; {name => $classification->name});
}
return $name;
} }
sub _check_description { sub _check_description {
my ($invocant, $description) = @_; my ($invocant, $description) = @_;
$description = trim($description || ''); $description = trim($description || '');
return $description; return $description;
} }
sub _check_sortkey { sub _check_sortkey {
my ($invocant, $sortkey) = @_; my ($invocant, $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;
} }
##################################### #####################################
...@@ -124,41 +129,45 @@ sub _check_sortkey { ...@@ -124,41 +129,45 @@ sub _check_sortkey {
use constant FIELD_NAME => 'classification'; use constant FIELD_NAME => 'classification';
use constant is_default => 0; use constant is_default => 0;
use constant is_active => 1; use constant is_active => 1;
############################### ###############################
#### Methods #### #### Methods ####
############################### ###############################
sub set_name { $_[0]->set('name', $_[1]); } sub set_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); } sub set_description { $_[0]->set('description', $_[1]); }
sub set_sortkey { $_[0]->set('sortkey', $_[1]); } sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub product_count { sub product_count {
my $self = shift; my $self = shift;
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'};
} }
sub products { sub products {
my $self = shift; my $self = shift;
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);
} }
return $self->{'products'}; return $self->{'products'};
} }
############################### ###############################
...@@ -166,7 +175,7 @@ sub products { ...@@ -166,7 +175,7 @@ sub products {
############################### ###############################
sub description { return $_[0]->{'description'}; } sub description { return $_[0]->{'description'}; }
sub sortkey { return $_[0]->{'sortkey'}; } sub sortkey { return $_[0]->{'sortkey'}; }
############################### ###############################
...@@ -177,27 +186,32 @@ sub sortkey { return $_[0]->{'sortkey'}; } ...@@ -177,27 +186,32 @@ sub sortkey { return $_[0]->{'sortkey'}; }
# in global/choose-product.html.tmpl. # in global/choose-product.html.tmpl.
sub sort_products_by_classification { sub sort_products_by_classification {
my $products = shift; my $products = shift;
my $list; my $list;
if (Bugzilla->params->{'useclassification'}) { if (Bugzilla->params->{'useclassification'}) {
my $class = {}; my $class = {};
# Get all classifications with at least one product.
foreach my $product (@$products) { # Get all classifications with at least one product.
$class->{$product->classification_id}->{'object'} ||= foreach my $product (@$products) {
new Bugzilla::Classification($product->classification_id); $class->{$product->classification_id}->{'object'}
# Nice way to group products per classification, without querying ||= new Bugzilla::Classification($product->classification_id);
# the DB again.
push(@{$class->{$product->classification_id}->{'products'}}, $product); # Nice way to group products per classification, without querying
} # the DB again.
$list = [sort {$a->{'object'}->sortkey <=> $b->{'object'}->sortkey push(@{$class->{$product->classification_id}->{'products'}}, $product);
|| lc($a->{'object'}->name) cmp lc($b->{'object'}->name)}
(values %$class)];
}
else {
$list = [{object => undef, products => $products}];
} }
return $list; $list = [
sort {
$a->{'object'}->sortkey <=> $b->{'object'}->sortkey
|| lc($a->{'object'}->name) cmp lc($b->{'object'}->name)
} (values %$class)
];
}
else {
$list = [{object => undef, products => $products}];
}
return $list;
} }
1; 1;
......
...@@ -21,20 +21,20 @@ use constant AUDIT_UPDATES => 0; ...@@ -21,20 +21,20 @@ use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0; use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw( use constant DB_COLUMNS => qw(
id id
tag tag
weight weight
); );
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
weight weight
); );
use constant DB_TABLE => 'longdescs_tags_weights'; 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;
......
...@@ -16,32 +16,21 @@ use Bugzilla::Config::Common; ...@@ -16,32 +16,21 @@ use Bugzilla::Config::Common;
our $sortkey = 200; 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', {name => 'allowemailchange', type => 'b', default => 1},
default => 0
}, {name => 'allowuserdeletion', type => 'b', default => 0},
{ {
name => 'allowemailchange', name => 'last_visit_keep_days',
type => 'b', type => 't',
default => 1 default => 10,
}, checker => \&check_numeric
}
{ );
name => 'allowuserdeletion',
type => 'b',
default => 0
},
{
name => 'last_visit_keep_days',
type => 't',
default => 10,
checker => \&check_numeric
});
return @param_list; return @param_list;
} }
......
...@@ -17,36 +17,32 @@ our $sortkey = 1700; ...@@ -17,36 +17,32 @@ our $sortkey = 1700;
use constant get_param_list => ( use constant get_param_list => (
{ {
name => 'inbound_proxies', name => 'inbound_proxies',
type => 't', type => 't',
default => '', default => '',
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',
type => 's', type => 's',
choices => ['off', 'this_domain_only', 'include_subdomains'], choices => ['off', 'this_domain_only', 'include_subdomains'],
default => 'off', default => 'off',
checker => \&check_multi checker => \&check_multi
}, },
); );
sub check_inbound_proxies { sub check_inbound_proxies {
my $inbound_proxies = shift; my $inbound_proxies = shift;
return "" if $inbound_proxies eq "*"; return "" if $inbound_proxies eq "*";
my @proxies = split(/[\s,]+/, $inbound_proxies); my @proxies = split(/[\s,]+/, $inbound_proxies);
foreach my $proxy (@proxies) { foreach my $proxy (@proxies) {
validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address"; validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address";
} }
return ""; return "";
} }
1; 1;
...@@ -16,56 +16,49 @@ use Bugzilla::Config::Common; ...@@ -16,56 +16,49 @@ use Bugzilla::Config::Common;
our $sortkey = 400; 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',
type => 't', type => 't',
default => '', default => '',
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',
type => 's', type => 's',
choices => ['off', 'X-Sendfile', 'X-Accel-Redirect', 'X-LIGHTTPD-send-file'], choices => ['off', 'X-Sendfile', 'X-Accel-Redirect', 'X-LIGHTTPD-send-file'],
default => 'off', default => 'off',
checker => \&check_multi checker => \&check_multi
}, },
{ {
name => 'maxattachmentsize', name => 'maxattachmentsize',
type => 't', type => 't',
default => '1000', default => '1000',
checker => \&check_maxattachmentsize checker => \&check_maxattachmentsize
}, },
# The maximum size (in bytes) for patches and non-patch attachments. # The maximum size (in bytes) for patches and non-patch attachments.
# The default limit is 1000KB, which is 24KB less than mysql's default # The default limit is 1000KB, which is 24KB less than mysql's default
# maximum packet size (which determines how much data can be sent in a # maximum packet size (which determines how much data can be sent in a
# single mysql packet and thus how much data can be inserted into the # single mysql packet and thus how much data can be inserted into the
# database) to provide breathing space for the data in other fields of # database) to provide breathing space for the data in other fields of
# the attachment record as well as any mysql packet overhead (I don't # the attachment record as well as any mysql packet overhead (I don't
# know of any, but I suspect there may be some.) # know of any, but I suspect there may be some.)
{ {
name => 'maxlocalattachment', name => 'maxlocalattachment',
type => 't', type => 't',
default => '0', default => '0',
checker => \&check_numeric checker => \&check_numeric
} ); }
);
return @param_list; return @param_list;
} }
......
...@@ -16,111 +16,89 @@ use Bugzilla::Config::Common; ...@@ -16,111 +16,89 @@ use Bugzilla::Config::Common;
our $sortkey = 300; 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', {name => 'auth_env_email', type => 't', default => '',},
default => '',
}, {name => 'auth_env_realname', type => 't', default => '',},
{ # XXX in the future:
name => 'auth_env_email', #
type => 't', # user_verify_class and user_info_class should have choices gathered from
default => '', # whatever sits in their respective directories
}, #
# rather than comma-separated lists, these two should eventually become
{ # arrays, but that requires alterations to editparams first
name => 'auth_env_realname',
type => 't', {
default => '', name => 'user_info_class',
}, type => 's',
choices => ['CGI', 'Env', 'Env,CGI'],
# XXX in the future: default => 'CGI',
# checker => \&check_multi
# user_verify_class and user_info_class should have choices gathered from },
# whatever sits in their respective directories
# {
# rather than comma-separated lists, these two should eventually become name => 'user_verify_class',
# arrays, but that requires alterations to editparams first type => 'o',
choices => ['DB', 'RADIUS', 'LDAP'],
{ default => 'DB',
name => 'user_info_class', checker => \&check_user_verify_class
type => 's', },
choices => [ 'CGI', 'Env', 'Env,CGI' ],
default => 'CGI', {
checker => \&check_multi name => 'rememberlogin',
}, type => 's',
choices => ['on', 'defaulton', 'defaultoff', 'off'],
{ default => 'on',
name => 'user_verify_class', checker => \&check_multi
type => 'o', },
choices => [ 'DB', 'RADIUS', 'LDAP' ],
default => 'DB', {name => 'requirelogin', type => 'b', default => '0'},
checker => \&check_user_verify_class
}, {
name => 'emailregexp',
{ type => 't',
name => 'rememberlogin', default => q:^[\\w\\.\\+\\-=']+@[\\w\\.\\-]+\\.[\\w\\-]+$:,
type => 's', checker => \&check_regexp
choices => ['on', 'defaulton', 'defaultoff', 'off'], },
default => 'on',
checker => \&check_multi {
}, name => 'emailregexpdesc',
type => 'l',
{ default => 'A legal address must contain exactly one \'@\', and at least '
name => 'requirelogin', . 'one \'.\' after the @.'
type => 'b', },
default => '0'
}, {
name => 'use_email_as_login',
{ type => 'b',
name => 'emailregexp', default => '1',
type => 't', onchange => \&change_use_email_as_login
default => q:^[\\w\\.\\+\\-=']+@[\\w\\.\\-]+\\.[\\w\\-]+$:, },
checker => \&check_regexp
}, {
name => 'createemailregexp',
{ type => 't',
name => 'emailregexpdesc', default => q:.*:,
type => 'l', checker => \&check_regexp
default => 'A legal address must contain exactly one \'@\', and at least ' . },
'one \'.\' after the @.'
}, {
name => 'password_complexity',
{ type => 's',
name => 'use_email_as_login', choices => [
type => 'b', 'no_constraints', 'mixed_letters',
default => '1', 'letters_numbers', 'letters_numbers_specialchars'
onchange => \&change_use_email_as_login ],
}, default => 'no_constraints',
checker => \&check_multi
{ },
name => 'createemailregexp',
type => 't', {name => 'password_check_on_login', type => 'b', default => '1'},
default => q:.*:, {name => 'auth_delegation', type => 'b', default => 0,},
checker => \&check_regexp
},
{
name => 'password_complexity',
type => 's',
choices => [ 'no_constraints', 'mixed_letters', 'letters_numbers',
'letters_numbers_specialchars' ],
default => 'no_constraints',
checker => \&check_multi
},
{
name => 'password_check_on_login',
type => 'b',
default => '1'
},
{
name => 'auth_delegation',
type => 'b',
default => 0,
},
); );
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.
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