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 strict;
use warnings;
package CPAN::Meta;
our $VERSION = '2.150005';
......@@ -92,7 +93,7 @@ BEGIN {
no strict 'refs';
for my $attr (@STRING_READERS) {
*$attr = sub { $_[0]{ $attr } };
*$attr = sub { $_[0]{$attr} };
}
}
......@@ -121,10 +122,9 @@ BEGIN {
no strict 'refs';
for my $attr (@LIST_READERS) {
*$attr = sub {
my $value = $_[0]{ $attr };
croak "$attr must be called in list context"
unless wantarray;
return @{ _dclone($value) } if ref $value;
my $value = $_[0]{$attr};
croak "$attr must be called in list context" unless wantarray;
return @{_dclone($value)} if ref $value;
return $value;
};
}
......@@ -163,7 +163,7 @@ BEGIN {
for my $attr (@MAP_READERS) {
(my $subname = $attr) =~ s/-/_/;
*$subname = sub {
my $value = $_[0]{ $attr };
my $value = $_[0]{$attr};
return _dclone($value) if $value;
return {};
};
......@@ -182,7 +182,7 @@ BEGIN {
#pod =cut
sub custom_keys {
return grep { /^x_/i } keys %{$_[0]};
return grep {/^x_/i} keys %{$_[0]};
}
sub custom {
......@@ -220,29 +220,29 @@ sub _new {
my ($class, $struct, $options) = @_;
my $self;
if ( $options->{lazy_validation} ) {
if ($options->{lazy_validation}) {
# try to convert to a valid structure; if succeeds, then return it
my $cmc = CPAN::Meta::Converter->new( $struct );
$self = $cmc->convert( version => 2 ); # valid or dies
my $cmc = CPAN::Meta::Converter->new($struct);
$self = $cmc->convert(version => 2); # valid or dies
return bless $self, $class;
}
else {
# validate original struct
my $cmv = CPAN::Meta::Validator->new( $struct );
unless ( $cmv->is_valid) {
die "Invalid metadata structure. Errors: "
. join(", ", $cmv->errors) . "\n";
my $cmv = CPAN::Meta::Validator->new($struct);
unless ($cmv->is_valid) {
die "Invalid metadata structure. Errors: " . join(", ", $cmv->errors) . "\n";
}
}
# up-convert older spec versions
my $version = $struct->{'meta-spec'}{version} || '1.0';
if ( $version == 2 ) {
if ($version == 2) {
$self = $struct;
}
else {
my $cmc = CPAN::Meta::Converter->new( $struct );
$self = $cmc->convert( version => 2 );
my $cmc = CPAN::Meta::Converter->new($struct);
$self = $cmc->convert(version => 2);
}
return bless $self, $class;
......@@ -268,10 +268,10 @@ sub new {
sub create {
my ($class, $struct, $options) = @_;
my $version = __PACKAGE__->VERSION || 2;
$struct->{generated_by} ||= __PACKAGE__ . " version $version" ;
$struct->{generated_by} ||= __PACKAGE__ . " version $version";
$struct->{'meta-spec'}{version} ||= int($version);
my $self = eval { $class->_new($struct, $options) };
croak ($@) if $@;
croak($@) if $@;
return $self;
}
......@@ -293,12 +293,11 @@ sub load_file {
my ($class, $file, $options) = @_;
$options->{lazy_validation} = 1 unless exists $options->{lazy_validation};
croak "load_file() requires a valid, readable filename"
unless -r $file;
croak "load_file() requires a valid, readable filename" unless -r $file;
my $self;
eval {
my $struct = Parse::CPAN::Meta->load_file( $file );
my $struct = Parse::CPAN::Meta->load_file($file);
$self = $class->_new($struct, $options);
};
croak($@) if $@;
......@@ -320,7 +319,7 @@ sub load_yaml_string {
my $self;
eval {
my ($struct) = Parse::CPAN::Meta->load_yaml_string( $yaml );
my ($struct) = Parse::CPAN::Meta->load_yaml_string($yaml);
$self = $class->_new($struct, $options);
};
croak($@) if $@;
......@@ -342,7 +341,7 @@ sub load_json_string {
my $self;
eval {
my $struct = Parse::CPAN::Meta->load_json_string( $json );
my $struct = Parse::CPAN::Meta->load_json_string($json);
$self = $class->_new($struct, $options);
};
croak($@) if $@;
......@@ -365,7 +364,7 @@ sub load_string {
my $self;
eval {
my $struct = Parse::CPAN::Meta->load_string( $string );
my $struct = Parse::CPAN::Meta->load_string($string);
$self = $class->_new($struct, $options);
};
croak($@) if $@;
......@@ -400,22 +399,18 @@ sub save {
my $version = $options->{version} || '2';
my $layer = $] ge '5.008001' ? ':utf8' : '';
if ( $version ge '2' ) {
carp "'$file' should end in '.json'"
unless $file =~ m{\.json$};
if ($version ge '2') {
carp "'$file' should end in '.json'" unless $file =~ m{\.json$};
}
else {
carp "'$file' should end in '.yml'"
unless $file =~ m{\.yml$};
carp "'$file' should end in '.yml'" unless $file =~ m{\.yml$};
}
my $data = $self->as_string( $options );
open my $fh, ">$layer", $file
or die "Error opening '$file' for writing: $!\n";
my $data = $self->as_string($options);
open my $fh, ">$layer", $file or die "Error opening '$file' for writing: $!\n";
print {$fh} $data;
close $fh
or die "Error closing '$file': $!\n";
close $fh or die "Error closing '$file': $!\n";
return 1;
}
......@@ -455,7 +450,7 @@ sub effective_prereqs {
return $prereq unless @$features;
my @other = map {; $self->feature($_)->prereqs } @$features;
my @other = map { ; $self->feature($_)->prereqs } @$features;
return $prereq->with_merged_prereqs(\@other);
}
......@@ -476,11 +471,11 @@ sub effective_prereqs {
sub should_index_file {
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;
}
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};
return if index($filename, $no_index_dir) == 0;
}
......@@ -502,11 +497,11 @@ sub should_index_file {
sub should_index_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;
}
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;
}
......@@ -526,8 +521,8 @@ sub features {
my ($self) = @_;
my $opt_f = $self->optional_features;
my @features = map {; CPAN::Meta::Feature->new($_ => $opt_f->{ $_ }) }
keys %$opt_f;
my @features
= map { ; CPAN::Meta::Feature->new($_ => $opt_f->{$_}) } keys %$opt_f;
return @features;
}
......@@ -546,7 +541,7 @@ sub feature {
my ($self, $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);
}
......@@ -567,9 +562,9 @@ sub feature {
sub as_struct {
my ($self, $options) = @_;
my $struct = _dclone($self);
if ( $options->{version} ) {
my $cmc = CPAN::Meta::Converter->new( $struct );
$struct = $cmc->convert( version => $options->{version} );
if ($options->{version}) {
my $cmc = CPAN::Meta::Converter->new($struct);
$struct = $cmc->convert(version => $options->{version});
}
return $struct;
}
......@@ -603,28 +598,28 @@ sub as_string {
my $version = $options->{version} || '2';
my $struct;
if ( $self->meta_spec_version ne $version ) {
my $cmc = CPAN::Meta::Converter->new( $self->as_struct );
$struct = $cmc->convert( version => $version );
if ($self->meta_spec_version ne $version) {
my $cmc = CPAN::Meta::Converter->new($self->as_struct);
$struct = $cmc->convert(version => $version);
}
else {
$struct = $self->as_struct;
}
my ($data, $backend);
if ( $version ge '2' ) {
if ($version ge '2') {
$backend = Parse::CPAN::Meta->json_backend();
local $struct->{x_serialization_backend} = sprintf '%s version %s',
$backend, $backend->VERSION;
local $struct->{x_serialization_backend} = sprintf '%s version %s', $backend,
$backend->VERSION;
$data = $backend->new->pretty->canonical->encode($struct);
}
else {
$backend = Parse::CPAN::Meta->yaml_backend();
local $struct->{x_serialization_backend} = sprintf '%s version %s',
$backend, $backend->VERSION;
local $struct->{x_serialization_backend} = sprintf '%s version %s', $backend,
$backend->VERSION;
$data = eval { no strict 'refs'; &{"$backend\::Dump"}($struct) };
if ( $@ ) {
croak $backend->can('errstr') ? $backend->errstr : $@
if ($@) {
croak $backend->can('errstr') ? $backend->errstr : $@;
}
}
......@@ -633,7 +628,7 @@ sub as_string {
# Used by JSON::PP, etc. for "convert_blessed"
sub TO_JSON {
return { %{ $_[0] } };
return {%{$_[0]}};
}
1;
......
use 5.006;
use strict;
use warnings;
package CPAN::Meta::Feature;
our $VERSION = '2.150005';
......@@ -44,7 +45,7 @@ sub new {
#pod
#pod =cut
sub identifier { $_[0]{identifier} }
sub identifier { $_[0]{identifier} }
#pod =method description
#pod
......@@ -61,7 +62,7 @@ sub description { $_[0]{description} }
#pod
#pod =cut
sub prereqs { $_[0]{prereqs} }
sub prereqs { $_[0]{prereqs} }
1;
......
......@@ -2,6 +2,7 @@
use 5.006;
use strict;
use warnings;
package CPAN::Meta::History;
our $VERSION = '2.150005';
......
......@@ -11,15 +11,16 @@ use CPAN::Meta::Converter 2.141170;
sub _is_identical {
my ($left, $right) = @_;
return
(not defined $left and not defined $right)
return (not defined $left and not defined $right)
# if either of these are references, we compare the serialized value
|| (defined $left and defined $right and $left eq $right);
}
sub _identical {
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);
return $left;
}
......@@ -31,10 +32,10 @@ sub _merge {
$current->{$key} = $next->{$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'}) {
$current->{$key} = $merger->($current->{$key}, $next->{$key}, [ @{$path}, $key ]);
$current->{$key} = $merger->($current->{$key}, $next->{$key}, [@{$path}, $key]);
}
else {
croak sprintf "Can't merge unknown attribute '%s'", join '.', @{$path}, $key;
......@@ -50,7 +51,7 @@ sub _uniq {
sub _set_addition {
my ($left, $right) = @_;
return [ +_uniq(@{$left}, @{$right}) ];
return [+_uniq(@{$left}, @{$right})];
}
sub _uniq_map {
......@@ -59,12 +60,13 @@ sub _uniq_map {
if (not exists $left->{$key}) {
$left->{$key} = $right->{$key};
}
# identical strings or references are merged identically
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') {
$left->{$key} = _uniq_map($left->{$key}, $right->{$key}, [ @{$path}, $key ]);
$left->{$key} = _uniq_map($left->{$key}, $right->{$key}, [@{$path}, $key]);
}
else {
croak 'Duplication of element ' . join '.', @{$path}, $key;
......@@ -98,22 +100,26 @@ sub _optional_features {
$left->{$key} = $right->{$key};
}
else {
for my $subkey (keys %{ $right->{$key} }) {
for my $subkey (keys %{$right->{$key}}) {
next if $subkey eq 'prereqs';
if (not exists $left->{$key}{$subkey}) {
$left->{$key}{$subkey} = $right->{$key}{$subkey};
}
else {
Carp::croak "Cannot merge two optional_features named '$key' with different '$subkey' values"
if do { no warnings 'uninitialized'; $left->{$key}{$subkey} ne $right->{$key}{$subkey} };
Carp::croak
"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;
$left->{$key}{prereqs} =
CPAN::Meta::Prereqs->new($left->{$key}{prereqs})
->with_merged_prereqs(CPAN::Meta::Prereqs->new($right->{$key}{prereqs}))
->as_string_hash;
$left->{$key}{prereqs}
= CPAN::Meta::Prereqs->new($left->{$key}{prereqs})
->with_merged_prereqs(CPAN::Meta::Prereqs->new($right->{$key}{prereqs}))
->as_string_hash;
}
}
return $left;
......@@ -131,21 +137,19 @@ my %default = (
my ($left, $right) = @_;
return join ', ', _uniq(split(/, /, $left), split(/, /, $right));
},
license => \&_set_addition,
'meta-spec' => {
version => \&_identical,
url => \&_identical
},
name => \&_identical,
release_status => \&_identical,
version => \&_identical,
description => \&_identical,
keywords => \&_set_addition,
no_index => { map { ($_ => \&_set_addition) } qw/file directory package namespace/ },
license => \&_set_addition,
'meta-spec' => {version => \&_identical, url => \&_identical},
name => \&_identical,
release_status => \&_identical,
version => \&_identical,
description => \&_identical,
keywords => \&_set_addition,
no_index =>
{map { ($_ => \&_set_addition) } qw/file directory package namespace/},
optional_features => \&_optional_features,
prereqs => sub {
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;
},
provides => \&_uniq_map,
......@@ -163,10 +167,10 @@ sub new {
my ($class, %arguments) = @_;
croak 'default version required' if not exists $arguments{default_version};
my %mapping = %default;
my %extra = %{ $arguments{extra_mappings} || {} };
my %extra = %{$arguments{extra_mappings} || {}};
for my $key (keys %extra) {
if (ref($mapping{$key}) eq 'HASH') {
$mapping{$key} = { %{ $mapping{$key} }, %{ $extra{$key} } };
$mapping{$key} = {%{$mapping{$key}}, %{$extra{$key}}};
}
else {
$mapping{$key} = $extra{$key};
......@@ -174,7 +178,7 @@ sub new {
}
return bless {
default_version => $arguments{default_version},
mapping => _coerce_mapping(\%mapping, []),
mapping => _coerce_mapping(\%mapping, []),
}, $class;
}
......@@ -194,10 +198,10 @@ sub _coerce_mapping {
$ret{$key} = $value;
}
elsif (ref($value) eq 'HASH') {
my $mapping = _coerce_mapping($value, [ @{$map_path}, $key ]);
my $mapping = _coerce_mapping($value, [@{$map_path}, $key]);
$ret{$key} = sub {
my ($left, $right, $path) = @_;
return _merge($left, $right, $mapping, [ @{$path} ]);
return _merge($left, $right, $mapping, [@{$path}]);
};
}
elsif ($coderef_for{$value}) {
......@@ -214,13 +218,12 @@ sub merge {
my ($self, @items) = @_;
my $current = {};
for my $next (@items) {
if ( blessed($next) && $next->isa('CPAN::Meta') ) {
if (blessed($next) && $next->isa('CPAN::Meta')) {
$next = $next->as_struct;
}
elsif ( ref($next) eq 'HASH' ) {
my $cmc = CPAN::Meta::Converter->new(
$next, default_version => $self->{default_version}
);
elsif (ref($next) eq 'HASH') {
my $cmc = CPAN::Meta::Converter->new($next,
default_version => $self->{default_version});
$next = $cmc->upgrade_fragment;
}
else {
......
use 5.006;
use strict;
use warnings;
package CPAN::Meta::Prereqs;
our $VERSION = '2.150005';
......@@ -45,34 +46,33 @@ use CPAN::Meta::Requirements 2.121;
#pod
#pod =cut
sub __legal_phases { qw(configure build test runtime develop) }
sub __legal_types { qw(requires recommends suggests conflicts) }
sub __legal_phases {qw(configure build test runtime develop)}
sub __legal_types {qw(requires recommends suggests conflicts)}
# expect a prereq spec from META.json -- rjbs, 2010-04-11
sub new {
my ($class, $prereq_spec) = @_;
$prereq_spec ||= {};
my %is_legal_phase = map {; $_ => 1 } $class->__legal_phases;
my %is_legal_type = map {; $_ => 1 } $class->__legal_types;
my %is_legal_phase = map { ; $_ => 1 } $class->__legal_phases;
my %is_legal_type = map { ; $_ => 1 } $class->__legal_types;
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};
my $phase_spec = $prereq_spec->{ $phase };
my $phase_spec = $prereq_spec->{$phase};
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};
my $spec = $phase_spec->{ $type };
my $spec = $phase_spec->{$type};
next TYPE unless keys %$spec;
$guts{prereqs}{$phase}{$type} = CPAN::Meta::Requirements->from_string_hash(
$spec
);
$guts{prereqs}{$phase}{$type}
= CPAN::Meta::Requirements->from_string_hash($spec);
}
}
......@@ -152,7 +152,7 @@ sub with_merged_prereqs {
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 {
sub merged_requirements {
my ($self, $phases, $types) = @_;
$phases = [qw/runtime build test/] unless defined $phases;
$types = [qw/requires recommends/] unless defined $types;
$phases = [qw/runtime build test/] unless defined $phases;
$types = [qw/requires recommends/] unless defined $types;
confess "merged_requirements phases argument must be an arrayref"
unless ref $phases eq 'ARRAY';
......@@ -184,15 +184,15 @@ sub merged_requirements {
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) {
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) {
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 {
my $req = $self->requirements_for($phase, $type);
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 {
$self->{finalized} = 1;
for my $phase (keys %{ $self->{prereqs} }) {
$_->finalize for values %{ $self->{prereqs}{$phase} };
for my $phase (keys %{$self->{prereqs}}) {
$_->finalize for values %{$self->{prereqs}{$phase}};
}
}
......@@ -268,7 +268,7 @@ sub finalize {
sub clone {
my ($self) = @_;
my $clone = (ref $self)->new( $self->as_string_hash );
my $clone = (ref $self)->new($self->as_string_hash);
}
1;
......
......@@ -6,6 +6,7 @@
use 5.006;
use strict;
use warnings;
package CPAN::Meta::Spec;
our $VERSION = '2.150005';
......
=head1 NAME
JSON::PP::Boolean - dummy module providing JSON::PP::Boolean
......
use 5.008001;
use strict;
package Parse::CPAN::Meta;
# ABSTRACT: Parse META.yml and META.json CPAN metadata files
our $VERSION = '1.4417';
......@@ -8,7 +10,7 @@ our $VERSION = '1.4417';
use Exporter;
use Carp 'croak';
our @ISA = qw/Exporter/;
our @ISA = qw/Exporter/;
our @EXPORT_OK = qw/Load LoadFile/;
sub load_file {
......@@ -23,19 +25,19 @@ sub load_file {
return $class->load_json_string($meta);
}
else {
$class->load_string($meta); # try to detect yaml/json
$class->load_string($meta); # try to detect yaml/json
}
}
sub load_string {
my ($class, $string) = @_;
if ( $string =~ /^---/ ) { # looks like YAML
if ($string =~ /^---/) { # looks like YAML
return $class->load_yaml_string($string);
}
elsif ( $string =~ /^\s*\{/ ) { # looks like JSON
elsif ($string =~ /^\s*\{/) { # looks like JSON
return $class->load_json_string($string);
}
else { # maybe doc-marker-free YAML
else { # maybe doc-marker-free YAML
return $class->load_yaml_string($string);
}
}
......@@ -45,7 +47,7 @@ sub load_yaml_string {
my $backend = $class->yaml_backend();
my $data = eval { no strict 'refs'; &{"$backend\::Load"}($string) };
croak $@ if $@;
return $data || {}; # in case document was valid but empty
return $data || {}; # in case document was valid but empty
}
sub load_json_string {
......@@ -56,15 +58,14 @@ sub load_json_string {
}
sub yaml_backend {
if (! defined $ENV{PERL_YAML_BACKEND} ) {
_can_load( 'CPAN::Meta::YAML', 0.011 )
if (!defined $ENV{PERL_YAML_BACKEND}) {
_can_load('CPAN::Meta::YAML', 0.011)
or croak "CPAN::Meta::YAML 0.011 is not available\n";
return "CPAN::Meta::YAML";
}
else {
my $backend = $ENV{PERL_YAML_BACKEND};
_can_load( $backend )
or croak "Could not load PERL_YAML_BACKEND '$backend'\n";
_can_load($backend) or croak "Could not load PERL_YAML_BACKEND '$backend'\n";
$backend->can("Load")
or croak "PERL_YAML_BACKEND '$backend' does not implement Load()\n";
return $backend;
......@@ -72,51 +73,48 @@ sub yaml_backend {
}
sub json_backend {
if (! $ENV{PERL_JSON_BACKEND} or $ENV{PERL_JSON_BACKEND} eq 'JSON::PP') {
_can_load( 'JSON::PP' => 2.27103 )
or croak "JSON::PP 2.27103 is not available\n";
if (!$ENV{PERL_JSON_BACKEND} or $ENV{PERL_JSON_BACKEND} eq 'JSON::PP') {
_can_load('JSON::PP' => 2.27103) or croak "JSON::PP 2.27103 is not available\n";
return 'JSON::PP';
}
else {
_can_load( 'JSON' => 2.5 )
or croak "JSON 2.5 is required for " .
"\$ENV{PERL_JSON_BACKEND} = '$ENV{PERL_JSON_BACKEND}'\n";
_can_load('JSON' => 2.5)
or croak "JSON 2.5 is required for "
. "\$ENV{PERL_JSON_BACKEND} = '$ENV{PERL_JSON_BACKEND}'\n";
return "JSON";
}
}
sub _slurp {
require Encode;
open my $fh, "<:raw", "$_[0]" ## no critic
open my $fh, "<:raw", "$_[0]" ## no critic
or die "can't open $_[0] for reading: $!";
my $content = do { local $/; <$fh> };
$content = Encode::decode('UTF-8', $content, Encode::PERLQQ());
return $content;
}
sub _can_load {
my ($module, $version) = @_;
(my $file = $module) =~ s{::}{/}g;
$file .= ".pm";
return 1 if $INC{$file};
return 0 if exists $INC{$file}; # prior load failed
eval { require $file; 1 }
or return 0;
if ( defined $version ) {
eval { $module->VERSION($version); 1 }
or return 0;
return 0 if exists $INC{$file}; # prior load failed
eval { require $file; 1 } or return 0;
if (defined $version) {
eval { $module->VERSION($version); 1 } or return 0;
}
return 1;
}
# Kept for backwards compatibility only
# Create an object from a file
sub LoadFile ($) { ## no critic
sub LoadFile ($) { ## no critic
return Load(_slurp(shift));
}
# Parse a document from a string.
sub Load ($) { ## no critic
sub Load ($) { ## no critic
require CPAN::Meta::YAML;
my $object = eval { CPAN::Meta::YAML::Load(shift) };
croak $@ if $@;
......
......@@ -27,7 +27,7 @@ use constant DATE_FIELDS => {};
use constant BASE64_FIELDS => {};
# 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.
# Methods that can modify data MUST not be listed here.
......@@ -46,8 +46,8 @@ use constant REST_RESOURCES => [];
##################
sub login_exempt {
my ($class, $method) = @_;
return $class->LOGIN_EXEMPT->{$method};
my ($class, $method) = @_;
return $class->LOGIN_EXEMPT->{$method};
}
1;
......
......@@ -26,33 +26,34 @@ extends 'Bugzilla::API::1_0::Resource';
##############
use constant READ_ONLY => qw(
get
get
);
use constant PUBLIC_METHODS => qw(
get
update
get
update
);
sub REST_RESOURCES {
use re '/a';
return [
# bug-id
qr{^/bug_user_last_visit/(\d+)$}, {
GET => {
method => 'get',
params => sub {
return { ids => [$_[0]] };
},
},
POST => {
method => 'update',
params => sub {
return { ids => [$_[0]] };
},
},
use re '/a';
return [
# bug-id
qr{^/bug_user_last_visit/(\d+)$},
{
GET => {
method => 'get',
params => sub {
return {ids => [$_[0]]};
},
];
},
POST => {
method => 'update',
params => sub {
return {ids => [$_[0]]};
},
},
},
];
}
############
......@@ -60,75 +61,75 @@ sub REST_RESOURCES {
############
sub update {
my ($self, $params) = validate(@_, 'ids');
my $user = Bugzilla->user;
my $dbh = Bugzilla->dbh;
my ($self, $params) = validate(@_, 'ids');
my $user = Bugzilla->user;
my $dbh = Bugzilla->dbh;
$user->login(LOGIN_REQUIRED);
$user->login(LOGIN_REQUIRED);
my $ids = $params->{ids} // [];
ThrowCodeError('param_required', { param => 'ids' }) unless @$ids;
my $ids = $params->{ids} // [];
ThrowCodeError('param_required', {param => 'ids'}) unless @$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 skip
# aliases.
$user->visible_bugs([grep /^[0-9]+$/, @$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 skip
# aliases.
$user->visible_bugs([grep /^[0-9]+$/, @$ids]);
$dbh->bz_start_transaction();
my @results;
my $last_visit_ts = $dbh->selectrow_array('SELECT NOW()');
foreach my $bug_id (@$ids) {
my $bug = Bugzilla::Bug->check({ id => $bug_id, cache => 1 });
$dbh->bz_start_transaction();
my @results;
my $last_visit_ts = $dbh->selectrow_array('SELECT NOW()');
foreach my $bug_id (@$ids) {
my $bug = Bugzilla::Bug->check({id => $bug_id, cache => 1});
ThrowUserError('user_not_involved', { bug_id => $bug->id })
unless $user->is_involved_in_bug($bug);
ThrowUserError('user_not_involved', {bug_id => $bug->id})
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(
@results,
_bug_user_last_visit_to_hash(
$bug->bug_id, $last_visit_ts, $params
));
}
$dbh->bz_commit_transaction();
push(@results,
_bug_user_last_visit_to_hash($bug->bug_id, $last_visit_ts, $params));
}
$dbh->bz_commit_transaction();
return \@results;
return \@results;
}
sub get {
my ($self, $params) = validate(@_, 'ids');
my $user = Bugzilla->user;
my $ids = $params->{ids};
$user->login(LOGIN_REQUIRED);
my @last_visits;
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
# 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;
}
else {
@last_visits = map {
_bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, $params)
} @{ $user->last_visited };
}
return \@last_visits;
my ($self, $params) = validate(@_, 'ids');
my $user = Bugzilla->user;
my $ids = $params->{ids};
$user->login(LOGIN_REQUIRED);
my @last_visits;
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
# 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;
}
else {
@last_visits
= map { _bug_user_last_visit_to_hash($_->bug_id, $_->last_visit_ts, $params) }
@{$user->last_visited};
}
return \@last_visits;
}
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),
last_visit_ts => as_datetime($last_visit_ts));
my %result
= (id => as_int($bug_id), last_visit_ts => as_datetime($last_visit_ts));
return filter($params, \%result);
return filter($params, \%result);
}
1;
......
......@@ -26,104 +26,76 @@ extends 'Bugzilla::API::1_0::Resource';
##############
# Basic info that is needed before logins
use constant LOGIN_EXEMPT => {
parameters => 1,
timezone => 1,
version => 1,
};
use constant LOGIN_EXEMPT => {parameters => 1, timezone => 1, version => 1,};
use constant READ_ONLY => qw(
extensions
last_audit_time
parameters
timezone
time
version
extensions
last_audit_time
parameters
timezone
time
version
);
use constant PUBLIC_METHODS => qw(
extensions
last_audit_time
parameters
time
timezone
version
extensions
last_audit_time
parameters
time
timezone
version
);
# Logged-out users do not need to know more than that.
use constant PARAMETERS_LOGGED_OUT => qw(
maintainer
requirelogin
maintainer
requirelogin
);
# These parameters are guessable from the web UI when the user
# is logged in. So it's safe to access them.
use constant PARAMETERS_LOGGED_IN => qw(
allowemailchange
attachment_base
commentonchange_resolution
commentonduplicate
defaultopsys
defaultplatform
defaultpriority
defaultseverity
duplicate_or_move_bug_status
emailregexpdesc
letsubmitterchoosemilestone
letsubmitterchoosepriority
mailfrom
maintainer
maxattachmentsize
maxlocalattachment
noresolveonopenblockers
password_complexity
rememberlogin
requirelogin
search_allow_no_criteria
urlbase
use_email_as_login
use_see_also
useclassification
usemenuforusers
useqacontact
usestatuswhiteboard
usetargetmilestone
allowemailchange
attachment_base
commentonchange_resolution
commentonduplicate
defaultopsys
defaultplatform
defaultpriority
defaultseverity
duplicate_or_move_bug_status
emailregexpdesc
letsubmitterchoosemilestone
letsubmitterchoosepriority
mailfrom
maintainer
maxattachmentsize
maxlocalattachment
noresolveonopenblockers
password_complexity
rememberlogin
requirelogin
search_allow_no_criteria
urlbase
use_email_as_login
use_see_also
useclassification
usemenuforusers
useqacontact
usestatuswhiteboard
usetargetmilestone
);
sub REST_RESOURCES {
my $rest_resources = [
qr{^/version$}, {
GET => {
method => 'version'
}
},
qr{^/extensions$}, {
GET => {
method => 'extensions'
}
},
qr{^/timezone$}, {
GET => {
method => 'timezone'
}
},
qr{^/time$}, {
GET => {
method => 'time'
}
},
qr{^/last_audit_time$}, {
GET => {
method => 'last_audit_time'
}
},
qr{^/parameters$}, {
GET => {
method => 'parameters'
}
}
];
return $rest_resources;
my $rest_resources = [
qr{^/version$}, {GET => {method => 'version'}},
qr{^/extensions$}, {GET => {method => 'extensions'}},
qr{^/timezone$}, {GET => {method => 'timezone'}},
qr{^/time$}, {GET => {method => 'time'}},
qr{^/last_audit_time$}, {GET => {method => 'last_audit_time'}},
qr{^/parameters$}, {GET => {method => 'parameters'}}
];
return $rest_resources;
}
############
......@@ -131,88 +103,86 @@ sub REST_RESOURCES {
############
sub version {
my $self = shift;
return { version => as_string(BUGZILLA_VERSION) };
my $self = shift;
return {version => as_string(BUGZILLA_VERSION)};
}
sub extensions {
my $self = shift;
my %retval;
foreach my $extension (@{ Bugzilla->extensions }) {
my $version = $extension->VERSION || 0;
my $name = $extension->NAME;
$retval{$name}->{version} = as_string($version);
}
return { extensions => \%retval };
my $self = shift;
my %retval;
foreach my $extension (@{Bugzilla->extensions}) {
my $version = $extension->VERSION || 0;
my $name = $extension->NAME;
$retval{$name}->{version} = as_string($version);
}
return {extensions => \%retval};
}
sub timezone {
my $self = shift;
# All Webservices return times in UTC; Use UTC here for backwards compat.
return { timezone => as_string("+0000") };
my $self = shift;
# All Webservices return times in UTC; Use UTC here for backwards compat.
return {timezone => as_string("+0000")};
}
sub time {
my ($self) = @_;
# All Webservices return times in UTC; Use UTC here for backwards compat.
# 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 $now_utc = DateTime->now();
return {
db_time => as_datetime($db_time),
web_time => as_datetime($now_utc),
};
my ($self) = @_;
# All Webservices return times in UTC; Use UTC here for backwards compat.
# 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 $now_utc = DateTime->now();
return {db_time => as_datetime($db_time), web_time => as_datetime($now_utc),};
}
sub last_audit_time {
my ($self, $params) = validate(@_, 'class');
my $dbh = Bugzilla->dbh;
my $sql_statement = "SELECT MAX(at_time) FROM audit_log";
my $class_values = $params->{class};
my @class_values_quoted;
foreach my $class_value (@$class_values) {
push (@class_values_quoted, $dbh->quote($class_value))
if $class_value =~ /^Bugzilla(::[a-zA-Z0-9_]+)*$/;
}
if (@class_values_quoted) {
$sql_statement .= " WHERE " . $dbh->sql_in('class', \@class_values_quoted);
}
my $last_audit_time = $dbh->selectrow_array("$sql_statement");
# All Webservices return times in UTC; Use UTC here for backwards compat.
# Hardcode values where appropriate
$last_audit_time = datetime_from($last_audit_time, 'UTC');
return {
last_audit_time => as_datetime($last_audit_time)
};
my ($self, $params) = validate(@_, 'class');
my $dbh = Bugzilla->dbh;
my $sql_statement = "SELECT MAX(at_time) FROM audit_log";
my $class_values = $params->{class};
my @class_values_quoted;
foreach my $class_value (@$class_values) {
push(@class_values_quoted, $dbh->quote($class_value))
if $class_value =~ /^Bugzilla(::[a-zA-Z0-9_]+)*$/;
}
if (@class_values_quoted) {
$sql_statement .= " WHERE " . $dbh->sql_in('class', \@class_values_quoted);
}
my $last_audit_time = $dbh->selectrow_array("$sql_statement");
# All Webservices return times in UTC; Use UTC here for backwards compat.
# Hardcode values where appropriate
$last_audit_time = datetime_from($last_audit_time, 'UTC');
return {last_audit_time => as_datetime($last_audit_time)};
}
sub parameters {
my ($self, $args) = @_;
my $user = Bugzilla->login(LOGIN_OPTIONAL);
my $params = Bugzilla->params;
$args ||= {};
my @params_list = $user->in_group('tweakparams')
? keys(%$params)
: $user->id ? PARAMETERS_LOGGED_IN : PARAMETERS_LOGGED_OUT;
my %parameters;
foreach my $param (@params_list) {
next unless filter_wants($args, $param);
$parameters{$param} = as_string($params->{$param});
}
return { parameters => \%parameters };
my ($self, $args) = @_;
my $user = Bugzilla->login(LOGIN_OPTIONAL);
my $params = Bugzilla->params;
$args ||= {};
my @params_list
= $user->in_group('tweakparams') ? keys(%$params)
: $user->id ? PARAMETERS_LOGGED_IN
: PARAMETERS_LOGGED_OUT;
my %parameters;
foreach my $param (@params_list) {
next unless filter_wants($args, $param);
$parameters{$param} = as_string($params->{$param});
}
return {parameters => \%parameters};
}
1;
......
......@@ -25,26 +25,27 @@ extends 'Bugzilla::API::1_0::Resource';
##############
use constant READ_ONLY => qw(
get
get
);
use constant PUBLIC_METHODS => qw(
get
get
);
sub REST_RESOURCES {
my $rest_resources = [
qr{^/classification/([^/]+)$}, {
GET => {
method => 'get',
params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return { $param => [ $_[0] ] };
}
}
my $rest_resources = [
qr{^/classification/([^/]+)$},
{
GET => {
method => 'get',
params => sub {
my $param = $_[0] =~ /^\d+$/ ? 'ids' : 'names';
return {$param => [$_[0]]};
}
];
return $rest_resources;
}
}
];
return $rest_resources;
}
############
......@@ -52,57 +53,68 @@ sub REST_RESOURCES {
############
sub get {
my ($self, $params) = validate(@_, 'names', 'ids');
my ($self, $params) = validate(@_, 'names', 'ids');
defined $params->{names} || defined $params->{ids}
|| ThrowCodeError('params_required', { function => 'Classification.get',
params => ['names', 'ids'] });
defined $params->{names}
|| defined $params->{ids}
|| ThrowCodeError('params_required',
{function => 'Classification.get', params => ['names', 'ids']});
my $user = Bugzilla->user;
my $user = Bugzilla->user;
Bugzilla->params->{'useclassification'}
|| $user->in_group('editclassifications')
|| ThrowUserError('auth_classification_not_enabled');
Bugzilla->params->{'useclassification'}
|| $user->in_group('editclassifications')
|| ThrowUserError('auth_classification_not_enabled');
Bugzilla->switch_to_shadow_db;
Bugzilla->switch_to_shadow_db;
my @classification_objs = @{ params_to_objects($params, 'Bugzilla::Classification') };
unless ($user->in_group('editclassifications')) {
my %selectable_class = map { $_->id => 1 } @{$user->get_selectable_classifications};
@classification_objs = grep { $selectable_class{$_->id} } @classification_objs;
}
my @classification_objs
= @{params_to_objects($params, 'Bugzilla::Classification')};
unless ($user->in_group('editclassifications')) {
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 {
my ($self, $classification, $params) = @_;
my $user = Bugzilla->user;
return unless (Bugzilla->params->{'useclassification'} || $user->in_group('editclassifications'));
my $products = $user->in_group('editclassifications') ?
$classification->products : $user->get_selectable_products($classification->id);
return filter $params, {
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 ],
my ($self, $classification, $params) = @_;
my $user = Bugzilla->user;
return
unless (Bugzilla->params->{'useclassification'}
|| $user->in_group('editclassifications'));
my $products
= $user->in_group('editclassifications')
? $classification->products
: $user->get_selectable_products($classification->id);
return filter $params,
{
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 {
my ($self, $product, $params) = @_;
return filter $params, {
id => as_int($product->id),
name => as_string($product->name),
description => as_string($product->description),
}, undef, 'products';
my ($self, $product, $params) = @_;
return filter $params,
{
id => as_int($product->id),
name => as_string($product->name),
description => as_string($product->description),
},
undef, 'products';
}
1;
......
......@@ -16,18 +16,18 @@ use fields qw();
# 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
# that particular type of login method cannot log out.
use constant can_logout => 1;
use constant can_login => 1;
use constant requires_persistence => 1;
use constant requires_verification => 1;
use constant can_logout => 1;
use constant can_login => 1;
use constant requires_persistence => 1;
use constant requires_verification => 1;
use constant user_can_create_account => 0;
use constant is_automatic => 0;
use constant extern_id_used => 0;
use constant is_automatic => 0;
use constant extern_id_used => 0;
sub new {
my ($class) = @_;
my $self = fields::new($class);
return $self;
my ($class) = @_;
my $self = fields::new($class);
return $self;
}
1;
......
......@@ -26,28 +26,29 @@ use constant can_logout => 0;
# This method is only available to web services. An API key can never
# be used to authenticate a Web request.
sub get_login_info {
my ($self) = @_;
my $params = Bugzilla->input_params;
my ($user_id, $login_cookie);
my ($self) = @_;
my $params = Bugzilla->input_params;
my ($user_id, $login_cookie);
my $api_key_text = trim(delete $params->{'Bugzilla_api_key'});
if (!i_am_webservice() || !$api_key_text) {
return { failure => AUTH_NODATA };
}
my $api_key_text = trim(delete $params->{'Bugzilla_api_key'});
if (!i_am_webservice() || !$api_key_text) {
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) {
# The second part checks the correct capitalisation. Silly MySQL
ThrowUserError("api_key_not_valid");
}
elsif ($api_key->revoked) {
ThrowUserError('api_key_revoked');
}
if (!$api_key or $api_key->api_key ne $api_key_text) {
$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;
......@@ -21,65 +21,71 @@ use Bugzilla::Error;
use Bugzilla::Token;
sub get_login_info {
my ($self) = @_;
my $params = Bugzilla->input_params;
my $cgi = Bugzilla->cgi;
my $login = trim(delete $params->{'Bugzilla_login'});
my $password = delete $params->{'Bugzilla_password'};
# The token must match the cookie to authenticate the request.
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.
if ($login_token && $login_cookie) {
my ($time, undef) = split(/-/, $login_token);
# Regenerate the token based on the information we have.
my $expected_token = issue_hash_token(['login_request', $login_cookie], $time);
$valid = 1 if $expected_token eq $login_token;
$cgi->remove_cookie('Bugzilla_login_request_cookie');
}
# WebServices and other local scripts can bypass this check.
# This is safe because we won't store a login cookie in this case.
elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
$valid = 1;
}
# Else falls back to the Referer header and accept local URLs.
# Attachments are served from a separate host (ideally), and so
# an evil attachment cannot abuse this check with a redirect.
elsif (my $referer = $cgi->referer) {
my $urlbase = correct_urlbase();
$valid = 1 if $referer =~ /^\Q$urlbase\E/;
}
# If the web browser doesn't accept cookies and the Referer header
# is missing, we have no way to make sure that the authentication
# request comes from the user.
elsif ($login && $password) {
ThrowUserError('auth_untrusted_request', { login => $login });
}
if (!defined($login) || !defined($password) || !$valid) {
return { failure => AUTH_NODATA };
}
return { username => $login, password => $password };
my ($self) = @_;
my $params = Bugzilla->input_params;
my $cgi = Bugzilla->cgi;
my $login = trim(delete $params->{'Bugzilla_login'});
my $password = delete $params->{'Bugzilla_password'};
# The token must match the cookie to authenticate the request.
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.
if ($login_token && $login_cookie) {
my ($time, undef) = split(/-/, $login_token);
# Regenerate the token based on the information we have.
my $expected_token = issue_hash_token(['login_request', $login_cookie], $time);
$valid = 1 if $expected_token eq $login_token;
$cgi->remove_cookie('Bugzilla_login_request_cookie');
}
# WebServices and other local scripts can bypass this check.
# This is safe because we won't store a login cookie in this case.
elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
$valid = 1;
}
# Else falls back to the Referer header and accept local URLs.
# Attachments are served from a separate host (ideally), and so
# an evil attachment cannot abuse this check with a redirect.
elsif (my $referer = $cgi->referer) {
my $urlbase = correct_urlbase();
$valid = 1 if $referer =~ /^\Q$urlbase\E/;
}
# If the web browser doesn't accept cookies and the Referer header
# is missing, we have no way to make sure that the authentication
# request comes from the user.
elsif ($login && $password) {
ThrowUserError('auth_untrusted_request', {login => $login});
}
if (!defined($login) || !defined($password) || !$valid) {
return {failure => AUTH_NODATA};
}
return {username => $login, password => $password};
}
sub fail_nodata {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
if (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
ThrowUserError('login_required');
}
print $cgi->header();
$template->process("account/auth/login.html.tmpl",
{ 'target' => $cgi->url(-relative=>1) })
|| ThrowTemplateError($template->error());
exit;
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template;
if (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
ThrowUserError('login_required');
}
print $cgi->header();
$template->process("account/auth/login.html.tmpl",
{'target' => $cgi->url(-relative => 1)})
|| ThrowTemplateError($template->error());
exit;
}
1;
......@@ -23,128 +23,132 @@ use List::Util qw(first);
use constant requires_persistence => 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; }
# Note that Cookie never consults the Verifier, it always assumes
# it has a valid DB account or it fails.
sub get_login_info {
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh;
my ($user_id, $login_cookie);
if (!Bugzilla->request_cache->{auth_no_automatic_login}) {
$login_cookie = $cgi->cookie("Bugzilla_logincookie");
$user_id = $cgi->cookie("Bugzilla_login");
# If cookies cannot be found, this could mean that they haven't
# been made available yet. In this case, look at Bugzilla_cookie_list.
unless ($login_cookie) {
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
@{$cgi->{'Bugzilla_cookie_list'}};
$login_cookie = $cookie->value if $cookie;
}
unless ($user_id) {
my $cookie = first {$_->name eq 'Bugzilla_login'}
@{$cgi->{'Bugzilla_cookie_list'}};
$user_id = $cookie->value if $cookie;
}
my ($self) = @_;
my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh;
my ($user_id, $login_cookie);
if (!Bugzilla->request_cache->{auth_no_automatic_login}) {
$login_cookie = $cgi->cookie("Bugzilla_logincookie");
$user_id = $cgi->cookie("Bugzilla_login");
# If cookies cannot be found, this could mean that they haven't
# been made available yet. In this case, look at Bugzilla_cookie_list.
unless ($login_cookie) {
my $cookie = first { $_->name eq 'Bugzilla_logincookie' }
@{$cgi->{'Bugzilla_cookie_list'}};
$login_cookie = $cookie->value if $cookie;
}
unless ($user_id) {
my $cookie = first { $_->name eq 'Bugzilla_login' }
@{$cgi->{'Bugzilla_cookie_list'}};
$user_id = $cookie->value if $cookie;
}
# If the call is for a web service, and an api token is provided, check
# it is valid.
if (i_am_webservice()) {
if (exists Bugzilla->input_params->{Bugzilla_api_token}) {
my $api_token = Bugzilla->input_params->{Bugzilla_api_token};
my ($token_user_id, undef, undef, $token_type)
= Bugzilla::Token::GetTokenData($api_token);
if (!defined $token_type
|| $token_type ne 'api_token'
|| $user_id != $token_user_id)
{
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 = '';
}
# If the call is for a web service, and an api token is provided, check
# it is valid.
if (i_am_webservice()) {
if (exists Bugzilla->input_params->{Bugzilla_api_token}) {
my $api_token = Bugzilla->input_params->{Bugzilla_api_token};
my ($token_user_id, undef, undef, $token_type)
= Bugzilla::Token::GetTokenData($api_token);
if ( !defined $token_type
|| $token_type ne 'api_token'
|| $user_id != $token_user_id)
{
ThrowUserError('auth_invalid_token', {token => $api_token});
}
}
}
elsif ($login_cookie && Bugzilla->usage_mode == USAGE_MODE_REST) {
# 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'});
# REST requires an api-token when using cookie authentication
# fall back to a non-authenticated request
$login_cookie = '';
}
}
}
# 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
# we're going to verify against the db
trick_taint($ip_addr);
trick_taint($login_cookie);
detaint_natural($user_id);
# Anything goes for these params - they're just strings which
# we're going to verify against the db
trick_taint($ip_addr);
trick_taint($login_cookie);
detaint_natural($user_id);
my $db_cookie =
$dbh->selectrow_array('SELECT cookie
my $db_cookie = $dbh->selectrow_array(
'SELECT cookie
FROM logincookies
WHERE cookie = ?
AND userid = ?
AND (ipaddr = ? OR ipaddr IS NULL)',
undef, ($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
# throw an error notifying the client.
if (defined $db_cookie && $login_cookie eq $db_cookie) {
# If we logged in successfully, then update the lastused
# time on the login cookie
$dbh->do("UPDATE logincookies SET lastused = NOW()
WHERE cookie = ?", undef, $login_cookie);
return { user_id => $user_id };
}
elsif (i_am_webservice()) {
ThrowUserError('invalid_cookies_or_token');
}
AND (ipaddr = ? OR ipaddr IS NULL)', undef,
($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
# throw an error notifying the client.
if (defined $db_cookie && $login_cookie eq $db_cookie) {
# If we logged in successfully, then update the lastused
# time on the login cookie
$dbh->do(
"UPDATE logincookies SET lastused = NOW()
WHERE cookie = ?", undef, $login_cookie
);
return {user_id => $user_id};
}
# Either the cookie or token is invalid and we are not authenticating
# 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 };
elsif (i_am_webservice()) {
ThrowUserError('invalid_cookies_or_token');
}
}
# Either the cookie or token is invalid and we are not authenticating
# 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 {
my ($self) = @_;
my $input = Bugzilla->input_params;
my $usage_mode = Bugzilla->usage_mode;
my ($self) = @_;
my $input = Bugzilla->input_params;
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()) {
return $self->{'_login_token'} = undef;
}
if (!i_am_webservice()) {
return $self->{'_login_token'} = undef;
}
# Check if a token was passed in via requests for WebServices
my $token = trim(delete $input->{'Bugzilla_token'});
return $self->{'_login_token'} = undef if !$token;
# Check if a token was passed in via requests for WebServices
my $token = trim(delete $input->{'Bugzilla_token'});
return $self->{'_login_token'} = undef if !$token;
my ($user_id, $login_token) = split('-', $token, 2);
if (!detaint_natural($user_id) || !$login_token) {
return $self->{'_login_token'} = undef;
}
my ($user_id, $login_token) = split('-', $token, 2);
if (!detaint_natural($user_id) || !$login_token) {
return $self->{'_login_token'} = undef;
}
return $self->{'_login_token'} = {
user_id => $user_id,
login_token => $login_token
};
return $self->{'_login_token'}
= {user_id => $user_id, login_token => $login_token};
}
1;
......@@ -16,28 +16,31 @@ use parent qw(Bugzilla::Auth::Login);
use Bugzilla::Constants;
use Bugzilla::Error;
use constant can_logout => 0;
use constant can_login => 0;
use constant can_logout => 0;
use constant can_login => 0;
use constant requires_persistence => 0;
use constant requires_verification => 0;
use constant is_automatic => 1;
use constant extern_id_used => 1;
use constant is_automatic => 1;
use constant extern_id_used => 1;
sub get_login_info {
my ($self) = @_;
my ($self) = @_;
my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || '';
my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || '';
my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || '';
my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || '';
my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || '';
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,
realname => $env_realname };
return {
username => $env_email,
extern_id => $env_id,
realname => $env_realname
};
}
sub fail_nodata {
ThrowCodeError('env_no_email');
ThrowCodeError('env_no_email');
}
1;
......@@ -13,8 +13,8 @@ use warnings;
use base qw(Bugzilla::Auth::Login);
use fields qw(
_stack
successful
_stack
successful
);
use Hash::Util qw(lock_keys);
use Bugzilla::Hook;
......@@ -22,81 +22,87 @@ use Bugzilla::Constants;
use List::MoreUtils qw(any);
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
my $list = shift;
my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
lock_keys(%methods);
Bugzilla::Hook::process('auth_login_methods', { modules => \%methods });
$self->{_stack} = [];
foreach my $login_method (split(',', $list)) {
my $module = $methods{$login_method};
require $module;
$module =~ s|/|::|g;
$module =~ s/.pm$//;
push(@{$self->{_stack}}, $module->new(@_));
}
return $self;
my $class = shift;
my $self = $class->SUPER::new(@_);
my $list = shift;
my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
lock_keys(%methods);
Bugzilla::Hook::process('auth_login_methods', {modules => \%methods});
$self->{_stack} = [];
foreach my $login_method (split(',', $list)) {
my $module = $methods{$login_method};
require $module;
$module =~ s|/|::|g;
$module =~ s/.pm$//;
push(@{$self->{_stack}}, $module->new(@_));
}
return $self;
}
sub get_login_info {
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
# See Bugzilla::WebService::Server::JSONRPC for where and why
# auth_no_automatic_login is used.
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;
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
# See Bugzilla::WebService::Server::JSONRPC for where and why
# auth_no_automatic_login is used.
if (Bugzilla->request_cache->{auth_no_automatic_login}) {
next if $object->is_automatic;
}
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 {
my $self = shift;
# We fail from the bottom of the stack.
my @reverse_stack = reverse @{$self->{_stack}};
foreach my $object (@reverse_stack) {
# We pick the first object that actually has the method
# implemented.
if ($object->can('fail_nodata')) {
$object->fail_nodata(@_);
}
my $self = shift;
# We fail from the bottom of the stack.
my @reverse_stack = reverse @{$self->{_stack}};
foreach my $object (@reverse_stack) {
# We pick the first object that actually has the method
# implemented.
if ($object->can('fail_nodata')) {
$object->fail_nodata(@_);
}
}
}
sub can_login {
my ($self) = @_;
# We return true if any method can log in.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_login;
}
return 0;
my ($self) = @_;
# We return true if any method can log in.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_login;
}
return 0;
}
sub user_can_create_account {
my ($self) = @_;
# We return true if any method allows users to create accounts.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account;
}
return 0;
my ($self) = @_;
# We return true if any method allows users to create accounts.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account;
}
return 0;
}
sub extern_id_used {
my ($self) = @_;
return any { $_->extern_id_used } @{ $self->{_stack} };
my ($self) = @_;
return any { $_->extern_id_used } @{$self->{_stack}};
}
1;
......@@ -19,119 +19,131 @@ use Bugzilla::User;
use Bugzilla::Util;
use constant user_can_create_account => 1;
use constant extern_id_used => 0;
use constant extern_id_used => 0;
sub new {
my ($class, $login_type) = @_;
my $self = fields::new($class);
return $self;
my ($class, $login_type) = @_;
my $self = fields::new($class);
return $self;
}
sub can_change_password {
return $_[0]->can('change_password');
return $_[0]->can('change_password');
}
sub create_or_update_user {
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
my $extern_id = $params->{extern_id};
my $login = $params->{bz_username} || $params->{username};
my $email = Bugzilla->params->{use_email_as_login} ? $login : $params->{email};
my $password = $params->{password} || '*';
my $real_name = $params->{realname} || '';
my $user_id = $params->{user_id};
# A passed-in user_id always overrides anything else, for determining
# what account we should return.
if (!$user_id) {
my $login_user_id = login_to_id($login || '');
my $extern_user_id;
if ($extern_id) {
trick_taint($extern_id);
$extern_user_id = $dbh->selectrow_array('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;
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
my $extern_id = $params->{extern_id};
my $login = $params->{bz_username} || $params->{username};
my $email = Bugzilla->params->{use_email_as_login} ? $login : $params->{email};
my $password = $params->{password} || '*';
my $real_name = $params->{realname} || '';
my $user_id = $params->{user_id};
# A passed-in user_id always overrides anything else, for determining
# what account we should return.
if (!$user_id) {
my $login_user_id = login_to_id($login || '');
my $extern_user_id;
if ($extern_id) {
trick_taint($extern_id);
$extern_user_id = $dbh->selectrow_array(
'SELECT userid
FROM profiles WHERE extern_id = ?', undef, $extern_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 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 ($login && lc($user->login) ne lc($login)) {
$user->set_login($login);
$changed = 1;
# 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 ($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;
# 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});
}
$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;
......
......@@ -19,95 +19,97 @@ use Bugzilla::Util;
use Bugzilla::User;
sub check_credentials {
my ($self, $login_data) = @_;
my $dbh = Bugzilla->dbh;
my ($self, $login_data) = @_;
my $dbh = Bugzilla->dbh;
my $username = $login_data->{username};
my $user = new Bugzilla::User({ name => $username });
my $username = $login_data->{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->{bz_username} = $user->login;
$login_data->{user} = $user;
$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) {
return { failure => AUTH_LOCKOUT, user => $user };
return {failure => AUTH_LOCKOUT, user => $user, just_locked_out => 1};
}
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) {
return { failure => AUTH_LOCKOUT, user => $user,
just_locked_out => 1 };
}
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 }
}
}
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
# password tokens or login failures they may have generated.
Bugzilla::Token::DeletePasswordTokens($user->id, "user_logged_in");
$user->clear_login_failures();
# The user's credentials are okay, so delete any outstanding
# password tokens or login failures they may have generated.
Bugzilla::Token::DeletePasswordTokens($user->id, "user_logged_in");
$user->clear_login_failures();
my $update_password = 0;
my $update_password = 0;
# If their old password was using crypt() or some different hash
# than we're using now, convert the stored password to using
# whatever hashing system we're using now.
my $current_algorithm = PASSWORD_DIGEST_ALGORITHM;
$update_password = 1 if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/);
# If their old password was using crypt() or some different hash
# than we're using now, convert the stored password to using
# whatever hashing system we're using now.
my $current_algorithm = PASSWORD_DIGEST_ALGORITHM;
$update_password = 1 if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/);
# 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.
if ($real_password_crypted =~ /^([^,]+),/) {
$update_password = 1 if (length($1) != PASSWORD_SALT_LENGTH);
}
# 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.
if ($real_password_crypted =~ /^([^,]+),/) {
$update_password = 1 if (length($1) != PASSWORD_SALT_LENGTH);
}
# If needed, update the user's password.
if ($update_password) {
# We can't call $user->set_password because we don't want the password
# complexity rules to apply here.
$user->{cryptpassword} = bz_crypt($password);
$user->update();
}
# If needed, update the user's password.
if ($update_password) {
# We can't call $user->set_password because we don't want the password
# complexity rules to apply here.
$user->{cryptpassword} = bz_crypt($password);
$user->update();
}
return $login_data;
return $login_data;
}
sub change_password {
my ($self, $user, $password) = @_;
my $dbh = Bugzilla->dbh;
my $cryptpassword = bz_crypt($password);
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
undef, $cryptpassword, $user->id);
Bugzilla->memcached->clear({ table => 'profiles', id => $user->id });
my ($self, $user, $password) = @_;
my $dbh = Bugzilla->dbh;
my $cryptpassword = bz_crypt($password);
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
undef, $cryptpassword, $user->id);
Bugzilla->memcached->clear({table => 'profiles', id => $user->id});
}
1;
......@@ -23,35 +23,39 @@ use constant admin_can_create_account => 0;
use constant user_can_create_account => 0;
sub check_credentials {
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'};
my $username = $params->{username};
# If we're using RADIUS_email_suffix, we may need to cut it off from
# the login name.
if ($address_suffix) {
$username =~ s/\Q$address_suffix\E$//i;
}
# Create RADIUS object.
my $radius =
new Authen::Radius(Host => Bugzilla->params->{'RADIUS_server'},
Secret => Bugzilla->params->{'RADIUS_secret'})
|| return { failure => AUTH_ERROR, error => 'radius_preparation_error',
details => {errstr => Authen::Radius::strerror() } };
# Check the password.
$radius->check_pwd($username, $params->{password},
Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
|| return { failure => AUTH_LOGINFAILED };
$params->{bz_username} = $username;
# Build the user account's e-mail address.
$params->{email} = $username . $address_suffix;
return $params;
my ($self, $params) = @_;
my $dbh = Bugzilla->dbh;
my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'};
my $username = $params->{username};
# If we're using RADIUS_email_suffix, we may need to cut it off from
# the login name.
if ($address_suffix) {
$username =~ s/\Q$address_suffix\E$//i;
}
# Create RADIUS object.
my $radius = new Authen::Radius(
Host => Bugzilla->params->{'RADIUS_server'},
Secret => Bugzilla->params->{'RADIUS_secret'}
)
|| return {
failure => AUTH_ERROR,
error => 'radius_preparation_error',
details => {errstr => Authen::Radius::strerror()}
};
# Check the password.
$radius->check_pwd($username, $params->{password},
Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
|| return {failure => AUTH_LOGINFAILED};
$params->{bz_username} = $username;
# Build the user account's e-mail address.
$params->{email} = $username . $address_suffix;
return $params;
}
1;
......@@ -13,8 +13,8 @@ use warnings;
use base qw(Bugzilla::Auth::Verify);
use fields qw(
_stack
successful
_stack
successful
);
use Bugzilla::Hook;
......@@ -23,70 +23,75 @@ use Hash::Util qw(lock_keys);
use List::MoreUtils qw(any);
sub new {
my $class = shift;
my $list = shift;
my $self = $class->SUPER::new(@_);
my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list);
lock_keys(%methods);
Bugzilla::Hook::process('auth_verify_methods', { modules => \%methods });
$self->{_stack} = [];
foreach my $verify_method (split(',', $list)) {
my $module = $methods{$verify_method};
require $module;
$module =~ s|/|::|g;
$module =~ s/.pm$//;
push(@{$self->{_stack}}, $module->new(@_));
}
return $self;
my $class = shift;
my $list = shift;
my $self = $class->SUPER::new(@_);
my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list);
lock_keys(%methods);
Bugzilla::Hook::process('auth_verify_methods', {modules => \%methods});
$self->{_stack} = [];
foreach my $verify_method (split(',', $list)) {
my $module = $methods{$verify_method};
require $module;
$module =~ s|/|::|g;
$module =~ s/.pm$//;
push(@{$self->{_stack}}, $module->new(@_));
}
return $self;
}
sub can_change_password {
my ($self) = @_;
# We return true if any method can change passwords.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_change_password;
}
return 0;
my ($self) = @_;
# We return true if any method can change passwords.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->can_change_password;
}
return 0;
}
sub check_credentials {
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->check_credentials(@_);
$self->{successful} = $object;
last if !$result->{failure};
# 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;
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->check_credentials(@_);
$self->{successful} = $object;
last if !$result->{failure};
# 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;
}
sub create_or_update_user {
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->create_or_update_user(@_);
last if !$result->{failure};
}
# Returns the result at the bottom of the stack if they all fail.
return $result;
my $self = shift;
my $result;
foreach my $object (@{$self->{_stack}}) {
$result = $object->create_or_update_user(@_);
last if !$result->{failure};
}
# Returns the result at the bottom of the stack if they all fail.
return $result;
}
sub user_can_create_account {
my ($self) = @_;
# We return true if any method allows the user to create an account.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account;
}
return 0;
my ($self) = @_;
# We return true if any method allows the user to create an account.
foreach my $object (@{$self->{_stack}}) {
return 1 if $object->user_can_create_account;
}
return 0;
}
sub extern_id_used {
my ($self) = @_;
return any { $_->extern_id_used } @{ $self->{_stack} };
my ($self) = @_;
return any { $_->extern_id_used } @{$self->{_stack}};
}
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;
use constant DB_TABLE => 'bug_see_also';
use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'id';
# See Also is tracked in bugs_activity.
use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
id
bug_id
value
class
id
bug_id
value
class
);
# This must be strings with the names of the validations,
# instead of coderefs, because subclasses override these
# validators with their own.
use constant VALIDATORS => {
value => '_check_value',
bug_id => '_check_bug_id',
class => \&_check_class,
value => '_check_value',
bug_id => '_check_bug_id',
class => \&_check_class,
};
# This is the order we go through all of subclasses and
# pick the first one that should handle the url. New
# subclasses should be added at the end of the list.
use constant SUB_CLASSES => qw(
Bugzilla::BugUrl::Bugzilla::Local
Bugzilla::BugUrl::Bugzilla
Bugzilla::BugUrl::Launchpad
Bugzilla::BugUrl::Google
Bugzilla::BugUrl::Debian
Bugzilla::BugUrl::JIRA
Bugzilla::BugUrl::Trac
Bugzilla::BugUrl::MantisBT
Bugzilla::BugUrl::SourceForge
Bugzilla::BugUrl::GitHub
Bugzilla::BugUrl::Bugzilla::Local
Bugzilla::BugUrl::Bugzilla
Bugzilla::BugUrl::Launchpad
Bugzilla::BugUrl::Google
Bugzilla::BugUrl::Debian
Bugzilla::BugUrl::JIRA
Bugzilla::BugUrl::Trac
Bugzilla::BugUrl::MantisBT
Bugzilla::BugUrl::SourceForge
Bugzilla::BugUrl::GitHub
);
###############################
#### Accessors ######
###############################
sub class { return $_[0]->{class} }
sub class { return $_[0]->{class} }
sub bug_id { return $_[0]->{bug_id} }
###############################
......@@ -77,130 +78,127 @@ sub bug_id { return $_[0]->{bug_id} }
###############################
sub new {
my $class = shift;
my $param = shift;
if (ref $param) {
my $bug_id = $param->{bug_id};
my $name = $param->{name} || $param->{value};
if (!defined $bug_id) {
ThrowCodeError('bad_arg',
{ argument => 'bug_id',
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 };
my $class = shift;
my $param = shift;
if (ref $param) {
my $bug_id = $param->{bug_id};
my $name = $param->{name} || $param->{value};
if (!defined $bug_id) {
ThrowCodeError('bad_arg', {argument => 'bug_id', function => "${class}::new"});
}
if (!defined $name) {
ThrowCodeError('bad_arg', {argument => 'name', function => "${class}::new"});
}
unshift @_, $param;
return $class->SUPER::new(@_);
my $condition = 'bug_id = ? AND value = ?';
my @values = ($bug_id, $name);
$param = {condition => $condition, values => \@values};
}
unshift @_, $param;
return $class->SUPER::new(@_);
}
sub _do_list_select {
my $class = shift;
my $objects = $class->SUPER::_do_list_select(@_);
my $class = shift;
my $objects = $class->SUPER::_do_list_select(@_);
foreach my $object (@$objects) {
eval "use " . $object->class;
# If the class cannot be loaded, then we build a generic object.
bless $object, ($@ ? 'Bugzilla::BugUrl' : $object->class);
}
foreach my $object (@$objects) {
eval "use " . $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
# in every subclass.
sub should_handle {
my ($class, $input) = @_;
ThrowCodeError('unknown_method',
{ method => "${class}::should_handle" });
my ($class, $input) = @_;
ThrowCodeError('unknown_method', {method => "${class}::should_handle"});
}
sub class_for {
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);
}
my ($class, $value) = @_;
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 {
my ($class, $subclass) = @_;
eval "use $subclass"; die $@ if $@;
return $subclass;
my ($class, $subclass) = @_;
eval "use $subclass";
die $@ if $@;
return $subclass;
}
sub _check_bug_id {
my ($class, $bug_id) = @_;
my ($class, $bug_id) = @_;
my $bug;
if (blessed $bug_id) {
# We got a bug object passed in, use it
$bug = $bug_id;
$bug->check_is_visible;
}
else {
# We got a bug id passed in, check it and get the bug object
$bug = Bugzilla::Bug->check({ id => $bug_id });
}
my $bug;
if (blessed $bug_id) {
# We got a bug object passed in, use it
$bug = $bug_id;
$bug->check_is_visible;
}
else {
# 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 {
my ($class, $uri) = @_;
my $value = $uri->as_string;
if (!$value) {
ThrowCodeError('param_required',
{ function => 'add_see_also', param => '$value' });
}
# We assume that the URL is an HTTP URL if there is no (something)://
# in front.
if (!$uri->scheme) {
# This works better than setting $uri->scheme('http'), because
# that creates URLs like "http:domain.com" and doesn't properly
# 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' });
}
# 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
if (!$uri->authority or $uri->path !~ m{/}) {
ThrowUserError('bug_url_invalid',
{ url => $value, reason => 'path_only' });
}
if (length($uri->path) > MAX_BUG_URL_LENGTH) {
ThrowUserError('bug_url_too_long', { url => $uri->path });
}
return $uri;
my ($class, $uri) = @_;
my $value = $uri->as_string;
if (!$value) {
ThrowCodeError('param_required',
{function => 'add_see_also', param => '$value'});
}
# We assume that the URL is an HTTP URL if there is no (something)://
# in front.
if (!$uri->scheme) {
# This works better than setting $uri->scheme('http'), because
# that creates URLs like "http:domain.com" and doesn't properly
# 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'});
}
# 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
if (!$uri->authority or $uri->path !~ m{/}) {
ThrowUserError('bug_url_invalid', {url => $value, reason => 'path_only'});
}
if (length($uri->path) > MAX_BUG_URL_LENGTH) {
ThrowUserError('bug_url_too_long', {url => $uri->path});
}
return $uri;
}
1;
......
......@@ -21,37 +21,39 @@ use Bugzilla::Util;
###############################
sub should_handle {
my ($class, $uri) = @_;
return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0;
my ($class, $uri) = @_;
return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0;
}
sub _check_value {
my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri);
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.
# When we start validating the URL by accessing the other Bugzilla,
# we can allow aliases.
detaint_natural($bug_id);
if (!$bug_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");
# And remove any # part if there is one.
$uri->fragment(undef);
return $uri;
my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri);
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.
# When we start validating the URL by accessing the other Bugzilla,
# we can allow aliases.
detaint_natural($bug_id);
if (!$bug_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");
# And remove any # part if there is one.
$uri->fragment(undef);
return $uri;
}
sub target_bug_id {
my ($self) = @_;
return new URI($self->name)->query_param('id');
my ($self) = @_;
return new URI($self->name)->query_param('id');
}
1;
......@@ -20,77 +20,75 @@ use Bugzilla::Util;
#### Initialization ####
###############################
use constant VALIDATOR_DEPENDENCIES => {
value => ['bug_id'],
};
use constant VALIDATOR_DEPENDENCIES => {value => ['bug_id'],};
###############################
#### Methods ####
###############################
sub ref_bug_url {
my $self = shift;
if (!exists $self->{ref_bug_url}) {
my $ref_bug_id = new URI($self->name)->query_param('id');
my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
my $ref_value = $self->local_uri($self->bug_id);
$self->{ref_bug_url} =
new Bugzilla::BugUrl::Bugzilla::Local({ bug_id => $ref_bug->id,
value => $ref_value });
}
return $self->{ref_bug_url};
my $self = shift;
if (!exists $self->{ref_bug_url}) {
my $ref_bug_id = new URI($self->name)->query_param('id');
my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
my $ref_value = $self->local_uri($self->bug_id);
$self->{ref_bug_url} = new Bugzilla::BugUrl::Bugzilla::Local(
{bug_id => $ref_bug->id, value => $ref_value});
}
return $self->{ref_bug_url};
}
sub should_handle {
my ($class, $uri) = @_;
# Check if it is either a bug id number or an alias.
return 1 if $uri->as_string =~ m/^\w+$/;
# Check if it is a local Bugzilla uri and call
# Bugzilla::BugUrl::Bugzilla to check if it's a valid Bugzilla
# see also url.
my $canonical_local = URI->new($class->local_uri)->canonical;
if ($canonical_local->authority eq $uri->canonical->authority
and $canonical_local->path eq $uri->canonical->path)
{
return $class->SUPER::should_handle($uri);
}
return 0;
my ($class, $uri) = @_;
# Check if it is either a bug id number or an alias.
return 1 if $uri->as_string =~ m/^\w+$/;
# Check if it is a local Bugzilla uri and call
# Bugzilla::BugUrl::Bugzilla to check if it's a valid Bugzilla
# see also url.
my $canonical_local = URI->new($class->local_uri)->canonical;
if ( $canonical_local->authority eq $uri->canonical->authority
and $canonical_local->path eq $uri->canonical->path)
{
return $class->SUPER::should_handle($uri);
}
return 0;
}
sub _check_value {
my ($class, $uri, undef, $params) = @_;
# At this point we are going to treat any word as a
# bug id/alias to the local Bugzilla.
my $value = $uri->as_string;
if ($value =~ m/^\w+$/) {
$uri = new URI($class->local_uri($value));
} else {
# It's not a word, then we have to check
# 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 $self_bug_id = $params->{bug_id};
$params->{ref_bug} = $ref_bug;
if ($ref_bug->id == $self_bug_id) {
ThrowUserError('see_also_self_reference');
}
return $uri;
my ($class, $uri, undef, $params) = @_;
# At this point we are going to treat any word as a
# bug id/alias to the local Bugzilla.
my $value = $uri->as_string;
if ($value =~ m/^\w+$/) {
$uri = new URI($class->local_uri($value));
}
else {
# It's not a word, then we have to check
# 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 $self_bug_id = $params->{bug_id};
$params->{ref_bug} = $ref_bug;
if ($ref_bug->id == $self_bug_id) {
ThrowUserError('see_also_self_reference');
}
return $uri;
}
sub local_uri {
my ($self, $bug_id) = @_;
$bug_id ||= '';
return correct_urlbase() . "show_bug.cgi?id=$bug_id";
my ($self, $bug_id) = @_;
$bug_id ||= '';
return correct_urlbase() . "show_bug.cgi?id=$bug_id";
}
1;
......@@ -18,31 +18,35 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
my ($class, $uri) = @_;
# Debian BTS URLs can look like various things:
# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
# http://bugs.debian.org/1234
# https://debbugs.gnu.org/cgi/bugreport.cgi?bug=123
# https://debbugs.gnu.org/123
return ((lc($uri->authority) eq 'bugs.debian.org'
or lc($uri->authority) eq 'debbugs.gnu.org')
and (($uri->path =~ /bugreport\.cgi$/
and $uri->query_param('bug') =~ m|^\d+$|a)
or $uri->path =~ m|^/\d+$|a)) ? 1 : 0;
my ($class, $uri) = @_;
# Debian BTS URLs can look like various things:
# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
# http://bugs.debian.org/1234
# https://debbugs.gnu.org/cgi/bugreport.cgi?bug=123
# https://debbugs.gnu.org/123
return (
(
lc($uri->authority) eq 'bugs.debian.org'
or lc($uri->authority) eq 'debbugs.gnu.org'
)
and
(($uri->path =~ /bugreport\.cgi$/ and $uri->query_param('bug') =~ m|^\d+$|a)
or $uri->path =~ m|^/\d+$|a)
) ? 1 : 0;
}
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,
# and so we reduce all URLs to this.
$uri->path =~ m|^/(\d+)$|a || $uri->query_param('bug') =~ m|^(\d+)$|a;
$uri = new URI('https://' . $uri->authority . '/' . $1);
# This is the shortest standard URL form for Debian BTS URLs,
# and so we reduce all URLs to this.
$uri->path =~ m|^/(\d+)$|a || $uri->query_param('bug') =~ m|^(\d+)$|a;
$uri = new URI('https://' . $uri->authority . '/' . $1);
return $uri;
return $uri;
}
1;
......@@ -18,25 +18,25 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
my ($class, $uri) = @_;
# GitHub issue URLs have only one form:
# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/issues/111
# GitHub pull request URLs have only one form:
# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/pull/111
return (lc($uri->authority) eq 'github.com'
and $uri->path =~ m!^/[^/]+/[^/]+/(?:issues|pull)/\d+$!) ? 1 : 0;
my ($class, $uri) = @_;
# GitHub issue URLs have only one form:
# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/issues/111
# GitHub pull request URLs have only one form:
# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/pull/111
return (lc($uri->authority) eq 'github.com'
and $uri->path =~ m!^/[^/]+/[^/]+/(?:issues|pull)/\d+$!) ? 1 : 0;
}
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.
$uri->scheme('https');
# GitHub HTTP URLs redirect to HTTPS, so just use the HTTPS scheme.
$uri->scheme('https');
return $uri;
return $uri;
}
1;
......@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
my ($class, $uri) = @_;
my ($class, $uri) = @_;
# Google Code URLs only have one form:
# http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
return (lc($uri->authority) eq 'code.google.com'
and $uri->path =~ m|^/p/[^/]+/issues/detail$|
and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0;
# Google Code URLs only have one form:
# http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
return (lc($uri->authority) eq 'code.google.com'
and $uri->path =~ m|^/p/[^/]+/issues/detail$|
and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0;
}
sub _check_value {
my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri);
my ($class, $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');
}
$uri = $class->SUPER::_check_value($uri);
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;
......@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
my ($class, $uri) = @_;
my ($class, $uri) = @_;
# JIRA URLs have only one basic form (but the jira is optional):
# https://issues.apache.org/jira/browse/KEY-1234
# http://issues.example.com/browse/KEY-1234
return ($uri->path =~ m|/browse/[A-Z][A-Z]+-\d+$|) ? 1 : 0;
# JIRA URLs have only one basic form (but the jira is optional):
# https://issues.apache.org/jira/browse/KEY-1234
# http://issues.example.com/browse/KEY-1234
return ($uri->path =~ m|/browse/[A-Z][A-Z]+-\d+$|) ? 1 : 0;
}
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.
$uri->query(undef);
# And remove any # part if there is one.
$uri->fragment(undef);
# Make sure there are no query parameters.
$uri->query(undef);
return $uri;
# And remove any # part if there is one.
$uri->fragment(undef);
return $uri;
}
1;
......@@ -18,27 +18,28 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
my ($class, $uri) = @_;
# Launchpad bug URLs can look like various things:
# https://bugs.launchpad.net/ubuntu/+bug/1234
# https://launchpad.net/bugs/1234
# All variations end with either "/bugs/1234" or "/+bug/1234"
return ($uri->authority =~ /launchpad\.net$/
and $uri->path =~ m|bugs?/\d+$|a) ? 1 : 0;
my ($class, $uri) = @_;
# Launchpad bug URLs can look like various things:
# https://bugs.launchpad.net/ubuntu/+bug/1234
# https://launchpad.net/bugs/1234
# All variations end with either "/bugs/1234" or "/+bug/1234"
return ($uri->authority =~ /launchpad\.net$/ and $uri->path =~ m|bugs?/\d+$|a)
? 1
: 0;
}
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,
# and so we reduce all URLs to this.
$uri->path =~ m|bugs?/(\d+)$|a;
$uri = new URI("https://launchpad.net/bugs/$1");
# This is the shortest standard URL form for Launchpad bugs,
# and so we reduce all URLs to this.
$uri->path =~ m|bugs?/(\d+)$|a;
$uri = new URI("https://launchpad.net/bugs/$1");
return $uri;
return $uri;
}
1;
......@@ -18,22 +18,22 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
my ($class, $uri) = @_;
my ($class, $uri) = @_;
# MantisBT URLs look like the following ('bugs' directory is optional):
# http://www.mantisbt.org/bugs/view.php?id=1234
return ($uri->path_query =~ m|view\.php\?id=\d+$|) ? 1 : 0;
# MantisBT URLs look like the following ('bugs' directory is optional):
# http://www.mantisbt.org/bugs/view.php?id=1234
return ($uri->path_query =~ m|view\.php\?id=\d+$|) ? 1 : 0;
}
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.
$uri->fragment(undef);
# Remove any # part if there is one.
$uri->fragment(undef);
return $uri;
return $uri;
}
1;
......@@ -18,38 +18,45 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
my ($class, $uri) = @_;
# SourceForge tracker URLs have only one form:
# http://sourceforge.net/tracker/?func=detail&aid=111&group_id=111&atid=111
# SourceForge Allura ticket URLs have several forms:
# http://sourceforge.net/p/project/bugs/12345/
# http://sourceforge.net/p/project/feature-requests/12345/
# http://sourceforge.net/p/project/patches/12345/
# http://sourceforge.net/p/project/support-requests/12345/
return (lc($uri->authority) eq 'sourceforge.net'
and (($uri->path eq '/tracker/'
and $uri->query_param('func') eq 'detail'
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;
my ($class, $uri) = @_;
# SourceForge tracker URLs have only one form:
# http://sourceforge.net/tracker/?func=detail&aid=111&group_id=111&atid=111
# SourceForge Allura ticket URLs have several forms:
# http://sourceforge.net/p/project/bugs/12345/
# http://sourceforge.net/p/project/feature-requests/12345/
# http://sourceforge.net/p/project/patches/12345/
# http://sourceforge.net/p/project/support-requests/12345/
return (
lc($uri->authority) eq 'sourceforge.net'
and (
(
$uri->path eq '/tracker/'
and $uri->query_param('func') eq 'detail'
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 {
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.
$uri->fragment(undef);
# Remove any # part if there is one.
$uri->fragment(undef);
# Make sure the trailing slash is present
my $path = $uri->path;
$path =~ s!/*$!/!;
$uri->path($path);
# Make sure the trailing slash is present
my $path = $uri->path;
$path =~ s!/*$!/!;
$uri->path($path);
return $uri;
return $uri;
}
1;
......@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl);
###############################
sub should_handle {
my ($class, $uri) = @_;
my ($class, $uri) = @_;
# Trac URLs can look like various things:
# http://dev.mutt.org/trac/ticket/1234
# http://trac.roundcube.net/ticket/1484130
return ($uri->path =~ m|/ticket/\d+$|) ? 1 : 0;
# Trac URLs can look like various things:
# http://dev.mutt.org/trac/ticket/1234
# http://trac.roundcube.net/ticket/1484130
return ($uri->path =~ m|/ticket/\d+$|) ? 1 : 0;
}
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.
$uri->query(undef);
# And remove any # part if there is one.
$uri->fragment(undef);
# Make sure there are no query parameters.
$uri->query(undef);
return $uri;
# And remove any # part if there is one.
$uri->fragment(undef);
return $uri;
}
1;
......@@ -25,25 +25,27 @@ use constant LIST_ORDER => 'id';
use constant NAME_FIELD => 'id';
# turn off auditing and exclude these objects from memcached
use constant { AUDIT_CREATES => 0,
AUDIT_UPDATES => 0,
AUDIT_REMOVES => 0,
USE_MEMCACHED => 0 };
use constant {
AUDIT_CREATES => 0,
AUDIT_UPDATES => 0,
AUDIT_REMOVES => 0,
USE_MEMCACHED => 0
};
#####################################################################
# Provide accessors for our columns
#####################################################################
sub id { return $_[0]->{id} }
sub bug_id { return $_[0]->{bug_id} }
sub user_id { return $_[0]->{user_id} }
sub id { return $_[0]->{id} }
sub bug_id { return $_[0]->{bug_id} }
sub user_id { return $_[0]->{user_id} }
sub last_visit_ts { return $_[0]->{last_visit_ts} }
sub user {
my $self = shift;
my $self = shift;
$self->{user} //= Bugzilla::User->new({ id => $self->user_id, cache => 1 });
return $self->{user};
$self->{user} //= Bugzilla::User->new({id => $self->user_id, cache => 1});
return $self->{user};
}
1;
......
......@@ -17,122 +17,125 @@ use Type::Utils;
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 {
$_ =~ m{
$_ =~ m{
^(?: https?:// )? # optional http:// or https://
[*A-Za-z0-9.-]+ # hostname including wildcards. Possibly too permissive.
(?: :[0-9]+ )? # optional port
}x;
};
my $SRC = $SRC_KEYWORD | $SRC_URI;
my $SOURCE_LIST = ArrayRef[$SRC];
my $REFERRER_KEYWORD = enum [qw(
my $SRC = $SRC_KEYWORD | $SRC_URI;
my $SOURCE_LIST = ArrayRef [$SRC];
my $REFERRER_KEYWORD = enum [
qw(
no-referrer no-referrer-when-downgrade
origin origin-when-cross-origin unsafe-url
)];
)
];
my @ALL_BOOL = qw( sandbox upgrade_insecure_requests );
my @ALL_SRC = qw(
default_src child_src connect_src
font_src img_src media_src
object_src script_src style_src
my @ALL_SRC = qw(
default_src child_src connect_src
font_src img_src media_src
object_src script_src style_src
);
has \@ALL_SRC => ( is => 'ro', isa => $SOURCE_LIST, predicate => 1 );
has \@ALL_BOOL => ( is => 'ro', isa => Bool, default => 0 );
has 'report_uri' => ( is => 'ro', isa => Str, predicate => 1 );
has 'base_uri' => ( is => 'ro', isa => Str, predicate => 1 );
has 'report_only' => ( is => 'ro', isa => Bool );
has 'referrer' => ( is => 'ro', isa => $REFERRER_KEYWORD, predicate => 1 );
has 'value' => ( is => 'lazy' );
has 'nonce' => ( is => 'lazy', init_arg => undef, predicate => 1 );
has 'disable' => ( is => 'ro', isa => Bool, default => 0 );
has \@ALL_SRC => (is => 'ro', isa => $SOURCE_LIST, predicate => 1);
has \@ALL_BOOL => (is => 'ro', isa => Bool, default => 0);
has 'report_uri' => (is => 'ro', isa => Str, predicate => 1);
has 'base_uri' => (is => 'ro', isa => Str, predicate => 1);
has 'report_only' => (is => 'ro', isa => Bool);
has 'referrer' => (is => 'ro', isa => $REFERRER_KEYWORD, predicate => 1);
has 'value' => (is => 'lazy');
has 'nonce' => (is => 'lazy', init_arg => undef, predicate => 1);
has 'disable' => (is => 'ro', isa => Bool, default => 0);
sub _has_directive {
my ($self, $directive) = @_;
my $method = 'has_' . $directive;
return $self->$method;
my ($self, $directive) = @_;
my $method = 'has_' . $directive;
return $self->$method;
}
sub header_names {
my ($self) = @_;
my @names = ('Content-Security-Policy', 'X-Content-Security-Policy', 'X-WebKit-CSP');
if ($self->report_only) {
return map { $_ . '-Report-Only' } @names;
}
else {
return @names;
}
my ($self) = @_;
my @names
= ('Content-Security-Policy', 'X-Content-Security-Policy', 'X-WebKit-CSP');
if ($self->report_only) {
return map { $_ . '-Report-Only' } @names;
}
else {
return @names;
}
}
sub add_cgi_headers {
my ($self, $headers) = @_;
return if $self->disable;
foreach my $name ($self->header_names) {
$headers->{"-$name"} = $self->value;
}
my ($self, $headers) = @_;
return if $self->disable;
foreach my $name ($self->header_names) {
$headers->{"-$name"} = $self->value;
}
}
sub _build_value {
my $self = shift;
my @result;
my @list_directives = (@ALL_SRC);
my @boolean_directives = (@ALL_BOOL);
my @single_directives = qw(report_uri base_uri);
foreach my $directive (@list_directives) {
next unless $self->_has_directive($directive);
my @values = map { $self->_quote($_) } @{ $self->$directive };
if (@values) {
push @result, join(' ', _name($directive), @values);
}
my $self = shift;
my @result;
my @list_directives = (@ALL_SRC);
my @boolean_directives = (@ALL_BOOL);
my @single_directives = qw(report_uri base_uri);
foreach my $directive (@list_directives) {
next unless $self->_has_directive($directive);
my @values = map { $self->_quote($_) } @{$self->$directive};
if (@values) {
push @result, join(' ', _name($directive), @values);
}
}
foreach my $directive (@single_directives) {
next unless $self->_has_directive($directive);
my $value = $self->$directive;
if (defined $value) {
push @result, _name($directive) . ' ' . $value;
}
foreach my $directive (@single_directives) {
next unless $self->_has_directive($directive);
my $value = $self->$directive;
if (defined $value) {
push @result, _name($directive) . ' ' . $value;
}
}
foreach my $directive (@boolean_directives) {
if ($self->$directive) {
push @result, _name($directive);
}
foreach my $directive (@boolean_directives) {
if ($self->$directive) {
push @result, _name($directive);
}
}
return join('; ', @result);
return join('; ', @result);
}
sub _build_nonce {
return generate_random_password(48);
return generate_random_password(48);
}
sub _name {
my $name = shift;
$name =~ tr/_/-/;
return $name;
my $name = shift;
$name =~ tr/_/-/;
return $name;
}
sub _quote {
my ($self, $val) = @_;
if ($val eq 'nonce') {
return q{'nonce-} . $self->nonce . q{'};
}
elsif ($SRC_KEYWORD->check($val)) {
return qq{'$val'};
}
else {
return $val;
}
my ($self, $val) = @_;
if ($val eq 'nonce') {
return q{'nonce-} . $self->nonce . q{'};
}
elsif ($SRC_KEYWORD->check($val)) {
return qq{'$val'};
}
else {
return $val;
}
}
1;
__END__
......
......@@ -26,26 +26,26 @@ use parent qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object Exporter);
use constant IS_CONFIG => 1;
use constant DB_TABLE => 'classifications';
use constant DB_TABLE => 'classifications';
use constant LIST_ORDER => 'sortkey, name';
use constant DB_COLUMNS => qw(
id
name
description
sortkey
id
name
description
sortkey
);
use constant UPDATE_COLUMNS => qw(
name
description
sortkey
name
description
sortkey
);
use constant VALIDATORS => {
name => \&_check_name,
description => \&_check_description,
sortkey => \&_check_sortkey,
name => \&_check_name,
description => \&_check_description,
sortkey => \&_check_sortkey,
};
###############################
......@@ -53,29 +53,31 @@ use constant VALIDATORS => {
###############################
sub remove_from_db {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $self = shift;
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.
my $product_ids = $dbh->selectcol_arrayref(
'SELECT id FROM products WHERE classification_id = ?', undef, $self->id);
if (@$product_ids) {
$dbh->do('UPDATE products SET classification_id = 1 WHERE '
. $dbh->sql_in('id', $product_ids));
foreach my $id (@$product_ids) {
Bugzilla->memcached->clear({ table => 'products', id => $id });
}
Bugzilla->memcached->clear_config();
# Reclassify products to the default classification, if needed.
my $product_ids
= $dbh->selectcol_arrayref(
'SELECT id FROM products WHERE classification_id = ?',
undef, $self->id);
if (@$product_ids) {
$dbh->do('UPDATE products SET classification_id = 1 WHERE '
. $dbh->sql_in('id', $product_ids));
foreach my $id (@$product_ids) {
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 {
###############################
sub _check_name {
my ($invocant, $name) = @_;
$name = trim($name);
$name || ThrowUserError('classification_not_specified');
if (length($name) > MAX_CLASSIFICATION_SIZE) {
ThrowUserError('classification_name_too_long', {'name' => $name});
}
my $classification = new Bugzilla::Classification({name => $name});
if ($classification && (!ref $invocant || $classification->id != $invocant->id)) {
ThrowUserError("classification_already_exists", { name => $classification->name });
}
return $name;
my ($invocant, $name) = @_;
$name = trim($name);
$name || ThrowUserError('classification_not_specified');
if (length($name) > MAX_CLASSIFICATION_SIZE) {
ThrowUserError('classification_name_too_long', {'name' => $name});
}
my $classification = new Bugzilla::Classification({name => $name});
if ($classification && (!ref $invocant || $classification->id != $invocant->id))
{
ThrowUserError("classification_already_exists",
{name => $classification->name});
}
return $name;
}
sub _check_description {
my ($invocant, $description) = @_;
my ($invocant, $description) = @_;
$description = trim($description || '');
return $description;
$description = trim($description || '');
return $description;
}
sub _check_sortkey {
my ($invocant, $sortkey) = @_;
$sortkey ||= 0;
my $stored_sortkey = $sortkey;
if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) {
ThrowUserError('classification_invalid_sortkey', { 'sortkey' => $stored_sortkey });
}
return $sortkey;
my ($invocant, $sortkey) = @_;
$sortkey ||= 0;
my $stored_sortkey = $sortkey;
if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) {
ThrowUserError('classification_invalid_sortkey',
{'sortkey' => $stored_sortkey});
}
return $sortkey;
}
#####################################
......@@ -124,41 +129,45 @@ sub _check_sortkey {
use constant FIELD_NAME => 'classification';
use constant is_default => 0;
use constant is_active => 1;
use constant is_active => 1;
###############################
#### Methods ####
###############################
sub set_name { $_[0]->set('name', $_[1]); }
sub set_name { $_[0]->set('name', $_[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 {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!defined $self->{'product_count'}) {
$self->{'product_count'} = $dbh->selectrow_array(q{
if (!defined $self->{'product_count'}) {
$self->{'product_count'} = $dbh->selectrow_array(
q{
SELECT COUNT(*) FROM products
WHERE classification_id = ?}, undef, $self->id) || 0;
}
return $self->{'product_count'};
WHERE classification_id = ?}, undef, $self->id
) || 0;
}
return $self->{'product_count'};
}
sub products {
my $self = shift;
my $dbh = Bugzilla->dbh;
my $self = shift;
my $dbh = Bugzilla->dbh;
if (!$self->{'products'}) {
my $product_ids = $dbh->selectcol_arrayref(q{
if (!$self->{'products'}) {
my $product_ids = $dbh->selectcol_arrayref(
q{
SELECT id FROM products
WHERE classification_id = ?
ORDER BY name}, undef, $self->id);
ORDER BY name}, undef, $self->id
);
$self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
}
return $self->{'products'};
$self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
}
return $self->{'products'};
}
###############################
......@@ -166,7 +175,7 @@ sub products {
###############################
sub description { return $_[0]->{'description'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
sub sortkey { return $_[0]->{'sortkey'}; }
###############################
......@@ -177,27 +186,32 @@ sub sortkey { return $_[0]->{'sortkey'}; }
# in global/choose-product.html.tmpl.
sub sort_products_by_classification {
my $products = shift;
my $list;
if (Bugzilla->params->{'useclassification'}) {
my $class = {};
# Get all classifications with at least one product.
foreach my $product (@$products) {
$class->{$product->classification_id}->{'object'} ||=
new Bugzilla::Classification($product->classification_id);
# Nice way to group products per classification, without querying
# the DB again.
push(@{$class->{$product->classification_id}->{'products'}}, $product);
}
$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}];
my $products = shift;
my $list;
if (Bugzilla->params->{'useclassification'}) {
my $class = {};
# Get all classifications with at least one product.
foreach my $product (@$products) {
$class->{$product->classification_id}->{'object'}
||= new Bugzilla::Classification($product->classification_id);
# Nice way to group products per classification, without querying
# the DB again.
push(@{$class->{$product->classification_id}->{'products'}}, $product);
}
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;
......
......@@ -21,20 +21,20 @@ use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw(
id
tag
weight
id
tag
weight
);
use constant UPDATE_COLUMNS => qw(
weight
weight
);
use constant DB_TABLE => 'longdescs_tags_weights';
use constant ID_FIELD => 'id';
use constant NAME_FIELD => 'tag';
use constant LIST_ORDER => 'weight DESC';
use constant VALIDATORS => { };
use constant VALIDATORS => {};
# There's no gain to caching these objects
use constant USE_MEMCACHED => 0;
......
......@@ -16,32 +16,21 @@ use Bugzilla::Config::Common;
our $sortkey = 200;
sub get_param_list {
my $class = shift;
my $class = shift;
my @param_list = (
{
name => 'allowbugdeletion',
type => 'b',
default => 0
},
{
name => 'allowemailchange',
type => 'b',
default => 1
},
{
name => 'allowuserdeletion',
type => 'b',
default => 0
},
{
name => 'last_visit_keep_days',
type => 't',
default => 10,
checker => \&check_numeric
});
{name => 'allowbugdeletion', type => 'b', default => 0},
{name => 'allowemailchange', type => 'b', default => 1},
{name => 'allowuserdeletion', type => 'b', default => 0},
{
name => 'last_visit_keep_days',
type => 't',
default => 10,
checker => \&check_numeric
}
);
return @param_list;
}
......
......@@ -17,36 +17,32 @@ our $sortkey = 1700;
use constant get_param_list => (
{
name => 'inbound_proxies',
type => 't',
default => '',
checker => \&check_inbound_proxies
name => 'inbound_proxies',
type => 't',
default => '',
checker => \&check_inbound_proxies
},
{
name => 'proxy_url',
type => 't',
default => ''
},
{name => 'proxy_url', type => 't', default => ''},
{
name => 'strict_transport_security',
type => 's',
choices => ['off', 'this_domain_only', 'include_subdomains'],
default => 'off',
checker => \&check_multi
name => 'strict_transport_security',
type => 's',
choices => ['off', 'this_domain_only', 'include_subdomains'],
default => 'off',
checker => \&check_multi
},
);
sub check_inbound_proxies {
my $inbound_proxies = shift;
return "" if $inbound_proxies eq "*";
my @proxies = split(/[\s,]+/, $inbound_proxies);
foreach my $proxy (@proxies) {
validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address";
}
return "";
my $inbound_proxies = shift;
return "" if $inbound_proxies eq "*";
my @proxies = split(/[\s,]+/, $inbound_proxies);
foreach my $proxy (@proxies) {
validate_ip($proxy) || return "$proxy is not a valid IPv4 or IPv6 address";
}
return "";
}
1;
......@@ -16,56 +16,49 @@ use Bugzilla::Config::Common;
our $sortkey = 400;
sub get_param_list {
my $class = shift;
my $class = shift;
my @param_list = (
{
name => 'allow_attachment_display',
type => 'b',
default => 0
},
{name => 'allow_attachment_display', type => 'b', default => 0},
{
name => 'attachment_base',
type => 't',
default => '',
checker => \&check_urlbase
},
{
name => 'attachment_base',
type => 't',
default => '',
checker => \&check_urlbase
},
{
name => 'allow_attachment_deletion',
type => 'b',
default => 0
},
{name => 'allow_attachment_deletion', type => 'b', default => 0},
{
name => 'xsendfile_header',
type => 's',
choices => ['off', 'X-Sendfile', 'X-Accel-Redirect', 'X-LIGHTTPD-send-file'],
default => 'off',
checker => \&check_multi
},
{
name => 'xsendfile_header',
type => 's',
choices => ['off', 'X-Sendfile', 'X-Accel-Redirect', 'X-LIGHTTPD-send-file'],
default => 'off',
checker => \&check_multi
},
{
name => 'maxattachmentsize',
type => 't',
default => '1000',
checker => \&check_maxattachmentsize
},
{
name => 'maxattachmentsize',
type => 't',
default => '1000',
checker => \&check_maxattachmentsize
},
# The maximum size (in bytes) for patches and non-patch attachments.
# 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
# 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
# the attachment record as well as any mysql packet overhead (I don't
# know of any, but I suspect there may be some.)
# The maximum size (in bytes) for patches and non-patch attachments.
# 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
# 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
# the attachment record as well as any mysql packet overhead (I don't
# know of any, but I suspect there may be some.)
{
name => 'maxlocalattachment',
type => 't',
default => '0',
checker => \&check_numeric
} );
{
name => 'maxlocalattachment',
type => 't',
default => '0',
checker => \&check_numeric
}
);
return @param_list;
}
......
......@@ -16,111 +16,89 @@ use Bugzilla::Config::Common;
our $sortkey = 300;
sub get_param_list {
my $class = shift;
my $class = shift;
my @param_list = (
{
name => 'auth_env_id',
type => 't',
default => '',
},
{
name => 'auth_env_email',
type => 't',
default => '',
},
{
name => 'auth_env_realname',
type => 't',
default => '',
},
# XXX in the future:
#
# 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
# arrays, but that requires alterations to editparams first
{
name => 'user_info_class',
type => 's',
choices => [ 'CGI', 'Env', 'Env,CGI' ],
default => 'CGI',
checker => \&check_multi
},
{
name => 'user_verify_class',
type => 'o',
choices => [ 'DB', 'RADIUS', 'LDAP' ],
default => 'DB',
checker => \&check_user_verify_class
},
{
name => 'rememberlogin',
type => 's',
choices => ['on', 'defaulton', 'defaultoff', 'off'],
default => 'on',
checker => \&check_multi
},
{
name => 'requirelogin',
type => 'b',
default => '0'
},
{
name => 'emailregexp',
type => 't',
default => q:^[\\w\\.\\+\\-=']+@[\\w\\.\\-]+\\.[\\w\\-]+$:,
checker => \&check_regexp
},
{
name => 'emailregexpdesc',
type => 'l',
default => 'A legal address must contain exactly one \'@\', and at least ' .
'one \'.\' after the @.'
},
{
name => 'use_email_as_login',
type => 'b',
default => '1',
onchange => \&change_use_email_as_login
},
{
name => 'createemailregexp',
type => 't',
default => q:.*:,
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,
},
{name => 'auth_env_id', type => 't', default => '',},
{name => 'auth_env_email', type => 't', default => '',},
{name => 'auth_env_realname', type => 't', default => '',},
# XXX in the future:
#
# 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
# arrays, but that requires alterations to editparams first
{
name => 'user_info_class',
type => 's',
choices => ['CGI', 'Env', 'Env,CGI'],
default => 'CGI',
checker => \&check_multi
},
{
name => 'user_verify_class',
type => 'o',
choices => ['DB', 'RADIUS', 'LDAP'],
default => 'DB',
checker => \&check_user_verify_class
},
{
name => 'rememberlogin',
type => 's',
choices => ['on', 'defaulton', 'defaultoff', 'off'],
default => 'on',
checker => \&check_multi
},
{name => 'requirelogin', type => 'b', default => '0'},
{
name => 'emailregexp',
type => 't',
default => q:^[\\w\\.\\+\\-=']+@[\\w\\.\\-]+\\.[\\w\\-]+$:,
checker => \&check_regexp
},
{
name => 'emailregexpdesc',
type => 'l',
default => 'A legal address must contain exactly one \'@\', and at least '
. 'one \'.\' after the @.'
},
{
name => 'use_email_as_login',
type => 'b',
default => '1',
onchange => \&change_use_email_as_login
},
{
name => 'createemailregexp',
type => 't',
default => q:.*:,
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;
}
......
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