Commit 7f3a749d 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 3395d78c
-pbp # Start with Perl Best Practices
-w # Show all warnings
-iob # Ignore old breakpoints
-l=80 # 80 characters per line
-vmll
-ibc
-iscl
-hsc
-mbl=2 # No more than 2 blank lines
-i=2 # Indentation is 2 columns
-ci=2 # Continuation indentation is 2 columns
-vt=0 # Less vertical tightness
-pt=2 # High parenthesis tightness
-bt=2 # High brace tightness
-sbt=2 # High square bracket tightness
-wn # Weld nested containers
-isbc # Don't indent comments without leading space
...@@ -16,18 +16,18 @@ use fields qw(); ...@@ -16,18 +16,18 @@ use fields qw();
# Determines whether or not a user can logout. It's really a subroutine, # Determines whether or not a user can logout. It's really a subroutine,
# but we implement it here as a constant. Override it in subclasses if # but we implement it here as a constant. Override it in subclasses if
# that particular type of login method cannot log out. # that particular type of login method cannot log out.
use constant can_logout => 1; use constant can_logout => 1;
use constant can_login => 1; use constant can_login => 1;
use constant requires_persistence => 1; use constant requires_persistence => 1;
use constant requires_verification => 1; use constant requires_verification => 1;
use constant user_can_create_account => 0; use constant user_can_create_account => 0;
use constant is_automatic => 0; use constant is_automatic => 0;
use constant extern_id_used => 0; use constant extern_id_used => 0;
sub new { sub new {
my ($class) = @_; my ($class) = @_;
my $self = fields::new($class); my $self = fields::new($class);
return $self; return $self;
} }
1; 1;
......
...@@ -26,28 +26,29 @@ use constant can_logout => 0; ...@@ -26,28 +26,29 @@ use constant can_logout => 0;
# This method is only available to web services. An API key can never # This method is only available to web services. An API key can never
# be used to authenticate a Web request. # be used to authenticate a Web request.
sub get_login_info { sub get_login_info {
my ($self) = @_; my ($self) = @_;
my $params = Bugzilla->input_params; my $params = Bugzilla->input_params;
my ($user_id, $login_cookie); my ($user_id, $login_cookie);
my $api_key_text = trim(delete $params->{'Bugzilla_api_key'}); my $api_key_text = trim(delete $params->{'Bugzilla_api_key'});
if (!i_am_webservice() || !$api_key_text) { if (!i_am_webservice() || !$api_key_text) {
return { failure => AUTH_NODATA }; return {failure => AUTH_NODATA};
} }
my $api_key = Bugzilla::User::APIKey->new({ name => $api_key_text }); my $api_key = Bugzilla::User::APIKey->new({name => $api_key_text});
if (!$api_key or $api_key->api_key ne $api_key_text) { if (!$api_key or $api_key->api_key ne $api_key_text) {
# The second part checks the correct capitalisation. Silly MySQL
ThrowUserError("api_key_not_valid");
}
elsif ($api_key->revoked) {
ThrowUserError('api_key_revoked');
}
$api_key->update_last_used(); # The second part checks the correct capitalisation. Silly MySQL
ThrowUserError("api_key_not_valid");
}
elsif ($api_key->revoked) {
ThrowUserError('api_key_revoked');
}
return { user_id => $api_key->user_id }; $api_key->update_last_used();
return {user_id => $api_key->user_id};
} }
1; 1;
...@@ -21,65 +21,71 @@ use Bugzilla::Error; ...@@ -21,65 +21,71 @@ use Bugzilla::Error;
use Bugzilla::Token; use Bugzilla::Token;
sub get_login_info { sub get_login_info {
my ($self) = @_; my ($self) = @_;
my $params = Bugzilla->input_params; my $params = Bugzilla->input_params;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
my $login = trim(delete $params->{'Bugzilla_login'}); my $login = trim(delete $params->{'Bugzilla_login'});
my $password = delete $params->{'Bugzilla_password'}; my $password = delete $params->{'Bugzilla_password'};
# The token must match the cookie to authenticate the request.
my $login_token = delete $params->{'Bugzilla_login_token'}; # The token must match the cookie to authenticate the request.
my $login_cookie = $cgi->cookie('Bugzilla_login_request_cookie'); my $login_token = delete $params->{'Bugzilla_login_token'};
my $login_cookie = $cgi->cookie('Bugzilla_login_request_cookie');
my $valid = 0;
# If the web browser accepts cookies, use them. my $valid = 0;
if ($login_token && $login_cookie) {
my ($time, undef) = split(/-/, $login_token); # If the web browser accepts cookies, use them.
# Regenerate the token based on the information we have. if ($login_token && $login_cookie) {
my $expected_token = issue_hash_token(['login_request', $login_cookie], $time); my ($time, undef) = split(/-/, $login_token);
$valid = 1 if $expected_token eq $login_token;
$cgi->remove_cookie('Bugzilla_login_request_cookie'); # Regenerate the token based on the information we have.
} my $expected_token = issue_hash_token(['login_request', $login_cookie], $time);
# WebServices and other local scripts can bypass this check. $valid = 1 if $expected_token eq $login_token;
# This is safe because we won't store a login cookie in this case. $cgi->remove_cookie('Bugzilla_login_request_cookie');
elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) { }
$valid = 1;
} # WebServices and other local scripts can bypass this check.
# Else falls back to the Referer header and accept local URLs. # This is safe because we won't store a login cookie in this case.
# Attachments are served from a separate host (ideally), and so elsif (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
# an evil attachment cannot abuse this check with a redirect. $valid = 1;
elsif (my $referer = $cgi->referer) { }
my $urlbase = correct_urlbase();
$valid = 1 if $referer =~ /^\Q$urlbase\E/; # Else falls back to the Referer header and accept local URLs.
} # Attachments are served from a separate host (ideally), and so
# If the web browser doesn't accept cookies and the Referer header # an evil attachment cannot abuse this check with a redirect.
# is missing, we have no way to make sure that the authentication elsif (my $referer = $cgi->referer) {
# request comes from the user. my $urlbase = correct_urlbase();
elsif ($login && $password) { $valid = 1 if $referer =~ /^\Q$urlbase\E/;
ThrowUserError('auth_untrusted_request', { login => $login }); }
}
# If the web browser doesn't accept cookies and the Referer header
if (!defined($login) || !defined($password) || !$valid) { # is missing, we have no way to make sure that the authentication
return { failure => AUTH_NODATA }; # request comes from the user.
} elsif ($login && $password) {
ThrowUserError('auth_untrusted_request', {login => $login});
return { username => $login, password => $password }; }
if (!defined($login) || !defined($password) || !$valid) {
return {failure => AUTH_NODATA};
}
return {username => $login, password => $password};
} }
sub fail_nodata { sub fail_nodata {
my ($self) = @_; my ($self) = @_;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
my $template = Bugzilla->template; my $template = Bugzilla->template;
if (Bugzilla->usage_mode != USAGE_MODE_BROWSER) { if (Bugzilla->usage_mode != USAGE_MODE_BROWSER) {
ThrowUserError('login_required'); ThrowUserError('login_required');
} }
print $cgi->header(); print $cgi->header();
$template->process("account/auth/login.html.tmpl", $template->process("account/auth/login.html.tmpl",
{ 'target' => $cgi->url(-relative=>1) }) {'target' => $cgi->url(-relative => 1)})
|| ThrowTemplateError($template->error()); || ThrowTemplateError($template->error());
exit; exit;
} }
1; 1;
...@@ -23,121 +23,124 @@ use List::Util qw(first); ...@@ -23,121 +23,124 @@ use List::Util qw(first);
use constant requires_persistence => 0; use constant requires_persistence => 0;
use constant requires_verification => 0; use constant requires_verification => 0;
use constant can_login => 0; use constant can_login => 0;
sub is_automatic { return $_[0]->login_token ? 0 : 1; } sub is_automatic { return $_[0]->login_token ? 0 : 1; }
# Note that Cookie never consults the Verifier, it always assumes # Note that Cookie never consults the Verifier, it always assumes
# it has a valid DB account or it fails. # it has a valid DB account or it fails.
sub get_login_info { sub get_login_info {
my ($self) = @_; my ($self) = @_;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my ($user_id, $login_cookie); my ($user_id, $login_cookie);
if (!Bugzilla->request_cache->{auth_no_automatic_login}) { if (!Bugzilla->request_cache->{auth_no_automatic_login}) {
$login_cookie = $cgi->cookie("Bugzilla_logincookie"); $login_cookie = $cgi->cookie("Bugzilla_logincookie");
$user_id = $cgi->cookie("Bugzilla_login"); $user_id = $cgi->cookie("Bugzilla_login");
# If cookies cannot be found, this could mean that they haven't # If cookies cannot be found, this could mean that they haven't
# been made available yet. In this case, look at Bugzilla_cookie_list. # been made available yet. In this case, look at Bugzilla_cookie_list.
unless ($login_cookie) { unless ($login_cookie) {
my $cookie = first {$_->name eq 'Bugzilla_logincookie'} my $cookie = first { $_->name eq 'Bugzilla_logincookie' }
@{$cgi->{'Bugzilla_cookie_list'}}; @{$cgi->{'Bugzilla_cookie_list'}};
$login_cookie = $cookie->value if $cookie; $login_cookie = $cookie->value if $cookie;
} }
unless ($user_id) { unless ($user_id) {
my $cookie = first {$_->name eq 'Bugzilla_login'} my $cookie = first { $_->name eq 'Bugzilla_login' }
@{$cgi->{'Bugzilla_cookie_list'}}; @{$cgi->{'Bugzilla_cookie_list'}};
$user_id = $cookie->value if $cookie; $user_id = $cookie->value if $cookie;
}
# If the call is for a web service, and an api token is provided, check
# it is valid.
if (i_am_webservice() && 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 });
}
}
} }
# If no cookies were provided, we also look for a login token # If the call is for a web service, and an api token is provided, check
# passed in the parameters of a webservice # it is valid.
my $token = $self->login_token; if (i_am_webservice() && Bugzilla->input_params->{Bugzilla_api_token}) {
if ($token && (!$login_cookie || !$user_id)) { my $api_token = Bugzilla->input_params->{Bugzilla_api_token};
($user_id, $login_cookie) = ($token->{'user_id'}, $token->{'login_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});
}
} }
}
# 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);
my $db_cookie = # Anything goes for these params - they're just strings which
$dbh->selectrow_array('SELECT cookie # 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
FROM logincookies FROM logincookies
WHERE cookie = ? WHERE cookie = ?
AND userid = ? AND userid = ?
AND (ipaddr = ? OR ipaddr IS NULL)', AND (ipaddr = ? OR ipaddr IS NULL)', undef,
undef, ($login_cookie, $user_id, $ip_addr)); ($login_cookie, $user_id, $ip_addr)
);
# If the cookie or token is valid, return a valid username.
# If they were not valid and we are using a webservice, then # If the cookie or token is valid, return a valid username.
# throw an error notifying the client. # If they were not valid and we are using a webservice, then
if (defined $db_cookie && $login_cookie eq $db_cookie) { # throw an error notifying the client.
# If we logged in successfully, then update the lastused if (defined $db_cookie && $login_cookie eq $db_cookie) {
# time on the login cookie
$dbh->do("UPDATE logincookies SET lastused = NOW() # If we logged in successfully, then update the lastused
WHERE cookie = ?", undef, $login_cookie); # time on the login cookie
return { user_id => $user_id }; $dbh->do(
} "UPDATE logincookies SET lastused = NOW()
elsif (i_am_webservice()) { WHERE cookie = ?", undef, $login_cookie
ThrowUserError('invalid_cookies_or_token'); );
} return {user_id => $user_id};
} }
elsif (i_am_webservice()) {
# Either the cookie or token is invalid and we are not authenticating ThrowUserError('invalid_cookies_or_token');
# via a webservice, or we did not receive a cookie or token. We don't }
# want to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to }
# actually throw an error when it gets a bad cookie or token. It should just
# look like there was no cookie or token to begin with. # Either the cookie or token is invalid and we are not authenticating
return { failure => AUTH_NODATA }; # via a webservice, or we did not receive a cookie or token. We don't
# want to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to
# actually throw an error when it gets a bad cookie or token. It should just
# look like there was no cookie or token to begin with.
return {failure => AUTH_NODATA};
} }
sub login_token { sub login_token {
my ($self) = @_; my ($self) = @_;
my $input = Bugzilla->input_params; my $input = Bugzilla->input_params;
my $usage_mode = Bugzilla->usage_mode; my $usage_mode = Bugzilla->usage_mode;
return $self->{'_login_token'} if exists $self->{'_login_token'}; return $self->{'_login_token'} if exists $self->{'_login_token'};
if (!i_am_webservice()) { if (!i_am_webservice()) {
return $self->{'_login_token'} = undef; return $self->{'_login_token'} = undef;
} }
# Check if a token was passed in via requests for WebServices # Check if a token was passed in via requests for WebServices
my $token = trim(delete $input->{'Bugzilla_token'}); my $token = trim(delete $input->{'Bugzilla_token'});
return $self->{'_login_token'} = undef if !$token; return $self->{'_login_token'} = undef if !$token;
my ($user_id, $login_token) = split('-', $token, 2); my ($user_id, $login_token) = split('-', $token, 2);
if (!detaint_natural($user_id) || !$login_token) { if (!detaint_natural($user_id) || !$login_token) {
return $self->{'_login_token'} = undef; return $self->{'_login_token'} = undef;
} }
return $self->{'_login_token'} = { return $self->{'_login_token'}
user_id => $user_id, = {user_id => $user_id, login_token => $login_token};
login_token => $login_token
};
} }
1; 1;
...@@ -16,28 +16,31 @@ use parent qw(Bugzilla::Auth::Login); ...@@ -16,28 +16,31 @@ use parent qw(Bugzilla::Auth::Login);
use Bugzilla::Constants; use Bugzilla::Constants;
use Bugzilla::Error; use Bugzilla::Error;
use constant can_logout => 0; use constant can_logout => 0;
use constant can_login => 0; use constant can_login => 0;
use constant requires_persistence => 0; use constant requires_persistence => 0;
use constant requires_verification => 0; use constant requires_verification => 0;
use constant is_automatic => 1; use constant is_automatic => 1;
use constant extern_id_used => 1; use constant extern_id_used => 1;
sub get_login_info { sub get_login_info {
my ($self) = @_; my ($self) = @_;
my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || ''; my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || '';
my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || ''; my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || '';
my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || ''; my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || '';
return { failure => AUTH_NODATA } if !$env_email; return {failure => AUTH_NODATA} if !$env_email;
return { username => $env_email, extern_id => $env_id, return {
realname => $env_realname }; username => $env_email,
extern_id => $env_id,
realname => $env_realname
};
} }
sub fail_nodata { sub fail_nodata {
ThrowCodeError('env_no_email'); ThrowCodeError('env_no_email');
} }
1; 1;
...@@ -13,8 +13,8 @@ use warnings; ...@@ -13,8 +13,8 @@ use warnings;
use base qw(Bugzilla::Auth::Login); use base qw(Bugzilla::Auth::Login);
use fields qw( use fields qw(
_stack _stack
successful successful
); );
use Hash::Util qw(lock_keys); use Hash::Util qw(lock_keys);
use Bugzilla::Hook; use Bugzilla::Hook;
...@@ -22,81 +22,87 @@ use Bugzilla::Constants; ...@@ -22,81 +22,87 @@ use Bugzilla::Constants;
use List::MoreUtils qw(any); use List::MoreUtils qw(any);
sub new { sub new {
my $class = shift; my $class = shift;
my $self = $class->SUPER::new(@_); my $self = $class->SUPER::new(@_);
my $list = shift; my $list = shift;
my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list); my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
lock_keys(%methods); lock_keys(%methods);
Bugzilla::Hook::process('auth_login_methods', { modules => \%methods }); Bugzilla::Hook::process('auth_login_methods', {modules => \%methods});
$self->{_stack} = []; $self->{_stack} = [];
foreach my $login_method (split(',', $list)) { foreach my $login_method (split(',', $list)) {
my $module = $methods{$login_method}; my $module = $methods{$login_method};
require $module; require $module;
$module =~ s|/|::|g; $module =~ s|/|::|g;
$module =~ s/.pm$//; $module =~ s/.pm$//;
push(@{$self->{_stack}}, $module->new(@_)); push(@{$self->{_stack}}, $module->new(@_));
} }
return $self; return $self;
} }
sub get_login_info { sub get_login_info {
my $self = shift; my $self = shift;
my $result; my $result;
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
# See Bugzilla::WebService::Server::JSONRPC for where and why
# auth_no_automatic_login is used. # See Bugzilla::WebService::Server::JSONRPC for where and why
if (Bugzilla->request_cache->{auth_no_automatic_login}) { # auth_no_automatic_login is used.
next if $object->is_automatic; if (Bugzilla->request_cache->{auth_no_automatic_login}) {
} next if $object->is_automatic;
$result = $object->get_login_info(@_);
$self->{successful} = $object;
# We only carry on down the stack if this method denied all knowledge.
last unless ($result->{failure}
&& ($result->{failure} eq AUTH_NODATA
|| $result->{failure} eq AUTH_NO_SUCH_USER));
# If none of the methods succeed, it's undef.
$self->{successful} = undef;
} }
return $result; $result = $object->get_login_info(@_);
$self->{successful} = $object;
# We only carry on down the stack if this method denied all knowledge.
last
unless ($result->{failure}
&& ( $result->{failure} eq AUTH_NODATA
|| $result->{failure} eq AUTH_NO_SUCH_USER));
# If none of the methods succeed, it's undef.
$self->{successful} = undef;
}
return $result;
} }
sub fail_nodata { sub fail_nodata {
my $self = shift; my $self = shift;
# We fail from the bottom of the stack.
my @reverse_stack = reverse @{$self->{_stack}}; # We fail from the bottom of the stack.
foreach my $object (@reverse_stack) { my @reverse_stack = reverse @{$self->{_stack}};
# We pick the first object that actually has the method foreach my $object (@reverse_stack) {
# implemented.
if ($object->can('fail_nodata')) { # We pick the first object that actually has the method
$object->fail_nodata(@_); # implemented.
} if ($object->can('fail_nodata')) {
$object->fail_nodata(@_);
} }
}
} }
sub can_login { sub can_login {
my ($self) = @_; my ($self) = @_;
# We return true if any method can log in.
foreach my $object (@{$self->{_stack}}) { # We return true if any method can log in.
return 1 if $object->can_login; foreach my $object (@{$self->{_stack}}) {
} return 1 if $object->can_login;
return 0; }
return 0;
} }
sub user_can_create_account { sub user_can_create_account {
my ($self) = @_; my ($self) = @_;
# We return true if any method allows users to create accounts.
foreach my $object (@{$self->{_stack}}) { # We return true if any method allows users to create accounts.
return 1 if $object->user_can_create_account; foreach my $object (@{$self->{_stack}}) {
} return 1 if $object->user_can_create_account;
return 0; }
return 0;
} }
sub extern_id_used { sub extern_id_used {
my ($self) = @_; my ($self) = @_;
return any { $_->extern_id_used } @{ $self->{_stack} }; return any { $_->extern_id_used } @{$self->{_stack}};
} }
1; 1;
...@@ -20,145 +20,154 @@ use Bugzilla::Token; ...@@ -20,145 +20,154 @@ use Bugzilla::Token;
use List::Util qw(first); use List::Util qw(first);
sub new { sub new {
my ($class) = @_; my ($class) = @_;
my $self = fields::new($class); my $self = fields::new($class);
return $self; return $self;
} }
sub persist_login { sub persist_login {
my ($self, $user) = @_; my ($self, $user) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
my $input_params = Bugzilla->input_params; my $input_params = Bugzilla->input_params;
my $ip_addr; my $ip_addr;
if ($input_params->{'Bugzilla_restrictlogin'}) { if ($input_params->{'Bugzilla_restrictlogin'}) {
$ip_addr = remote_ip(); $ip_addr = remote_ip();
# The IP address is valid, at least for comparing with itself in a
# subsequent login # The IP address is valid, at least for comparing with itself in a
trick_taint($ip_addr); # subsequent login
} trick_taint($ip_addr);
}
$dbh->bz_start_transaction();
$dbh->bz_start_transaction();
my $login_cookie =
Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie'); my $login_cookie
= Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
$dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
VALUES (?, ?, ?, NOW())", $dbh->do(
undef, $login_cookie, $user->id, $ip_addr); "INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
VALUES (?, ?, ?, NOW())", undef, $login_cookie, $user->id, $ip_addr
# Issuing a new cookie is a good time to clean up the old );
# cookies.
$dbh->do("DELETE FROM logincookies WHERE lastused < " # Issuing a new cookie is a good time to clean up the old
. $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', # cookies.
MAX_LOGINCOOKIE_AGE, 'DAY')); $dbh->do("DELETE FROM logincookies WHERE lastused < "
. $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', MAX_LOGINCOOKIE_AGE, 'DAY'));
$dbh->bz_commit_transaction();
$dbh->bz_commit_transaction();
# We do not want WebServices to generate login cookies.
# All we need is the login token for User.login. # We do not want WebServices to generate login cookies.
return $login_cookie if i_am_webservice(); # All we need is the login token for User.login.
return $login_cookie if i_am_webservice();
# Prevent JavaScript from accessing login cookies.
my %cookieargs = ('-httponly' => 1); # Prevent JavaScript from accessing login cookies.
my %cookieargs = ('-httponly' => 1);
# Remember cookie only if admin has told so
# or admin didn't forbid it and user told to remember. # Remember cookie only if admin has told so
if ( Bugzilla->params->{'rememberlogin'} eq 'on' || # or admin didn't forbid it and user told to remember.
(Bugzilla->params->{'rememberlogin'} ne 'off' && if (
$input_params->{'Bugzilla_remember'} && Bugzilla->params->{'rememberlogin'} eq 'on'
$input_params->{'Bugzilla_remember'} eq 'on') ) || ( Bugzilla->params->{'rememberlogin'} ne 'off'
{ && $input_params->{'Bugzilla_remember'}
# Not a session cookie, so set an infinite expiry && $input_params->{'Bugzilla_remember'} eq 'on')
$cookieargs{'-expires'} = 'Fri, 01-Jan-2038 00:00:00 GMT'; )
} {
if (Bugzilla->params->{'ssl_redirect'}) { # Not a session cookie, so set an infinite expiry
# Make these cookies only be sent to us by the browser during $cookieargs{'-expires'} = 'Fri, 01-Jan-2038 00:00:00 GMT';
# HTTPS sessions, if we're using SSL. }
$cookieargs{'-secure'} = 1; if (Bugzilla->params->{'ssl_redirect'}) {
}
# Make these cookies only be sent to us by the browser during
$cgi->send_cookie(-name => 'Bugzilla_login', # HTTPS sessions, if we're using SSL.
-value => $user->id, $cookieargs{'-secure'} = 1;
%cookieargs); }
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
-value => $login_cookie, $cgi->send_cookie(-name => 'Bugzilla_login', -value => $user->id, %cookieargs);
%cookieargs); $cgi->send_cookie(
-name => 'Bugzilla_logincookie',
-value => $login_cookie,
%cookieargs
);
} }
sub logout { sub logout {
my ($self, $param) = @_; my ($self, $param) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
my $input = Bugzilla->input_params; my $input = Bugzilla->input_params;
$param = {} unless $param; $param = {} unless $param;
my $user = $param->{user} || Bugzilla->user; my $user = $param->{user} || Bugzilla->user;
my $type = $param->{type} || LOGOUT_ALL; my $type = $param->{type} || LOGOUT_ALL;
if ($type == LOGOUT_ALL) { if ($type == LOGOUT_ALL) {
$dbh->do("DELETE FROM logincookies WHERE userid = ?", $dbh->do("DELETE FROM logincookies WHERE userid = ?", undef, $user->id);
undef, $user->id); return;
return; }
}
# The LOGOUT_*_CURRENT options require the current login cookie.
# The LOGOUT_*_CURRENT options require the current login cookie. # If a new cookie has been issued during this run, that's the current one.
# If a new cookie has been issued during this run, that's the current one. # If not, it's the one we've received.
# If not, it's the one we've received. my @login_cookies;
my @login_cookies; my $cookie = first { $_->name eq 'Bugzilla_logincookie' }
my $cookie = first {$_->name eq 'Bugzilla_logincookie'} @{$cgi->{'Bugzilla_cookie_list'}};
@{$cgi->{'Bugzilla_cookie_list'}}; if ($cookie) {
if ($cookie) { push(@login_cookies, $cookie->value);
push(@login_cookies, $cookie->value); }
} elsif ($cookie = $cgi->cookie('Bugzilla_logincookie')) {
elsif ($cookie = $cgi->cookie('Bugzilla_logincookie')) { push(@login_cookies, $cookie);
push(@login_cookies, $cookie); }
}
# If we are a webservice using a token instead of cookie
# If we are a webservice using a token instead of cookie # then add that as well to the login cookies to delete
# then add that as well to the login cookies to delete if (my $login_token = $user->authorizer->login_token) {
if (my $login_token = $user->authorizer->login_token) { push(@login_cookies, $login_token->{'login_token'});
push(@login_cookies, $login_token->{'login_token'}); }
}
# Make sure that @login_cookies is not empty to not break SQL statements.
# Make sure that @login_cookies is not empty to not break SQL statements. push(@login_cookies, '') unless @login_cookies;
push(@login_cookies, '') unless @login_cookies;
# These queries use both the cookie ID and the user ID as keys. Even
# These queries use both the cookie ID and the user ID as keys. Even # though we know the userid must match, we still check it in the SQL
# though we know the userid must match, we still check it in the SQL # as a sanity check, since there is no locking here, and if the user
# as a sanity check, since there is no locking here, and if the user # logged out from two machines simultaneously, while someone else
# logged out from two machines simultaneously, while someone else # logged in and got the same cookie, we could be logging the other
# logged in and got the same cookie, we could be logging the other # user out here. Yes, this is very very very unlikely, but why take
# user out here. Yes, this is very very very unlikely, but why take # chances? - bbaetz
# chances? - bbaetz map { trick_taint($_) } @login_cookies;
map { trick_taint($_) } @login_cookies; @login_cookies = map { $dbh->quote($_) } @login_cookies;
@login_cookies = map { $dbh->quote($_) } @login_cookies; if ($type == LOGOUT_KEEP_CURRENT) {
if ($type == LOGOUT_KEEP_CURRENT) { $dbh->do(
$dbh->do("DELETE FROM logincookies WHERE " . "DELETE FROM logincookies WHERE "
$dbh->sql_in('cookie', \@login_cookies, 1) . . $dbh->sql_in('cookie', \@login_cookies, 1)
" AND userid = ?", . " AND userid = ?",
undef, $user->id); undef, $user->id
} elsif ($type == LOGOUT_CURRENT) { );
$dbh->do("DELETE FROM logincookies WHERE " . }
$dbh->sql_in('cookie', \@login_cookies) . elsif ($type == LOGOUT_CURRENT) {
" AND userid = ?", $dbh->do(
undef, $user->id); "DELETE FROM logincookies WHERE "
} else { . $dbh->sql_in('cookie', \@login_cookies)
die("Invalid type $type supplied to logout()"); . " AND userid = ?",
} undef, $user->id
);
if ($type != LOGOUT_KEEP_CURRENT) { }
clear_browser_cookies(); else {
} die("Invalid type $type supplied to logout()");
}
if ($type != LOGOUT_KEEP_CURRENT) {
clear_browser_cookies();
}
} }
sub clear_browser_cookies { sub clear_browser_cookies {
my $cgi = Bugzilla->cgi; my $cgi = Bugzilla->cgi;
$cgi->remove_cookie('Bugzilla_login'); $cgi->remove_cookie('Bugzilla_login');
$cgi->remove_cookie('Bugzilla_logincookie'); $cgi->remove_cookie('Bugzilla_logincookie');
$cgi->remove_cookie('sudo'); $cgi->remove_cookie('sudo');
} }
1; 1;
...@@ -19,113 +19,127 @@ use Bugzilla::User; ...@@ -19,113 +19,127 @@ use Bugzilla::User;
use Bugzilla::Util; use Bugzilla::Util;
use constant user_can_create_account => 1; use constant user_can_create_account => 1;
use constant extern_id_used => 0; use constant extern_id_used => 0;
sub new { sub new {
my ($class, $login_type) = @_; my ($class, $login_type) = @_;
my $self = fields::new($class); my $self = fields::new($class);
return $self; return $self;
} }
sub can_change_password { sub can_change_password {
return $_[0]->can('change_password'); return $_[0]->can('change_password');
} }
sub create_or_update_user { sub create_or_update_user {
my ($self, $params) = @_; my ($self, $params) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $extern_id = $params->{extern_id}; my $extern_id = $params->{extern_id};
my $username = $params->{bz_username} || $params->{username}; my $username = $params->{bz_username} || $params->{username};
my $password = $params->{password} || '*'; my $password = $params->{password} || '*';
my $real_name = $params->{realname} || ''; my $real_name = $params->{realname} || '';
my $user_id = $params->{user_id}; my $user_id = $params->{user_id};
# A passed-in user_id always overrides anything else, for determining # A passed-in user_id always overrides anything else, for determining
# what account we should return. # what account we should return.
if (!$user_id) { if (!$user_id) {
my $username_user_id = login_to_id($username || ''); my $username_user_id = login_to_id($username || '');
my $extern_user_id; my $extern_user_id;
if ($extern_id) { if ($extern_id) {
trick_taint($extern_id); trick_taint($extern_id);
$extern_user_id = $dbh->selectrow_array('SELECT userid $extern_user_id = $dbh->selectrow_array(
FROM profiles WHERE extern_id = ?', undef, $extern_id); 'SELECT userid
} FROM profiles WHERE extern_id = ?', undef, $extern_id
);
# If we have both a valid extern_id and a valid username, and they are
# not the same id, then we have a conflict.
if ($username_user_id && $extern_user_id
&& $username_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 => $username} };
}
# If we have a valid username, but no valid id,
# then we have to create the user. This happens when we're
# passed only a username, and that username doesn't exist already.
if ($username && !$username_user_id && !$extern_user_id) {
validate_email_syntax($username)
|| return { failure => AUTH_ERROR,
error => 'auth_invalid_email',
details => {addr => $username} };
# 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 => $username,
cryptpassword => $password,
realname => $real_name});
$username_user_id = $user->id;
}
# If we have a valid username id and an extern_id, but no valid
# extern_user_id, then we have to set the user's extern_id.
if ($extern_id && $username_user_id && !$extern_user_id) {
$dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
undef, $extern_id, $username_user_id);
Bugzilla->memcached->clear({ table => 'profiles', id => $username_user_id });
}
# Finally, at this point, one of these will give us a valid user id.
$user_id = $extern_user_id || $username_user_id;
} }
# If we still don't have a valid user_id, then we weren't passed # If we have both a valid extern_id and a valid username, and they are
# enough information in $params, and we should die right here. # not the same id, then we have a conflict.
ThrowCodeError('bad_arg', {argument => 'params', function => if ( $username_user_id
'Bugzilla::Auth::Verify::create_or_update_user'}) && $extern_user_id
unless $user_id; && $username_user_id ne $extern_user_id)
{
my $user = new Bugzilla::User($user_id); my $extern_name = Bugzilla::User->new($extern_user_id)->login;
return {
# Now that we have a valid User, we need to see if any data has to be updated. failure => AUTH_ERROR,
my $changed = 0; error => "extern_id_conflict",
details =>
{extern_id => $extern_id, extern_user => $extern_name, username => $username}
};
}
if ($username && lc($user->login) ne lc($username)) { # If we have a valid username, but no valid id,
validate_email_syntax($username) # then we have to create the user. This happens when we're
|| return { failure => AUTH_ERROR, error => 'auth_invalid_email', # passed only a username, and that username doesn't exist already.
details => {addr => $username} }; if ($username && !$username_user_id && !$extern_user_id) {
$user->set_login($username); validate_email_syntax($username) || return {
$changed = 1; failure => AUTH_ERROR,
error => 'auth_invalid_email',
details => {addr => $username}
};
# 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 => $username, cryptpassword => $password, realname => $real_name
});
$username_user_id = $user->id;
} }
if ($real_name && $user->name ne $real_name) {
# $real_name is more than likely tainted, but we only use it # If we have a valid username id and an extern_id, but no valid
# in a placeholder and we never use it after this. # extern_user_id, then we have to set the user's extern_id.
trick_taint($real_name); if ($extern_id && $username_user_id && !$extern_user_id) {
$user->set_name($real_name); $dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
$changed = 1; undef, $extern_id, $username_user_id);
Bugzilla->memcached->clear({table => 'profiles', id => $username_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 || $username_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 ($username && lc($user->login) ne lc($username)) {
validate_email_syntax($username) || return {
failure => AUTH_ERROR,
error => 'auth_invalid_email',
details => {addr => $username}
};
$user->set_login($username);
$changed = 1;
}
if ($real_name && $user->name ne $real_name) {
# $real_name is more than likely tainted, but we only use it
# in a placeholder and we never use it after this.
trick_taint($real_name);
$user->set_name($real_name);
$changed = 1;
}
$user->update() if $changed;
return {user => $user};
} }
1; 1;
......
...@@ -19,95 +19,97 @@ use Bugzilla::Util; ...@@ -19,95 +19,97 @@ use Bugzilla::Util;
use Bugzilla::User; use Bugzilla::User;
sub check_credentials { sub check_credentials {
my ($self, $login_data) = @_; my ($self, $login_data) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $username = $login_data->{username}; my $username = $login_data->{username};
my $user = new Bugzilla::User({ name => $username }); my $user = new Bugzilla::User({name => $username});
return { failure => AUTH_NO_SUCH_USER } unless $user; return {failure => AUTH_NO_SUCH_USER} unless $user;
$login_data->{user} = $user; $login_data->{user} = $user;
$login_data->{bz_username} = $user->login; $login_data->{bz_username} = $user->login;
if ($user->account_is_locked_out) {
return {failure => AUTH_LOCKOUT, user => $user};
}
my $password = $login_data->{password};
my $real_password_crypted = $user->cryptpassword;
# Using the internal crypted password as the salt,
# crypt the password the user entered.
my $entered_password_crypted = bz_crypt($password, $real_password_crypted);
if ($entered_password_crypted ne $real_password_crypted) {
# Record the login failure
$user->note_login_failure();
# Immediately check if we are locked out
if ($user->account_is_locked_out) { if ($user->account_is_locked_out) {
return { failure => AUTH_LOCKOUT, user => $user }; return {failure => AUTH_LOCKOUT, user => $user, just_locked_out => 1};
} }
my $password = $login_data->{password}; return {
my $real_password_crypted = $user->cryptpassword; failure => AUTH_LOGINFAILED,
failure_count => scalar(@{$user->account_ip_login_failures}),
# Using the internal crypted password as the salt, };
# crypt the password the user entered. }
my $entered_password_crypted = bz_crypt($password, $real_password_crypted);
# Force the user to change their password if it does not meet the current
if ($entered_password_crypted ne $real_password_crypted) { # criteria. This should usually only happen if the criteria has changed.
# Record the login failure if ( Bugzilla->usage_mode == USAGE_MODE_BROWSER
$user->note_login_failure(); && Bugzilla->params->{password_check_on_login})
{
# Immediately check if we are locked out my $check = validate_password_check($password);
if ($user->account_is_locked_out) { if ($check) {
return { failure => AUTH_LOCKOUT, user => $user, return {
just_locked_out => 1 }; failure => AUTH_ERROR,
} user_error => $check,
details => {locked_user => $user}
return { failure => AUTH_LOGINFAILED, };
failure_count => scalar(@{ $user->account_ip_login_failures }),
};
}
# Force the user to change their password if it does not meet the current
# criteria. This should usually only happen if the criteria has changed.
if (Bugzilla->usage_mode == USAGE_MODE_BROWSER &&
Bugzilla->params->{password_check_on_login})
{
my $check = validate_password_check($password);
if ($check) {
return {
failure => AUTH_ERROR,
user_error => $check,
details => { locked_user => $user }
}
}
} }
}
# The user's credentials are okay, so delete any outstanding # The user's credentials are okay, so delete any outstanding
# password tokens or login failures they may have generated. # password tokens or login failures they may have generated.
Bugzilla::Token::DeletePasswordTokens($user->id, "user_logged_in"); Bugzilla::Token::DeletePasswordTokens($user->id, "user_logged_in");
$user->clear_login_failures(); $user->clear_login_failures();
my $update_password = 0; my $update_password = 0;
# If their old password was using crypt() or some different hash # If their old password was using crypt() or some different hash
# than we're using now, convert the stored password to using # than we're using now, convert the stored password to using
# whatever hashing system we're using now. # whatever hashing system we're using now.
my $current_algorithm = PASSWORD_DIGEST_ALGORITHM; my $current_algorithm = PASSWORD_DIGEST_ALGORITHM;
$update_password = 1 if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/); $update_password = 1 if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/);
# If their old password was using a different length salt than what # If their old password was using a different length salt than what
# we're using now, update the password to use the new salt length. # we're using now, update the password to use the new salt length.
if ($real_password_crypted =~ /^([^,]+),/) { if ($real_password_crypted =~ /^([^,]+),/) {
$update_password = 1 if (length($1) != PASSWORD_SALT_LENGTH); $update_password = 1 if (length($1) != PASSWORD_SALT_LENGTH);
} }
# If needed, update the user's password. # If needed, update the user's password.
if ($update_password) { if ($update_password) {
# We can't call $user->set_password because we don't want the password
# complexity rules to apply here. # We can't call $user->set_password because we don't want the password
$user->{cryptpassword} = bz_crypt($password); # complexity rules to apply here.
$user->update(); $user->{cryptpassword} = bz_crypt($password);
} $user->update();
}
return $login_data; return $login_data;
} }
sub change_password { sub change_password {
my ($self, $user, $password) = @_; my ($self, $user, $password) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $cryptpassword = bz_crypt($password); my $cryptpassword = bz_crypt($password);
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?", $dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
undef, $cryptpassword, $user->id); undef, $cryptpassword, $user->id);
Bugzilla->memcached->clear({ table => 'profiles', id => $user->id }); Bugzilla->memcached->clear({table => 'profiles', id => $user->id});
} }
1; 1;
...@@ -23,33 +23,37 @@ use constant admin_can_create_account => 0; ...@@ -23,33 +23,37 @@ use constant admin_can_create_account => 0;
use constant user_can_create_account => 0; use constant user_can_create_account => 0;
sub check_credentials { sub check_credentials {
my ($self, $params) = @_; my ($self, $params) = @_;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'}; my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'};
my $username = $params->{username}; my $username = $params->{username};
# If we're using RADIUS_email_suffix, we may need to cut it off from # If we're using RADIUS_email_suffix, we may need to cut it off from
# the login name. # the login name.
if ($address_suffix) { if ($address_suffix) {
$username =~ s/\Q$address_suffix\E$//i; $username =~ s/\Q$address_suffix\E$//i;
} }
# Create RADIUS object. # Create RADIUS object.
my $radius = my $radius = new Authen::Radius(
new Authen::Radius(Host => Bugzilla->params->{'RADIUS_server'}, Host => Bugzilla->params->{'RADIUS_server'},
Secret => Bugzilla->params->{'RADIUS_secret'}) Secret => Bugzilla->params->{'RADIUS_secret'}
|| return { failure => AUTH_ERROR, error => 'radius_preparation_error', )
details => {errstr => Authen::Radius::strerror() } }; || return {
failure => AUTH_ERROR,
# Check the password. error => 'radius_preparation_error',
$radius->check_pwd($username, $params->{password}, details => {errstr => Authen::Radius::strerror()}
Bugzilla->params->{'RADIUS_NAS_IP'} || undef) };
|| return { failure => AUTH_LOGINFAILED };
# Check the password.
# Build the user account's e-mail address. $radius->check_pwd($username, $params->{password},
$params->{bz_username} = $username . $address_suffix; Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
|| return {failure => AUTH_LOGINFAILED};
return $params;
# Build the user account's e-mail address.
$params->{bz_username} = $username . $address_suffix;
return $params;
} }
1; 1;
...@@ -13,8 +13,8 @@ use warnings; ...@@ -13,8 +13,8 @@ use warnings;
use base qw(Bugzilla::Auth::Verify); use base qw(Bugzilla::Auth::Verify);
use fields qw( use fields qw(
_stack _stack
successful successful
); );
use Bugzilla::Hook; use Bugzilla::Hook;
...@@ -23,70 +23,75 @@ use Hash::Util qw(lock_keys); ...@@ -23,70 +23,75 @@ use Hash::Util qw(lock_keys);
use List::MoreUtils qw(any); use List::MoreUtils qw(any);
sub new { sub new {
my $class = shift; my $class = shift;
my $list = shift; my $list = shift;
my $self = $class->SUPER::new(@_); my $self = $class->SUPER::new(@_);
my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list); my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list);
lock_keys(%methods); lock_keys(%methods);
Bugzilla::Hook::process('auth_verify_methods', { modules => \%methods }); Bugzilla::Hook::process('auth_verify_methods', {modules => \%methods});
$self->{_stack} = []; $self->{_stack} = [];
foreach my $verify_method (split(',', $list)) { foreach my $verify_method (split(',', $list)) {
my $module = $methods{$verify_method}; my $module = $methods{$verify_method};
require $module; require $module;
$module =~ s|/|::|g; $module =~ s|/|::|g;
$module =~ s/.pm$//; $module =~ s/.pm$//;
push(@{$self->{_stack}}, $module->new(@_)); push(@{$self->{_stack}}, $module->new(@_));
} }
return $self; return $self;
} }
sub can_change_password { sub can_change_password {
my ($self) = @_; my ($self) = @_;
# We return true if any method can change passwords.
foreach my $object (@{$self->{_stack}}) { # We return true if any method can change passwords.
return 1 if $object->can_change_password; foreach my $object (@{$self->{_stack}}) {
} return 1 if $object->can_change_password;
return 0; }
return 0;
} }
sub check_credentials { sub check_credentials {
my $self = shift; my $self = shift;
my $result; my $result;
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
$result = $object->check_credentials(@_); $result = $object->check_credentials(@_);
$self->{successful} = $object; $self->{successful} = $object;
last if !$result->{failure}; last if !$result->{failure};
# So that if none of them succeed, it's undef.
$self->{successful} = undef; # So that if none of them succeed, it's undef.
} $self->{successful} = undef;
# Returns the result at the bottom of the stack if they all fail. }
return $result;
# Returns the result at the bottom of the stack if they all fail.
return $result;
} }
sub create_or_update_user { sub create_or_update_user {
my $self = shift; my $self = shift;
my $result; my $result;
foreach my $object (@{$self->{_stack}}) { foreach my $object (@{$self->{_stack}}) {
$result = $object->create_or_update_user(@_); $result = $object->create_or_update_user(@_);
last if !$result->{failure}; last if !$result->{failure};
} }
# Returns the result at the bottom of the stack if they all fail.
return $result; # Returns the result at the bottom of the stack if they all fail.
return $result;
} }
sub user_can_create_account { sub user_can_create_account {
my ($self) = @_; my ($self) = @_;
# We return true if any method allows the user to create an account.
foreach my $object (@{$self->{_stack}}) { # We return true if any method allows the user to create an account.
return 1 if $object->user_can_create_account; foreach my $object (@{$self->{_stack}}) {
} return 1 if $object->user_can_create_account;
return 0; }
return 0;
} }
sub extern_id_used { sub extern_id_used {
my ($self) = @_; my ($self) = @_;
return any { $_->extern_id_used } @{ $self->{_stack} }; return any { $_->extern_id_used } @{$self->{_stack}};
} }
1; 1;
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -28,48 +28,49 @@ use URI::QueryParam; ...@@ -28,48 +28,49 @@ use URI::QueryParam;
use constant DB_TABLE => 'bug_see_also'; use constant DB_TABLE => 'bug_see_also';
use constant NAME_FIELD => 'value'; use constant NAME_FIELD => 'value';
use constant LIST_ORDER => 'id'; use constant LIST_ORDER => 'id';
# See Also is tracked in bugs_activity. # See Also is tracked in bugs_activity.
use constant AUDIT_CREATES => 0; use constant AUDIT_CREATES => 0;
use constant AUDIT_UPDATES => 0; use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0; use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw( use constant DB_COLUMNS => qw(
id id
bug_id bug_id
value value
class class
); );
# This must be strings with the names of the validations, # This must be strings with the names of the validations,
# instead of coderefs, because subclasses override these # instead of coderefs, because subclasses override these
# validators with their own. # validators with their own.
use constant VALIDATORS => { use constant VALIDATORS => {
value => '_check_value', value => '_check_value',
bug_id => '_check_bug_id', bug_id => '_check_bug_id',
class => \&_check_class, class => \&_check_class,
}; };
# This is the order we go through all of subclasses and # This is the order we go through all of subclasses and
# pick the first one that should handle the url. New # pick the first one that should handle the url. New
# subclasses should be added at the end of the list. # subclasses should be added at the end of the list.
use constant SUB_CLASSES => qw( use constant SUB_CLASSES => qw(
Bugzilla::BugUrl::Bugzilla::Local Bugzilla::BugUrl::Bugzilla::Local
Bugzilla::BugUrl::Bugzilla Bugzilla::BugUrl::Bugzilla
Bugzilla::BugUrl::Launchpad Bugzilla::BugUrl::Launchpad
Bugzilla::BugUrl::Google Bugzilla::BugUrl::Google
Bugzilla::BugUrl::Debian Bugzilla::BugUrl::Debian
Bugzilla::BugUrl::JIRA Bugzilla::BugUrl::JIRA
Bugzilla::BugUrl::Trac Bugzilla::BugUrl::Trac
Bugzilla::BugUrl::MantisBT Bugzilla::BugUrl::MantisBT
Bugzilla::BugUrl::SourceForge Bugzilla::BugUrl::SourceForge
Bugzilla::BugUrl::GitHub Bugzilla::BugUrl::GitHub
); );
############################### ###############################
#### Accessors ###### #### Accessors ######
############################### ###############################
sub class { return $_[0]->{class} } sub class { return $_[0]->{class} }
sub bug_id { return $_[0]->{bug_id} } sub bug_id { return $_[0]->{bug_id} }
############################### ###############################
...@@ -77,130 +78,127 @@ sub bug_id { return $_[0]->{bug_id} } ...@@ -77,130 +78,127 @@ sub bug_id { return $_[0]->{bug_id} }
############################### ###############################
sub new { sub new {
my $class = shift; my $class = shift;
my $param = shift; my $param = shift;
if (ref $param) { if (ref $param) {
my $bug_id = $param->{bug_id}; my $bug_id = $param->{bug_id};
my $name = $param->{name} || $param->{value}; my $name = $param->{name} || $param->{value};
if (!defined $bug_id) { if (!defined $bug_id) {
ThrowCodeError('bad_arg', ThrowCodeError('bad_arg', {argument => 'bug_id', function => "${class}::new"});
{ argument => 'bug_id', }
function => "${class}::new" }); if (!defined $name) {
} ThrowCodeError('bad_arg', {argument => 'name', function => "${class}::new"});
if (!defined $name) {
ThrowCodeError('bad_arg',
{ argument => 'name',
function => "${class}::new" });
}
my $condition = 'bug_id = ? AND value = ?';
my @values = ($bug_id, $name);
$param = { condition => $condition, values => \@values };
} }
unshift @_, $param; my $condition = 'bug_id = ? AND value = ?';
return $class->SUPER::new(@_); my @values = ($bug_id, $name);
$param = {condition => $condition, values => \@values};
}
unshift @_, $param;
return $class->SUPER::new(@_);
} }
sub _do_list_select { sub _do_list_select {
my $class = shift; my $class = shift;
my $objects = $class->SUPER::_do_list_select(@_); my $objects = $class->SUPER::_do_list_select(@_);
foreach my $object (@$objects) { foreach my $object (@$objects) {
eval "use " . $object->class; eval "use " . $object->class;
# If the class cannot be loaded, then we build a generic object.
bless $object, ($@ ? 'Bugzilla::BugUrl' : $object->class); # If the class cannot be loaded, then we build a generic object.
} bless $object, ($@ ? 'Bugzilla::BugUrl' : $object->class);
}
return $objects return $objects;
} }
# This is an abstract method. It must be overridden # This is an abstract method. It must be overridden
# in every subclass. # in every subclass.
sub should_handle { sub should_handle {
my ($class, $input) = @_; my ($class, $input) = @_;
ThrowCodeError('unknown_method', ThrowCodeError('unknown_method', {method => "${class}::should_handle"});
{ method => "${class}::should_handle" });
} }
sub class_for { sub class_for {
my ($class, $value) = @_; my ($class, $value) = @_;
my @sub_classes = $class->SUB_CLASSES;
Bugzilla::Hook::process("bug_url_sub_classes",
{ sub_classes => \@sub_classes });
my $uri = URI->new($value);
foreach my $subclass (@sub_classes) {
eval "use $subclass";
die $@ if $@;
return wantarray ? ($subclass, $uri) : $subclass
if $subclass->should_handle($uri);
}
ThrowUserError('bug_url_invalid', { url => $value }); my @sub_classes = $class->SUB_CLASSES;
Bugzilla::Hook::process("bug_url_sub_classes", {sub_classes => \@sub_classes});
my $uri = URI->new($value);
foreach my $subclass (@sub_classes) {
eval "use $subclass";
die $@ if $@;
return wantarray ? ($subclass, $uri) : $subclass
if $subclass->should_handle($uri);
}
ThrowUserError('bug_url_invalid', {url => $value});
} }
sub _check_class { sub _check_class {
my ($class, $subclass) = @_; my ($class, $subclass) = @_;
eval "use $subclass"; die $@ if $@; eval "use $subclass";
return $subclass; die $@ if $@;
return $subclass;
} }
sub _check_bug_id { sub _check_bug_id {
my ($class, $bug_id) = @_; my ($class, $bug_id) = @_;
my $bug; my $bug;
if (blessed $bug_id) { if (blessed $bug_id) {
# We got a bug object passed in, use it
$bug = $bug_id; # We got a bug object passed in, use it
$bug->check_is_visible; $bug = $bug_id;
} $bug->check_is_visible;
else { }
# We got a bug id passed in, check it and get the bug object else {
$bug = Bugzilla::Bug->check({ id => $bug_id }); # We got a bug id passed in, check it and get the bug object
} $bug = Bugzilla::Bug->check({id => $bug_id});
}
return $bug->id; return $bug->id;
} }
sub _check_value { sub _check_value {
my ($class, $uri) = @_; my ($class, $uri) = @_;
my $value = $uri->as_string; my $value = $uri->as_string;
if (!$value) { if (!$value) {
ThrowCodeError('param_required', ThrowCodeError('param_required',
{ function => 'add_see_also', param => '$value' }); {function => 'add_see_also', param => '$value'});
} }
# We assume that the URL is an HTTP URL if there is no (something):// # We assume that the URL is an HTTP URL if there is no (something)://
# in front. # in front.
if (!$uri->scheme) { if (!$uri->scheme) {
# This works better than setting $uri->scheme('http'), because
# that creates URLs like "http:domain.com" and doesn't properly # This works better than setting $uri->scheme('http'), because
# differentiate the path from the domain. # that creates URLs like "http:domain.com" and doesn't properly
$uri = new URI("http://$value"); # differentiate the path from the domain.
} $uri = new URI("http://$value");
elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') { }
ThrowUserError('bug_url_invalid', { url => $value, reason => 'http' }); elsif ($uri->scheme ne 'http' && $uri->scheme ne 'https') {
} ThrowUserError('bug_url_invalid', {url => $value, reason => 'http'});
}
# This stops the following edge cases from being accepted:
# * show_bug.cgi?id=1 # This stops the following edge cases from being accepted:
# * /show_bug.cgi?id=1 # * show_bug.cgi?id=1
# * http:///show_bug.cgi?id=1 # * /show_bug.cgi?id=1
if (!$uri->authority or $uri->path !~ m{/}) { # * http:///show_bug.cgi?id=1
ThrowUserError('bug_url_invalid', if (!$uri->authority or $uri->path !~ m{/}) {
{ url => $value, reason => 'path_only' }); ThrowUserError('bug_url_invalid', {url => $value, reason => 'path_only'});
} }
if (length($uri->path) > MAX_BUG_URL_LENGTH) { if (length($uri->path) > MAX_BUG_URL_LENGTH) {
ThrowUserError('bug_url_too_long', { url => $uri->path }); ThrowUserError('bug_url_too_long', {url => $uri->path});
} }
return $uri; return $uri;
} }
1; 1;
......
...@@ -21,37 +21,39 @@ use Bugzilla::Util; ...@@ -21,37 +21,39 @@ use Bugzilla::Util;
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0; return ($uri->path =~ /show_bug\.cgi$/) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my ($class, $uri) = @_; my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri); $uri = $class->SUPER::_check_value($uri);
my $bug_id = $uri->query_param('id'); my $bug_id = $uri->query_param('id');
# We don't currently allow aliases, because we can't check to see
# if somebody's putting both an alias link and a numeric ID link. # We don't currently allow aliases, because we can't check to see
# When we start validating the URL by accessing the other Bugzilla, # if somebody's putting both an alias link and a numeric ID link.
# we can allow aliases. # When we start validating the URL by accessing the other Bugzilla,
detaint_natural($bug_id); # we can allow aliases.
if (!$bug_id) { detaint_natural($bug_id);
my $value = $uri->as_string; if (!$bug_id) {
ThrowUserError('bug_url_invalid', { url => $value, reason => 'id' }); my $value = $uri->as_string;
} ThrowUserError('bug_url_invalid', {url => $value, reason => 'id'});
}
# Make sure that "id" is the only query parameter.
$uri->query("id=$bug_id"); # Make sure that "id" is the only query parameter.
# And remove any # part if there is one. $uri->query("id=$bug_id");
$uri->fragment(undef);
# And remove any # part if there is one.
return $uri; $uri->fragment(undef);
return $uri;
} }
sub target_bug_id { sub target_bug_id {
my ($self) = @_; my ($self) = @_;
return new URI($self->name)->query_param('id'); return new URI($self->name)->query_param('id');
} }
1; 1;
...@@ -20,77 +20,75 @@ use Bugzilla::Util; ...@@ -20,77 +20,75 @@ use Bugzilla::Util;
#### Initialization #### #### Initialization ####
############################### ###############################
use constant VALIDATOR_DEPENDENCIES => { use constant VALIDATOR_DEPENDENCIES => {value => ['bug_id'],};
value => ['bug_id'],
};
############################### ###############################
#### Methods #### #### Methods ####
############################### ###############################
sub ref_bug_url { sub ref_bug_url {
my $self = shift; my $self = shift;
if (!exists $self->{ref_bug_url}) { if (!exists $self->{ref_bug_url}) {
my $ref_bug_id = new URI($self->name)->query_param('id'); my $ref_bug_id = new URI($self->name)->query_param('id');
my $ref_bug = Bugzilla::Bug->check($ref_bug_id); my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
my $ref_value = $self->local_uri($self->bug_id); my $ref_value = $self->local_uri($self->bug_id);
$self->{ref_bug_url} = $self->{ref_bug_url} = new Bugzilla::BugUrl::Bugzilla::Local(
new Bugzilla::BugUrl::Bugzilla::Local({ bug_id => $ref_bug->id, {bug_id => $ref_bug->id, value => $ref_value});
value => $ref_value }); }
} return $self->{ref_bug_url};
return $self->{ref_bug_url};
} }
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Check if it is either a bug id number or an alias. # Check if it is either a bug id number or an alias.
return 1 if $uri->as_string =~ m/^\w+$/; return 1 if $uri->as_string =~ m/^\w+$/;
# Check if it is a local Bugzilla uri and call # Check if it is a local Bugzilla uri and call
# Bugzilla::BugUrl::Bugzilla to check if it's a valid Bugzilla # Bugzilla::BugUrl::Bugzilla to check if it's a valid Bugzilla
# see also url. # see also url.
my $canonical_local = URI->new($class->local_uri)->canonical; my $canonical_local = URI->new($class->local_uri)->canonical;
if ($canonical_local->authority eq $uri->canonical->authority if ( $canonical_local->authority eq $uri->canonical->authority
and $canonical_local->path eq $uri->canonical->path) and $canonical_local->path eq $uri->canonical->path)
{ {
return $class->SUPER::should_handle($uri); return $class->SUPER::should_handle($uri);
} }
return 0; return 0;
} }
sub _check_value { sub _check_value {
my ($class, $uri, undef, $params) = @_; my ($class, $uri, undef, $params) = @_;
# At this point we are going to treat any word as a # At this point we are going to treat any word as a
# bug id/alias to the local Bugzilla. # bug id/alias to the local Bugzilla.
my $value = $uri->as_string; my $value = $uri->as_string;
if ($value =~ m/^\w+$/) { if ($value =~ m/^\w+$/) {
$uri = new URI($class->local_uri($value)); $uri = new URI($class->local_uri($value));
} else { }
# It's not a word, then we have to check else {
# if it's a valid Bugzilla url. # It's not a word, then we have to check
$uri = $class->SUPER::_check_value($uri); # if it's a valid Bugzilla url.
} $uri = $class->SUPER::_check_value($uri);
}
my $ref_bug_id = $uri->query_param('id');
my $ref_bug = Bugzilla::Bug->check($ref_bug_id); my $ref_bug_id = $uri->query_param('id');
my $self_bug_id = $params->{bug_id}; my $ref_bug = Bugzilla::Bug->check($ref_bug_id);
$params->{ref_bug} = $ref_bug; my $self_bug_id = $params->{bug_id};
$params->{ref_bug} = $ref_bug;
if ($ref_bug->id == $self_bug_id) {
ThrowUserError('see_also_self_reference'); if ($ref_bug->id == $self_bug_id) {
} ThrowUserError('see_also_self_reference');
}
return $uri;
return $uri;
} }
sub local_uri { sub local_uri {
my ($self, $bug_id) = @_; my ($self, $bug_id) = @_;
$bug_id ||= ''; $bug_id ||= '';
return correct_urlbase() . "show_bug.cgi?id=$bug_id"; return correct_urlbase() . "show_bug.cgi?id=$bug_id";
} }
1; 1;
...@@ -18,28 +18,30 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,28 +18,30 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Debian BTS URLs can look like various things: # Debian BTS URLs can look like various things:
# http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234 # http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1234
# http://bugs.debian.org/1234 # http://bugs.debian.org/1234
return (lc($uri->authority) eq 'bugs.debian.org' return (
and (($uri->path =~ /bugreport\.cgi$/ lc($uri->authority) eq 'bugs.debian.org'
and $uri->query_param('bug') =~ m|^\d+$|) and
or $uri->path =~ m|^/\d+$|)) ? 1 : 0; (($uri->path =~ /bugreport\.cgi$/ and $uri->query_param('bug') =~ m|^\d+$|)
or $uri->path =~ m|^/\d+$|)
) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_check_value(@_);
# This is the shortest standard URL form for Debian BTS URLs, # This is the shortest standard URL form for Debian BTS URLs,
# and so we reduce all URLs to this. # and so we reduce all URLs to this.
$uri->path =~ m|^/(\d+)$| || $uri->query_param('bug') =~ m|^(\d+)$|; $uri->path =~ m|^/(\d+)$| || $uri->query_param('bug') =~ m|^(\d+)$|;
$uri = new URI("http://bugs.debian.org/$1"); $uri = new URI("http://bugs.debian.org/$1");
return $uri; return $uri;
} }
1; 1;
...@@ -18,25 +18,25 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,25 +18,25 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# GitHub issue URLs have only one form: # GitHub issue URLs have only one form:
# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/issues/111 # https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/issues/111
# GitHub pull request URLs have only one form: # GitHub pull request URLs have only one form:
# https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/pull/111 # https://github.com/USER_OR_TEAM_OR_ORGANIZATION_NAME/REPOSITORY_NAME/pull/111
return (lc($uri->authority) eq 'github.com' return (lc($uri->authority) eq 'github.com'
and $uri->path =~ m!^/[^/]+/[^/]+/(?:issues|pull)/\d+$!) ? 1 : 0; and $uri->path =~ m!^/[^/]+/[^/]+/(?:issues|pull)/\d+$!) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my ($class, $uri) = @_; my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri); $uri = $class->SUPER::_check_value($uri);
# GitHub HTTP URLs redirect to HTTPS, so just use the HTTPS scheme. # GitHub HTTP URLs redirect to HTTPS, so just use the HTTPS scheme.
$uri->scheme('https'); $uri->scheme('https');
return $uri; return $uri;
} }
1; 1;
...@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Google Code URLs only have one form: # Google Code URLs only have one form:
# http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234 # http(s)://code.google.com/p/PROJECT_NAME/issues/detail?id=1234
return (lc($uri->authority) eq 'code.google.com' return (lc($uri->authority) eq 'code.google.com'
and $uri->path =~ m|^/p/[^/]+/issues/detail$| and $uri->path =~ m|^/p/[^/]+/issues/detail$|
and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0; and $uri->query_param('id') =~ /^\d+$/) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my ($class, $uri) = @_; my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri);
# While Google Code URLs can be either HTTP or HTTPS, $uri = $class->SUPER::_check_value($uri);
# always go with the HTTP scheme, as that's the default.
if ($uri->scheme eq 'https') {
$uri->scheme('http');
}
return $uri; # While Google Code URLs can be either HTTP or HTTPS,
# always go with the HTTP scheme, as that's the default.
if ($uri->scheme eq 'https') {
$uri->scheme('http');
}
return $uri;
} }
1; 1;
...@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# JIRA URLs have only one basic form (but the jira is optional): # JIRA URLs have only one basic form (but the jira is optional):
# https://issues.apache.org/jira/browse/KEY-1234 # https://issues.apache.org/jira/browse/KEY-1234
# http://issues.example.com/browse/KEY-1234 # http://issues.example.com/browse/KEY-1234
return ($uri->path =~ m|/browse/[A-Z][A-Z]+-\d+$|) ? 1 : 0; return ($uri->path =~ m|/browse/[A-Z][A-Z]+-\d+$|) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_check_value(@_);
# Make sure there are no query parameters. # Make sure there are no query parameters.
$uri->query(undef); $uri->query(undef);
# And remove any # part if there is one.
$uri->fragment(undef);
return $uri; # And remove any # part if there is one.
$uri->fragment(undef);
return $uri;
} }
1; 1;
...@@ -18,27 +18,28 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,27 +18,28 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Launchpad bug URLs can look like various things: # Launchpad bug URLs can look like various things:
# https://bugs.launchpad.net/ubuntu/+bug/1234 # https://bugs.launchpad.net/ubuntu/+bug/1234
# https://launchpad.net/bugs/1234 # https://launchpad.net/bugs/1234
# All variations end with either "/bugs/1234" or "/+bug/1234" # All variations end with either "/bugs/1234" or "/+bug/1234"
return ($uri->authority =~ /launchpad\.net$/ return ($uri->authority =~ /launchpad\.net$/ and $uri->path =~ m|bugs?/\d+$|)
and $uri->path =~ m|bugs?/\d+$|) ? 1 : 0; ? 1
: 0;
} }
sub _check_value { sub _check_value {
my ($class, $uri) = @_; my ($class, $uri) = @_;
$uri = $class->SUPER::_check_value($uri); $uri = $class->SUPER::_check_value($uri);
# This is the shortest standard URL form for Launchpad bugs, # This is the shortest standard URL form for Launchpad bugs,
# and so we reduce all URLs to this. # and so we reduce all URLs to this.
$uri->path =~ m|bugs?/(\d+)$|; $uri->path =~ m|bugs?/(\d+)$|;
$uri = new URI("https://launchpad.net/bugs/$1"); $uri = new URI("https://launchpad.net/bugs/$1");
return $uri; return $uri;
} }
1; 1;
...@@ -18,22 +18,22 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,22 +18,22 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# MantisBT URLs look like the following ('bugs' directory is optional): # MantisBT URLs look like the following ('bugs' directory is optional):
# http://www.mantisbt.org/bugs/view.php?id=1234 # http://www.mantisbt.org/bugs/view.php?id=1234
return ($uri->path_query =~ m|view\.php\?id=\d+$|) ? 1 : 0; return ($uri->path_query =~ m|view\.php\?id=\d+$|) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_check_value(@_);
# Remove any # part if there is one. # Remove any # part if there is one.
$uri->fragment(undef); $uri->fragment(undef);
return $uri; return $uri;
} }
1; 1;
...@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,27 +18,27 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# SourceForge tracker URLs have only one form: # SourceForge tracker URLs have only one form:
# http://sourceforge.net/tracker/?func=detail&aid=111&group_id=111&atid=111 # http://sourceforge.net/tracker/?func=detail&aid=111&group_id=111&atid=111
return (lc($uri->authority) eq 'sourceforge.net' return (lc($uri->authority) eq 'sourceforge.net'
and $uri->path =~ m|/tracker/| and $uri->path =~ m|/tracker/|
and $uri->query_param('func') eq 'detail' and $uri->query_param('func') eq 'detail'
and $uri->query_param('aid') and $uri->query_param('aid')
and $uri->query_param('group_id') and $uri->query_param('group_id')
and $uri->query_param('atid')) ? 1 : 0; and $uri->query_param('atid')) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_check_value(@_);
# Remove any # part if there is one. # Remove any # part if there is one.
$uri->fragment(undef); $uri->fragment(undef);
return $uri; return $uri;
} }
1; 1;
...@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl); ...@@ -18,25 +18,26 @@ use parent qw(Bugzilla::BugUrl);
############################### ###############################
sub should_handle { sub should_handle {
my ($class, $uri) = @_; my ($class, $uri) = @_;
# Trac URLs can look like various things: # Trac URLs can look like various things:
# http://dev.mutt.org/trac/ticket/1234 # http://dev.mutt.org/trac/ticket/1234
# http://trac.roundcube.net/ticket/1484130 # http://trac.roundcube.net/ticket/1484130
return ($uri->path =~ m|/ticket/\d+$|) ? 1 : 0; return ($uri->path =~ m|/ticket/\d+$|) ? 1 : 0;
} }
sub _check_value { sub _check_value {
my $class = shift; my $class = shift;
my $uri = $class->SUPER::_check_value(@_); my $uri = $class->SUPER::_check_value(@_);
# Make sure there are no query parameters. # Make sure there are no query parameters.
$uri->query(undef); $uri->query(undef);
# And remove any # part if there is one.
$uri->fragment(undef);
return $uri; # And remove any # part if there is one.
$uri->fragment(undef);
return $uri;
} }
1; 1;
...@@ -25,25 +25,27 @@ use constant LIST_ORDER => 'id'; ...@@ -25,25 +25,27 @@ use constant LIST_ORDER => 'id';
use constant NAME_FIELD => 'id'; use constant NAME_FIELD => 'id';
# turn off auditing and exclude these objects from memcached # turn off auditing and exclude these objects from memcached
use constant { AUDIT_CREATES => 0, use constant {
AUDIT_UPDATES => 0, AUDIT_CREATES => 0,
AUDIT_REMOVES => 0, AUDIT_UPDATES => 0,
USE_MEMCACHED => 0 }; AUDIT_REMOVES => 0,
USE_MEMCACHED => 0
};
##################################################################### #####################################################################
# Provide accessors for our columns # Provide accessors for our columns
##################################################################### #####################################################################
sub id { return $_[0]->{id} } sub id { return $_[0]->{id} }
sub bug_id { return $_[0]->{bug_id} } sub bug_id { return $_[0]->{bug_id} }
sub user_id { return $_[0]->{user_id} } sub user_id { return $_[0]->{user_id} }
sub last_visit_ts { return $_[0]->{last_visit_ts} } sub last_visit_ts { return $_[0]->{last_visit_ts} }
sub user { sub user {
my $self = shift; my $self = shift;
$self->{user} //= Bugzilla::User->new({ id => $self->user_id, cache => 1 }); $self->{user} //= Bugzilla::User->new({id => $self->user_id, cache => 1});
return $self->{user}; return $self->{user};
} }
1; 1;
......
...@@ -26,26 +26,26 @@ use parent qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object Exporter); ...@@ -26,26 +26,26 @@ use parent qw(Bugzilla::Field::ChoiceInterface Bugzilla::Object Exporter);
use constant IS_CONFIG => 1; use constant IS_CONFIG => 1;
use constant DB_TABLE => 'classifications'; use constant DB_TABLE => 'classifications';
use constant LIST_ORDER => 'sortkey, name'; use constant LIST_ORDER => 'sortkey, name';
use constant DB_COLUMNS => qw( use constant DB_COLUMNS => qw(
id id
name name
description description
sortkey sortkey
); );
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
name name
description description
sortkey sortkey
); );
use constant VALIDATORS => { use constant VALIDATORS => {
name => \&_check_name, name => \&_check_name,
description => \&_check_description, description => \&_check_description,
sortkey => \&_check_sortkey, sortkey => \&_check_sortkey,
}; };
############################### ###############################
...@@ -53,29 +53,31 @@ use constant VALIDATORS => { ...@@ -53,29 +53,31 @@ use constant VALIDATORS => {
############################### ###############################
sub remove_from_db { sub remove_from_db {
my $self = shift; my $self = shift;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
ThrowUserError("classification_not_deletable") if ($self->id == 1); ThrowUserError("classification_not_deletable") if ($self->id == 1);
$dbh->bz_start_transaction(); $dbh->bz_start_transaction();
# Reclassify products to the default classification, if needed. # Reclassify products to the default classification, if needed.
my $product_ids = $dbh->selectcol_arrayref( my $product_ids
'SELECT id FROM products WHERE classification_id = ?', undef, $self->id); = $dbh->selectcol_arrayref(
'SELECT id FROM products WHERE classification_id = ?',
if (@$product_ids) { undef, $self->id);
$dbh->do('UPDATE products SET classification_id = 1 WHERE '
. $dbh->sql_in('id', $product_ids)); if (@$product_ids) {
foreach my $id (@$product_ids) { $dbh->do('UPDATE products SET classification_id = 1 WHERE '
Bugzilla->memcached->clear({ table => 'products', id => $id }); . $dbh->sql_in('id', $product_ids));
} foreach my $id (@$product_ids) {
Bugzilla->memcached->clear_config(); Bugzilla->memcached->clear({table => 'products', id => $id});
} }
Bugzilla->memcached->clear_config();
}
$self->SUPER::remove_from_db(); $self->SUPER::remove_from_db();
$dbh->bz_commit_transaction(); $dbh->bz_commit_transaction();
} }
...@@ -84,38 +86,41 @@ sub remove_from_db { ...@@ -84,38 +86,41 @@ sub remove_from_db {
############################### ###############################
sub _check_name { sub _check_name {
my ($invocant, $name) = @_; my ($invocant, $name) = @_;
$name = trim($name); $name = trim($name);
$name || ThrowUserError('classification_not_specified'); $name || ThrowUserError('classification_not_specified');
if (length($name) > MAX_CLASSIFICATION_SIZE) { if (length($name) > MAX_CLASSIFICATION_SIZE) {
ThrowUserError('classification_name_too_long', {'name' => $name}); ThrowUserError('classification_name_too_long', {'name' => $name});
} }
my $classification = new Bugzilla::Classification({name => $name}); my $classification = new Bugzilla::Classification({name => $name});
if ($classification && (!ref $invocant || $classification->id != $invocant->id)) { if ($classification && (!ref $invocant || $classification->id != $invocant->id))
ThrowUserError("classification_already_exists", { name => $classification->name }); {
} ThrowUserError("classification_already_exists",
return $name; {name => $classification->name});
}
return $name;
} }
sub _check_description { sub _check_description {
my ($invocant, $description) = @_; my ($invocant, $description) = @_;
$description = trim($description || ''); $description = trim($description || '');
return $description; return $description;
} }
sub _check_sortkey { sub _check_sortkey {
my ($invocant, $sortkey) = @_; my ($invocant, $sortkey) = @_;
$sortkey ||= 0; $sortkey ||= 0;
my $stored_sortkey = $sortkey; my $stored_sortkey = $sortkey;
if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) { if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) {
ThrowUserError('classification_invalid_sortkey', { 'sortkey' => $stored_sortkey }); ThrowUserError('classification_invalid_sortkey',
} {'sortkey' => $stored_sortkey});
return $sortkey; }
return $sortkey;
} }
##################################### #####################################
...@@ -124,41 +129,45 @@ sub _check_sortkey { ...@@ -124,41 +129,45 @@ sub _check_sortkey {
use constant FIELD_NAME => 'classification'; use constant FIELD_NAME => 'classification';
use constant is_default => 0; use constant is_default => 0;
use constant is_active => 1; use constant is_active => 1;
############################### ###############################
#### Methods #### #### Methods ####
############################### ###############################
sub set_name { $_[0]->set('name', $_[1]); } sub set_name { $_[0]->set('name', $_[1]); }
sub set_description { $_[0]->set('description', $_[1]); } sub set_description { $_[0]->set('description', $_[1]); }
sub set_sortkey { $_[0]->set('sortkey', $_[1]); } sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
sub product_count { sub product_count {
my $self = shift; my $self = shift;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
if (!defined $self->{'product_count'}) { if (!defined $self->{'product_count'}) {
$self->{'product_count'} = $dbh->selectrow_array(q{ $self->{'product_count'} = $dbh->selectrow_array(
q{
SELECT COUNT(*) FROM products SELECT COUNT(*) FROM products
WHERE classification_id = ?}, undef, $self->id) || 0; WHERE classification_id = ?}, undef, $self->id
} ) || 0;
return $self->{'product_count'}; }
return $self->{'product_count'};
} }
sub products { sub products {
my $self = shift; my $self = shift;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
if (!$self->{'products'}) { if (!$self->{'products'}) {
my $product_ids = $dbh->selectcol_arrayref(q{ my $product_ids = $dbh->selectcol_arrayref(
q{
SELECT id FROM products SELECT id FROM products
WHERE classification_id = ? WHERE classification_id = ?
ORDER BY name}, undef, $self->id); ORDER BY name}, undef, $self->id
);
$self->{'products'} = Bugzilla::Product->new_from_list($product_ids); $self->{'products'} = Bugzilla::Product->new_from_list($product_ids);
} }
return $self->{'products'}; return $self->{'products'};
} }
############################### ###############################
...@@ -166,7 +175,7 @@ sub products { ...@@ -166,7 +175,7 @@ sub products {
############################### ###############################
sub description { return $_[0]->{'description'}; } sub description { return $_[0]->{'description'}; }
sub sortkey { return $_[0]->{'sortkey'}; } sub sortkey { return $_[0]->{'sortkey'}; }
############################### ###############################
...@@ -177,27 +186,32 @@ sub sortkey { return $_[0]->{'sortkey'}; } ...@@ -177,27 +186,32 @@ sub sortkey { return $_[0]->{'sortkey'}; }
# in global/choose-product.html.tmpl. # in global/choose-product.html.tmpl.
sub sort_products_by_classification { sub sort_products_by_classification {
my $products = shift; my $products = shift;
my $list; my $list;
if (Bugzilla->params->{'useclassification'}) { if (Bugzilla->params->{'useclassification'}) {
my $class = {}; my $class = {};
# Get all classifications with at least one product.
foreach my $product (@$products) { # Get all classifications with at least one product.
$class->{$product->classification_id}->{'object'} ||= foreach my $product (@$products) {
new Bugzilla::Classification($product->classification_id); $class->{$product->classification_id}->{'object'}
# Nice way to group products per classification, without querying ||= new Bugzilla::Classification($product->classification_id);
# the DB again.
push(@{$class->{$product->classification_id}->{'products'}}, $product); # Nice way to group products per classification, without querying
} # the DB again.
$list = [sort {$a->{'object'}->sortkey <=> $b->{'object'}->sortkey push(@{$class->{$product->classification_id}->{'products'}}, $product);
|| lc($a->{'object'}->name) cmp lc($b->{'object'}->name)}
(values %$class)];
}
else {
$list = [{object => undef, products => $products}];
} }
return $list; $list = [
sort {
$a->{'object'}->sortkey <=> $b->{'object'}->sortkey
|| lc($a->{'object'}->name) cmp lc($b->{'object'}->name)
} (values %$class)
];
}
else {
$list = [{object => undef, products => $products}];
}
return $list;
} }
1; 1;
......
...@@ -21,20 +21,20 @@ use constant AUDIT_UPDATES => 0; ...@@ -21,20 +21,20 @@ use constant AUDIT_UPDATES => 0;
use constant AUDIT_REMOVES => 0; use constant AUDIT_REMOVES => 0;
use constant DB_COLUMNS => qw( use constant DB_COLUMNS => qw(
id id
tag tag
weight weight
); );
use constant UPDATE_COLUMNS => qw( use constant UPDATE_COLUMNS => qw(
weight weight
); );
use constant DB_TABLE => 'longdescs_tags_weights'; use constant DB_TABLE => 'longdescs_tags_weights';
use constant ID_FIELD => 'id'; use constant ID_FIELD => 'id';
use constant NAME_FIELD => 'tag'; use constant NAME_FIELD => 'tag';
use constant LIST_ORDER => 'weight DESC'; use constant LIST_ORDER => 'weight DESC';
use constant VALIDATORS => { }; use constant VALIDATORS => {};
# There's no gain to caching these objects # There's no gain to caching these objects
use constant USE_MEMCACHED => 0; use constant USE_MEMCACHED => 0;
......
...@@ -16,32 +16,21 @@ use Bugzilla::Config::Common; ...@@ -16,32 +16,21 @@ use Bugzilla::Config::Common;
our $sortkey = 200; our $sortkey = 200;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'allowbugdeletion', type => 'b', default => 0},
name => 'allowbugdeletion',
type => 'b', {name => 'allowemailchange', type => 'b', default => 1},
default => 0
}, {name => 'allowuserdeletion', type => 'b', default => 0},
{ {
name => 'allowemailchange', name => 'last_visit_keep_days',
type => 'b', type => 't',
default => 1 default => 10,
}, checker => \&check_numeric
}
{ );
name => 'allowuserdeletion',
type => 'b',
default => 0
},
{
name => 'last_visit_keep_days',
type => 't',
default => 10,
checker => \&check_numeric
});
return @param_list; return @param_list;
} }
......
...@@ -16,31 +16,18 @@ use Bugzilla::Config::Common; ...@@ -16,31 +16,18 @@ use Bugzilla::Config::Common;
our $sortkey = 1700; our $sortkey = 1700;
use constant get_param_list => ( use constant get_param_list => (
{ {name => 'cookiedomain', type => 't', default => ''},
name => 'cookiedomain',
type => 't',
default => ''
},
{ {name => 'inbound_proxies', type => 't', default => '', checker => \&check_ip},
name => 'inbound_proxies',
type => 't',
default => '',
checker => \&check_ip
},
{ {name => 'proxy_url', type => 't', default => ''},
name => 'proxy_url',
type => 't',
default => ''
},
{ {
name => 'strict_transport_security', name => 'strict_transport_security',
type => 's', type => 's',
choices => ['off', 'this_domain_only', 'include_subdomains'], choices => ['off', 'this_domain_only', 'include_subdomains'],
default => 'off', default => 'off',
checker => \&check_multi checker => \&check_multi
}, },
); );
......
...@@ -16,48 +16,41 @@ use Bugzilla::Config::Common; ...@@ -16,48 +16,41 @@ use Bugzilla::Config::Common;
our $sortkey = 400; our $sortkey = 400;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'allow_attachment_display', type => 'b', default => 0},
name => 'allow_attachment_display',
type => 'b', {
default => 0 name => 'attachment_base',
}, type => 't',
default => '',
{ checker => \&check_urlbase
name => 'attachment_base', },
type => 't',
default => '', {name => 'allow_attachment_deletion', type => 'b', default => 0},
checker => \&check_urlbase
}, {
name => 'maxattachmentsize',
{ type => 't',
name => 'allow_attachment_deletion', default => '1000',
type => 'b', checker => \&check_maxattachmentsize
default => 0 },
},
# The maximum size (in bytes) for patches and non-patch attachments.
{ # The default limit is 1000KB, which is 24KB less than mysql's default
name => 'maxattachmentsize', # maximum packet size (which determines how much data can be sent in a
type => 't', # single mysql packet and thus how much data can be inserted into the
default => '1000', # database) to provide breathing space for the data in other fields of
checker => \&check_maxattachmentsize # 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 name => 'maxlocalattachment',
# maximum packet size (which determines how much data can be sent in a type => 't',
# single mysql packet and thus how much data can be inserted into the default => '0',
# database) to provide breathing space for the data in other fields of checker => \&check_numeric
# 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
} );
return @param_list; return @param_list;
} }
......
...@@ -16,111 +16,85 @@ use Bugzilla::Config::Common; ...@@ -16,111 +16,85 @@ use Bugzilla::Config::Common;
our $sortkey = 300; our $sortkey = 300;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'auth_env_id', type => 't', default => '',},
name => 'auth_env_id',
type => 't', {name => 'auth_env_email', type => 't', default => '',},
default => '',
}, {name => 'auth_env_realname', type => 't', default => '',},
{ # XXX in the future:
name => 'auth_env_email', #
type => 't', # user_verify_class and user_info_class should have choices gathered from
default => '', # whatever sits in their respective directories
}, #
# rather than comma-separated lists, these two should eventually become
{ # arrays, but that requires alterations to editparams first
name => 'auth_env_realname',
type => 't', {
default => '', name => 'user_info_class',
}, type => 's',
choices => ['CGI', 'Env', 'Env,CGI'],
# XXX in the future: default => 'CGI',
# checker => \&check_multi
# user_verify_class and user_info_class should have choices gathered from },
# whatever sits in their respective directories
# {
# rather than comma-separated lists, these two should eventually become name => 'user_verify_class',
# arrays, but that requires alterations to editparams first type => 'o',
choices => ['DB', 'RADIUS', 'LDAP'],
{ default => 'DB',
name => 'user_info_class', checker => \&check_user_verify_class
type => 's', },
choices => [ 'CGI', 'Env', 'Env,CGI' ],
default => 'CGI', {
checker => \&check_multi name => 'rememberlogin',
}, type => 's',
choices => ['on', 'defaulton', 'defaultoff', 'off'],
{ default => 'on',
name => 'user_verify_class', checker => \&check_multi
type => 'o', },
choices => [ 'DB', 'RADIUS', 'LDAP' ],
default => 'DB', {name => 'requirelogin', type => 'b', default => '0'},
checker => \&check_user_verify_class
}, {name => 'webservice_email_filter', type => 'b', default => 0},
{ {
name => 'rememberlogin', name => 'emailregexp',
type => 's', type => 't',
choices => ['on', 'defaulton', 'defaultoff', 'off'], default => q:^[\\w\\.\\+\\-=']+@[\\w\\.\\-]+\\.[\\w\\-]+$:,
default => 'on', checker => \&check_regexp
checker => \&check_multi },
},
{
{ name => 'emailregexpdesc',
name => 'requirelogin', type => 'l',
type => 'b', default => 'A legal address must contain exactly one \'@\', and at least '
default => '0' . 'one \'.\' after the @.'
}, },
{ {name => 'emailsuffix', type => 't', default => ''},
name => 'webservice_email_filter',
type => 'b', {
default => 0 name => 'createemailregexp',
}, type => 't',
default => q:.*:,
{ checker => \&check_regexp
name => 'emailregexp', },
type => 't',
default => q:^[\\w\\.\\+\\-=']+@[\\w\\.\\-]+\\.[\\w\\-]+$:, {
checker => \&check_regexp name => 'password_complexity',
}, type => 's',
choices => [
{ 'no_constraints', 'mixed_letters',
name => 'emailregexpdesc', 'letters_numbers', 'letters_numbers_specialchars'
type => 'l', ],
default => 'A legal address must contain exactly one \'@\', and at least ' . default => 'no_constraints',
'one \'.\' after the @.' checker => \&check_multi
}, },
{ {name => 'password_check_on_login', type => 'b', default => '1'},
name => 'emailsuffix',
type => 't',
default => ''
},
{
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'
},
); );
return @param_list; return @param_list;
} }
......
...@@ -26,55 +26,33 @@ sub get_param_list { ...@@ -26,55 +26,33 @@ sub get_param_list {
# and bug_status.is_open is not yet defined (hence the eval), so we use # and bug_status.is_open is not yet defined (hence the eval), so we use
# the bug statuses above as they are still hardcoded. # the bug statuses above as they are still hardcoded.
eval { eval {
my @current_closed_states = map {$_->name} closed_bug_statuses(); my @current_closed_states = map { $_->name } closed_bug_statuses();
# If no closed state was found, use the default list above.
@closed_bug_statuses = @current_closed_states if scalar(@current_closed_states); # If no closed state was found, use the default list above.
@closed_bug_statuses = @current_closed_states if scalar(@current_closed_states);
}; };
my @param_list = ( my @param_list = (
{ {
name => 'duplicate_or_move_bug_status', name => 'duplicate_or_move_bug_status',
type => 's', type => 's',
choices => \@closed_bug_statuses, choices => \@closed_bug_statuses,
default => $closed_bug_statuses[0], default => $closed_bug_statuses[0],
checker => \&check_bug_status checker => \&check_bug_status
}, },
{ {name => 'letsubmitterchoosepriority', type => 'b', default => 1},
name => 'letsubmitterchoosepriority',
type => 'b', {name => 'letsubmitterchoosemilestone', type => 'b', default => 1},
default => 1
}, {name => 'musthavemilestoneonaccept', type => 'b', default => 0},
{ {name => 'commentonchange_resolution', type => 'b', default => 0},
name => 'letsubmitterchoosemilestone',
type => 'b', {name => 'commentonduplicate', type => 'b', default => 0},
default => 1
}, {name => 'noresolveonopenblockers', type => 'b', default => 0,}
);
{
name => 'musthavemilestoneonaccept',
type => 'b',
default => 0
},
{
name => 'commentonchange_resolution',
type => 'b',
default => 0
},
{
name => 'commentonduplicate',
type => 'b',
default => 0
},
{
name => 'noresolveonopenblockers',
type => 'b',
default => 0,
} );
return @param_list; return @param_list;
} }
......
...@@ -25,73 +25,50 @@ sub get_param_list { ...@@ -25,73 +25,50 @@ sub get_param_list {
my @legal_OS = @{get_legal_field_values('op_sys')}; my @legal_OS = @{get_legal_field_values('op_sys')};
my @param_list = ( my @param_list = (
{ {name => 'useclassification', type => 'b', default => 0},
name => 'useclassification',
type => 'b', {name => 'usetargetmilestone', type => 'b', default => 0},
default => 0
}, {name => 'useqacontact', type => 'b', default => 0},
{ {name => 'usestatuswhiteboard', type => 'b', default => 0},
name => 'usetargetmilestone',
type => 'b', {name => 'use_see_also', type => 'b', default => 1},
default => 0
}, {
name => 'defaultpriority',
{ type => 's',
name => 'useqacontact', choices => \@legal_priorities,
type => 'b', default => $legal_priorities[-1],
default => 0 checker => \&check_priority
}, },
{ {
name => 'usestatuswhiteboard', name => 'defaultseverity',
type => 'b', type => 's',
default => 0 choices => \@legal_severities,
}, default => $legal_severities[-1],
checker => \&check_severity
{ },
name => 'use_see_also',
type => 'b', {
default => 1 name => 'defaultplatform',
}, type => 's',
choices => ['', @legal_platforms],
{ default => '',
name => 'defaultpriority', checker => \&check_platform
type => 's', },
choices => \@legal_priorities,
default => $legal_priorities[-1], {
checker => \&check_priority name => 'defaultopsys',
}, type => 's',
choices => ['', @legal_OS],
{ default => '',
name => 'defaultseverity', checker => \&check_opsys
type => 's', },
choices => \@legal_severities,
default => $legal_severities[-1], {name => 'collapsed_comment_tags', type => 't', default => 'obsolete, spam',}
checker => \&check_severity );
},
{
name => 'defaultplatform',
type => 's',
choices => ['', @legal_platforms],
default => '',
checker => \&check_platform
},
{
name => 'defaultopsys',
type => 's',
choices => ['', @legal_OS],
default => '',
checker => \&check_opsys
},
{
name => 'collapsed_comment_tags',
type => 't',
default => 'obsolete, spam',
});
return @param_list; return @param_list;
} }
......
...@@ -16,31 +16,13 @@ use Bugzilla::Config::Common; ...@@ -16,31 +16,13 @@ use Bugzilla::Config::Common;
our $sortkey = 100; our $sortkey = 100;
use constant get_param_list => ( use constant get_param_list => (
{ {name => 'urlbase', type => 't', default => '', checker => \&check_urlbase},
name => 'urlbase',
type => 't', {name => 'ssl_redirect', type => 'b', default => 0},
default => '',
checker => \&check_urlbase {name => 'sslbase', type => 't', default => '', checker => \&check_sslbase},
},
{name => 'cookiepath', type => 't', default => '/'},
{
name => 'ssl_redirect',
type => 'b',
default => 0
},
{
name => 'sslbase',
type => 't',
default => '',
checker => \&check_sslbase
},
{
name => 'cookiepath',
type => 't',
default => '/'
},
); );
1; 1;
...@@ -16,21 +16,17 @@ use Bugzilla::Config::Common; ...@@ -16,21 +16,17 @@ use Bugzilla::Config::Common;
our $sortkey = 800; our $sortkey = 800;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {
name => 'webdotbase', name => 'webdotbase',
type => 't', type => 't',
default => '', default => '',
checker => \&check_webdotbase checker => \&check_webdotbase
}, },
{ {name => 'font_file', type => 't', default => '', checker => \&check_font_file}
name => 'font_file', );
type => 't',
default => '',
checker => \&check_font_file
});
return @param_list; return @param_list;
} }
......
...@@ -17,39 +17,28 @@ our $sortkey = 150; ...@@ -17,39 +17,28 @@ our $sortkey = 150;
use constant get_param_list => ( use constant get_param_list => (
{ {
name => 'maintainer', name => 'maintainer',
type => 't', type => 't',
no_reset => '1', no_reset => '1',
default => '', default => '',
checker => \&check_email checker => \&check_email
}, },
{ {name => 'utf8', type => 'b', default => '0', checker => \&check_utf8},
name => 'utf8',
type => 'b',
default => '0',
checker => \&check_utf8
},
{ {name => 'shutdownhtml', type => 'l', default => ''},
name => 'shutdownhtml',
type => 'l',
default => ''
},
{ {name => 'announcehtml', type => 'l', default => ''},
name => 'announcehtml',
type => 'l',
default => ''
},
{ {
name => 'upgrade_notification', name => 'upgrade_notification',
type => 's', type => 's',
choices => ['development_snapshot', 'latest_stable_release', choices => [
'stable_branch_release', 'disabled'], 'development_snapshot', 'latest_stable_release',
default => 'latest_stable_release', 'stable_branch_release', 'disabled'
checker => \&check_notification ],
default => 'latest_stable_release',
checker => \&check_notification
}, },
); );
......
...@@ -20,84 +20,69 @@ sub get_param_list { ...@@ -20,84 +20,69 @@ sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'makeproductgroups', type => 'b', default => 0},
name => 'makeproductgroups',
type => 'b', {
default => 0 name => 'chartgroup',
}, type => 's',
choices => \&_get_all_group_names,
{ default => 'editbugs',
name => 'chartgroup', checker => \&check_group
type => 's', },
choices => \&_get_all_group_names,
default => 'editbugs', {
checker => \&check_group name => 'insidergroup',
}, type => 's',
choices => \&_get_all_group_names,
{ default => '',
name => 'insidergroup', checker => \&check_group
type => 's', },
choices => \&_get_all_group_names,
default => '', {
checker => \&check_group name => 'timetrackinggroup',
}, type => 's',
choices => \&_get_all_group_names,
{ default => 'editbugs',
name => 'timetrackinggroup', checker => \&check_group
type => 's', },
choices => \&_get_all_group_names,
default => 'editbugs', {
checker => \&check_group name => 'querysharegroup',
}, type => 's',
choices => \&_get_all_group_names,
{ default => 'editbugs',
name => 'querysharegroup', checker => \&check_group
type => 's', },
choices => \&_get_all_group_names,
default => 'editbugs', {
checker => \&check_group name => 'comment_taggers_group',
}, type => 's',
choices => \&_get_all_group_names,
{ default => 'editbugs',
name => 'comment_taggers_group', checker => \&check_comment_taggers_group
type => 's', },
choices => \&_get_all_group_names,
default => 'editbugs', {
checker => \&check_comment_taggers_group name => 'debug_group',
}, type => 's',
choices => \&_get_all_group_names,
{ default => 'admin',
name => 'debug_group', checker => \&check_group
type => 's', },
choices => \&_get_all_group_names,
default => 'admin', {name => 'usevisibilitygroups', type => 'b', default => 0},
checker => \&check_group
}, {name => 'strict_isolation', type => 'b', default => 0},
{ {name => 'or_groups', type => 'b', default => 0}
name => 'usevisibilitygroups', );
type => 'b',
default => 0
},
{
name => 'strict_isolation',
type => 'b',
default => 0
},
{
name => 'or_groups',
type => 'b',
default => 0
} );
return @param_list; return @param_list;
} }
sub _get_all_group_names { sub _get_all_group_names {
my @group_names = map {$_->name} Bugzilla::Group->get_all; my @group_names = map { $_->name } Bugzilla::Group->get_all;
unshift(@group_names, ''); unshift(@group_names, '');
return \@group_names; return \@group_names;
} }
1; 1;
...@@ -16,49 +16,22 @@ use Bugzilla::Config::Common; ...@@ -16,49 +16,22 @@ use Bugzilla::Config::Common;
our $sortkey = 1000; our $sortkey = 1000;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'LDAPserver', type => 't', default => ''},
name => 'LDAPserver',
type => 't',
default => ''
},
{ {name => 'LDAPstarttls', type => 'b', default => 0},
name => 'LDAPstarttls',
type => 'b',
default => 0
},
{ {name => 'LDAPbinddn', type => 't', default => ''},
name => 'LDAPbinddn',
type => 't',
default => ''
},
{ {name => 'LDAPBaseDN', type => 't', default => ''},
name => 'LDAPBaseDN',
type => 't',
default => ''
},
{ {name => 'LDAPuidattribute', type => 't', default => 'uid'},
name => 'LDAPuidattribute',
type => 't',
default => 'uid'
},
{ {name => 'LDAPmailattribute', type => 't', default => 'mail'},
name => 'LDAPmailattribute',
type => 't',
default => 'mail'
},
{ {name => 'LDAPfilter', type => 't', default => '',}
name => 'LDAPfilter', );
type => 't',
default => '',
} );
return @param_list; return @param_list;
} }
......
...@@ -16,68 +16,43 @@ use Bugzilla::Config::Common; ...@@ -16,68 +16,43 @@ use Bugzilla::Config::Common;
our $sortkey = 1200; our $sortkey = 1200;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {
name => 'mail_delivery_method', name => 'mail_delivery_method',
type => 's', type => 's',
choices => ['Sendmail', 'SMTP', 'Test', 'None'], choices => ['Sendmail', 'SMTP', 'Test', 'None'],
default => 'Sendmail', default => 'Sendmail',
checker => \&check_mail_delivery_method checker => \&check_mail_delivery_method
}, },
{ {name => 'mailfrom', type => 't', default => 'bugzilla-daemon'},
name => 'mailfrom',
type => 't', {
default => 'bugzilla-daemon' name => 'use_mailer_queue',
}, type => 'b',
default => 0,
{ checker => \&check_theschwartz_available,
name => 'use_mailer_queue', },
type => 'b',
default => 0, {
checker => \&check_theschwartz_available, name => 'smtpserver',
}, type => 't',
default => 'localhost',
{ checker => \&check_smtp_server
name => 'smtpserver', },
type => 't', {
default => 'localhost', name => 'smtp_username',
checker => \&check_smtp_server type => 't',
}, default => '',
{ checker => \&check_smtp_auth
name => 'smtp_username', },
type => 't', {name => 'smtp_password', type => 'p', default => ''},
default => '', {name => 'smtp_ssl', type => 'b', default => 0, checker => \&check_smtp_ssl},
checker => \&check_smtp_auth {name => 'smtp_debug', type => 'b', default => 0},
}, {name => 'whinedays', type => 't', default => 7, checker => \&check_numeric},
{ {name => 'globalwatchers', type => 't', default => '',},
name => 'smtp_password', );
type => 'p',
default => ''
},
{
name => 'smtp_ssl',
type => 'b',
default => 0,
checker => \&check_smtp_ssl
},
{
name => 'smtp_debug',
type => 'b',
default => 0
},
{
name => 'whinedays',
type => 't',
default => 7,
checker => \&check_numeric
},
{
name => 'globalwatchers',
type => 't',
default => '',
}, );
return @param_list; return @param_list;
} }
......
...@@ -17,16 +17,8 @@ our $sortkey = 1550; ...@@ -17,16 +17,8 @@ our $sortkey = 1550;
sub get_param_list { sub get_param_list {
return ( return (
{ {name => 'memcached_servers', type => 't', default => ''},
name => 'memcached_servers', {name => 'memcached_namespace', type => 't', default => 'bugzilla:',},
type => 't',
default => ''
},
{
name => 'memcached_namespace',
type => 't',
default => 'bugzilla:',
},
); );
} }
......
...@@ -16,47 +16,45 @@ use Bugzilla::Config::Common; ...@@ -16,47 +16,45 @@ use Bugzilla::Config::Common;
our $sortkey = 1400; our $sortkey = 1400;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {
name => 'quip_list_entry_control', name => 'quip_list_entry_control',
type => 's', type => 's',
choices => ['open', 'moderated', 'closed'], choices => ['open', 'moderated', 'closed'],
default => 'open', default => 'open',
checker => \&check_multi checker => \&check_multi
}, },
{ {
name => 'mybugstemplate', name => 'mybugstemplate',
type => 't', type => 't',
default => 'buglist.cgi?resolution=---&amp;emailassigned_to1=1&amp;emailreporter1=1&amp;emailtype1=exact&amp;email1=%userid%' default =>
}, 'buglist.cgi?resolution=---&amp;emailassigned_to1=1&amp;emailreporter1=1&amp;emailtype1=exact&amp;email1=%userid%'
},
{
name => 'defaultquery', {
type => 't', name => 'defaultquery',
default => 'resolution=---&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&emaillongdesc3=1&order=Importance&long_desc_type=substring' type => 't',
}, default =>
'resolution=---&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&emaillongdesc3=1&order=Importance&long_desc_type=substring'
{ },
name => 'search_allow_no_criteria',
type => 'b', {name => 'search_allow_no_criteria', type => 'b', default => 1},
default => 1
}, {
name => 'default_search_limit',
{ type => 't',
name => 'default_search_limit', default => '500',
type => 't', checker => \&check_numeric
default => '500', },
checker => \&check_numeric
}, {
name => 'max_search_results',
{ type => 't',
name => 'max_search_results', default => '10000',
type => 't', checker => \&check_numeric
default => '10000', },
checker => \&check_numeric
},
); );
return @param_list; return @param_list;
} }
......
...@@ -16,31 +16,15 @@ use Bugzilla::Config::Common; ...@@ -16,31 +16,15 @@ use Bugzilla::Config::Common;
our $sortkey = 1100; our $sortkey = 1100;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'RADIUS_server', type => 't', default => ''},
name => 'RADIUS_server',
type => 't', {name => 'RADIUS_secret', type => 't', default => ''},
default => ''
}, {name => 'RADIUS_NAS_IP', type => 't', default => ''},
{ {name => 'RADIUS_email_suffix', type => 't', default => ''},
name => 'RADIUS_secret',
type => 't',
default => ''
},
{
name => 'RADIUS_NAS_IP',
type => 't',
default => ''
},
{
name => 'RADIUS_email_suffix',
type => 't',
default => ''
},
); );
return @param_list; return @param_list;
} }
......
...@@ -16,35 +16,23 @@ use Bugzilla::Config::Common; ...@@ -16,35 +16,23 @@ use Bugzilla::Config::Common;
our $sortkey = 1500; our $sortkey = 1500;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'shadowdbhost', type => 't', default => '',},
name => 'shadowdbhost',
type => 't', {
default => '', name => 'shadowdbport',
}, type => 't',
default => '3306',
{ checker => \&check_numeric,
name => 'shadowdbport', },
type => 't',
default => '3306', {name => 'shadowdbsock', type => 't', default => '',},
checker => \&check_numeric,
}, # This entry must be _after_ the shadowdb{host,port,sock} settings so that
# they can be used in the validation here
{ {name => 'shadowdb', type => 't', default => '', checker => \&check_shadowdb}
name => 'shadowdbsock', );
type => 't',
default => '',
},
# This entry must be _after_ the shadowdb{host,port,sock} settings so that
# they can be used in the validation here
{
name => 'shadowdb',
type => 't',
default => '',
checker => \&check_shadowdb
} );
return @param_list; return @param_list;
} }
......
...@@ -16,32 +16,21 @@ use Bugzilla::Config::Common; ...@@ -16,32 +16,21 @@ use Bugzilla::Config::Common;
our $sortkey = 1600; our $sortkey = 1600;
sub get_param_list { sub get_param_list {
my $class = shift; my $class = shift;
my @param_list = ( my @param_list = (
{ {name => 'usemenuforusers', type => 'b', default => '0'},
name => 'usemenuforusers',
type => 'b', {name => 'ajax_user_autocompletion', type => 'b', default => '1',},
default => '0'
}, {
name => 'maxusermatches',
{ type => 't',
name => 'ajax_user_autocompletion', default => '1000',
type => 'b', checker => \&check_numeric
default => '1', },
},
{name => 'confirmuniqueusermatch', type => 'b', default => 1,}
{ );
name => 'maxusermatches',
type => 't',
default => '1000',
checker => \&check_numeric
},
{
name => 'confirmuniqueusermatch',
type => 'b',
default => 1,
} );
return @param_list; return @param_list;
} }
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -26,14 +26,19 @@ sub FIELD_NAME { return $_[0]->DB_TABLE; } ...@@ -26,14 +26,19 @@ sub FIELD_NAME { return $_[0]->DB_TABLE; }
#################### ####################
sub _check_if_controller { sub _check_if_controller {
my $self = shift; my $self = shift;
my $vis_fields = $self->controls_visibility_of_fields; my $vis_fields = $self->controls_visibility_of_fields;
my $values = $self->controlled_values_array; my $values = $self->controlled_values_array;
if (@$vis_fields || @$values) { if (@$vis_fields || @$values) {
ThrowUserError('fieldvalue_is_controller', ThrowUserError(
{ value => $self, fields => [map($_->name, @$vis_fields)], 'fieldvalue_is_controller',
vals => $self->controlled_values }); {
} value => $self,
fields => [map($_->name, @$vis_fields)],
vals => $self->controlled_values
}
);
}
} }
...@@ -42,145 +47,149 @@ sub _check_if_controller { ...@@ -42,145 +47,149 @@ sub _check_if_controller {
############# #############
sub is_active { return $_[0]->{'isactive'}; } sub is_active { return $_[0]->{'isactive'}; }
sub sortkey { return $_[0]->{'sortkey'}; } sub sortkey { return $_[0]->{'sortkey'}; }
sub bug_count { sub bug_count {
my $self = shift; my $self = shift;
return $self->{bug_count} if defined $self->{bug_count}; return $self->{bug_count} if defined $self->{bug_count};
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
my $fname = $self->field->name; my $fname = $self->field->name;
my $count; my $count;
if ($self->field->type == FIELD_TYPE_MULTI_SELECT) { if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bug_$fname $count = $dbh->selectrow_array(
WHERE value = ?", undef, $self->name); "SELECT COUNT(*) FROM bug_$fname
} WHERE value = ?", undef, $self->name
else { );
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs }
WHERE $fname = ?", else {
undef, $self->name); $count = $dbh->selectrow_array(
} "SELECT COUNT(*) FROM bugs
$self->{bug_count} = $count; WHERE $fname = ?", undef, $self->name
return $count; );
}
$self->{bug_count} = $count;
return $count;
} }
sub field { sub field {
my $invocant = shift; my $invocant = shift;
my $class = ref $invocant || $invocant; my $class = ref $invocant || $invocant;
my $cache = Bugzilla->request_cache; my $cache = Bugzilla->request_cache;
# This is just to make life easier for subclasses. Our auto-generated
# subclasses from Bugzilla::Field::Choice->type() already have this set. # This is just to make life easier for subclasses. Our auto-generated
$cache->{"field_$class"} ||= # subclasses from Bugzilla::Field::Choice->type() already have this set.
new Bugzilla::Field({ name => $class->FIELD_NAME }); $cache->{"field_$class"} ||= new Bugzilla::Field({name => $class->FIELD_NAME});
return $cache->{"field_$class"}; return $cache->{"field_$class"};
} }
sub is_default { sub is_default {
my $self = shift; my $self = shift;
my $name = $self->DEFAULT_MAP->{$self->field->name}; my $name = $self->DEFAULT_MAP->{$self->field->name};
# If it doesn't exist in DEFAULT_MAP, then there is no parameter
# related to this field. # If it doesn't exist in DEFAULT_MAP, then there is no parameter
return 0 unless $name; # related to this field.
return ($self->name eq Bugzilla->params->{$name}) ? 1 : 0; return 0 unless $name;
return ($self->name eq Bugzilla->params->{$name}) ? 1 : 0;
} }
sub is_static { sub is_static {
my $self = shift; my $self = shift;
# If we need to special-case Resolution for *anything* else, it should
# get its own subclass. # If we need to special-case Resolution for *anything* else, it should
if ($self->field->name eq 'resolution') { # get its own subclass.
return grep($_ eq $self->name, ('', 'FIXED', 'DUPLICATE')) if ($self->field->name eq 'resolution') {
? 1 : 0; return grep($_ eq $self->name, ('', 'FIXED', 'DUPLICATE')) ? 1 : 0;
} }
elsif ($self->field->custom) { elsif ($self->field->custom) {
return $self->name eq '---' ? 1 : 0; return $self->name eq '---' ? 1 : 0;
} }
return 0; return 0;
} }
sub controls_visibility_of_fields { sub controls_visibility_of_fields {
my $self = shift; my $self = shift;
my $dbh = Bugzilla->dbh; my $dbh = Bugzilla->dbh;
if (!$self->{controls_visibility_of_fields}) { if (!$self->{controls_visibility_of_fields}) {
my $ids = $dbh->selectcol_arrayref( my $ids = $dbh->selectcol_arrayref(
"SELECT id FROM fielddefs "SELECT id FROM fielddefs
INNER JOIN field_visibility INNER JOIN field_visibility
ON fielddefs.id = field_visibility.field_id ON fielddefs.id = field_visibility.field_id
WHERE value_id = ? AND visibility_field_id = ?", undef, WHERE value_id = ? AND visibility_field_id = ?", undef, $self->id,
$self->id, $self->field->id); $self->field->id
);
$self->{controls_visibility_of_fields} = $self->{controls_visibility_of_fields} = Bugzilla::Field->new_from_list($ids);
Bugzilla::Field->new_from_list($ids); }
}
return $self->{controls_visibility_of_fields}; return $self->{controls_visibility_of_fields};
} }
sub visibility_value { sub visibility_value {
my $self = shift; my $self = shift;
if ($self->{visibility_value_id}) { if ($self->{visibility_value_id}) {
require Bugzilla::Field::Choice; require Bugzilla::Field::Choice;
$self->{visibility_value} ||= $self->{visibility_value}
Bugzilla::Field::Choice->type($self->field->value_field)->new( ||= Bugzilla::Field::Choice->type($self->field->value_field)
$self->{visibility_value_id}); ->new($self->{visibility_value_id});
} }
return $self->{visibility_value}; return $self->{visibility_value};
} }
sub controlled_values { sub controlled_values {
my $self = shift; my $self = shift;
return $self->{controlled_values} if defined $self->{controlled_values}; return $self->{controlled_values} if defined $self->{controlled_values};
my $fields = $self->field->controls_values_of; my $fields = $self->field->controls_values_of;
my %controlled_values; my %controlled_values;
require Bugzilla::Field::Choice; require Bugzilla::Field::Choice;
foreach my $field (@$fields) { foreach my $field (@$fields) {
$controlled_values{$field->name} = $controlled_values{$field->name} = Bugzilla::Field::Choice->type($field)
Bugzilla::Field::Choice->type($field) ->match({visibility_value_id => $self->id});
->match({ visibility_value_id => $self->id }); }
} $self->{controlled_values} = \%controlled_values;
$self->{controlled_values} = \%controlled_values; return $self->{controlled_values};
return $self->{controlled_values};
} }
sub controlled_values_array { sub controlled_values_array {
my ($self) = @_; my ($self) = @_;
my $values = $self->controlled_values; my $values = $self->controlled_values;
return [map { @{ $values->{$_} } } keys %$values]; return [map { @{$values->{$_}} } keys %$values];
} }
sub is_visible_on_bug { sub is_visible_on_bug {
my ($self, $bug) = @_; my ($self, $bug) = @_;
# Values currently set on the bug are always shown. # Values currently set on the bug are always shown.
return 1 if $self->is_set_on_bug($bug); return 1 if $self->is_set_on_bug($bug);
# Inactive values are, otherwise, never shown. # Inactive values are, otherwise, never shown.
return 0 if !$self->is_active; return 0 if !$self->is_active;
# Values without a visibility value are, otherwise, always shown. # Values without a visibility value are, otherwise, always shown.
my $visibility_value = $self->visibility_value; my $visibility_value = $self->visibility_value;
return 1 if !$visibility_value; return 1 if !$visibility_value;
# Values with a visibility value are only shown if the visibility # Values with a visibility value are only shown if the visibility
# value is set on the bug. # value is set on the bug.
return $visibility_value->is_set_on_bug($bug); return $visibility_value->is_set_on_bug($bug);
} }
sub is_set_on_bug { sub is_set_on_bug {
my ($self, $bug) = @_; my ($self, $bug) = @_;
my $field_name = $self->FIELD_NAME; my $field_name = $self->FIELD_NAME;
# This allows bug/create/create.html.tmpl to pass in a hashref that
# looks like a bug object. # This allows bug/create/create.html.tmpl to pass in a hashref that
my $value = blessed($bug) ? $bug->$field_name : $bug->{$field_name}; # looks like a bug object.
$value = $value->name if blessed($value); my $value = blessed($bug) ? $bug->$field_name : $bug->{$field_name};
return 0 if !defined $value; $value = $value->name if blessed($value);
return 0 if !defined $value;
if ($self->field->type == FIELD_TYPE_BUG_URLS
or $self->field->type == FIELD_TYPE_MULTI_SELECT) if ( $self->field->type == FIELD_TYPE_BUG_URLS
{ or $self->field->type == FIELD_TYPE_MULTI_SELECT)
return grep($_ eq $self->name, @$value) ? 1 : 0; {
} return grep($_ eq $self->name, @$value) ? 1 : 0;
return $value eq $self->name ? 1 : 0; }
return $value eq $self->name ? 1 : 0;
} }
1; 1;
......
...@@ -12,33 +12,33 @@ use strict; ...@@ -12,33 +12,33 @@ use strict;
use warnings; use warnings;
sub process { sub process {
my ($name, $args) = @_; my ($name, $args) = @_;
_entering($name); _entering($name);
foreach my $extension (@{ Bugzilla->extensions }) { foreach my $extension (@{Bugzilla->extensions}) {
if ($extension->can($name)) { if ($extension->can($name)) {
$extension->$name($args); $extension->$name($args);
}
} }
}
_leaving($name); _leaving($name);
} }
sub in { sub in {
my $hook_name = shift; my $hook_name = shift;
my $currently_in = Bugzilla->request_cache->{hook_stack}->[-1] || ''; my $currently_in = Bugzilla->request_cache->{hook_stack}->[-1] || '';
return $hook_name eq $currently_in ? 1 : 0; return $hook_name eq $currently_in ? 1 : 0;
} }
sub _entering { sub _entering {
my ($hook_name) = @_; my ($hook_name) = @_;
my $hook_stack = Bugzilla->request_cache->{hook_stack} ||= []; my $hook_stack = Bugzilla->request_cache->{hook_stack} ||= [];
push(@$hook_stack, $hook_name); push(@$hook_stack, $hook_name);
} }
sub _leaving { sub _leaving {
pop @{ Bugzilla->request_cache->{hook_stack} }; pop @{Bugzilla->request_cache->{hook_stack}};
} }
1; 1;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -15,18 +15,18 @@ use Bugzilla::BugMail; ...@@ -15,18 +15,18 @@ use Bugzilla::BugMail;
BEGIN { eval "use parent qw(Bugzilla::Job::Mailer)"; } BEGIN { eval "use parent qw(Bugzilla::Job::Mailer)"; }
sub work { sub work {
my ($class, $job) = @_; my ($class, $job) = @_;
my $success = eval { my $success = eval {
Bugzilla::BugMail::dequeue($job->arg->{vars}); Bugzilla::BugMail::dequeue($job->arg->{vars});
1; 1;
}; };
if (!$success) { if (!$success) {
$job->failed($@); $job->failed($@);
undef $@; undef $@;
} }
else { else {
$job->completed; $job->completed;
} }
} }
1; 1;
...@@ -16,31 +16,33 @@ BEGIN { eval "use parent qw(TheSchwartz::Worker)"; } ...@@ -16,31 +16,33 @@ BEGIN { eval "use parent qw(TheSchwartz::Worker)"; }
# The longest we expect a job to possibly take, in seconds. # The longest we expect a job to possibly take, in seconds.
use constant grab_for => 300; use constant grab_for => 300;
# We don't want email to fail permanently very easily. Retry for 30 days. # We don't want email to fail permanently very easily. Retry for 30 days.
use constant max_retries => 725; use constant max_retries => 725;
# The first few retries happen quickly, but after that we wait an hour for # The first few retries happen quickly, but after that we wait an hour for
# each retry. # each retry.
sub retry_delay { sub retry_delay {
my ($class, $num_retries) = @_; my ($class, $num_retries) = @_;
if ($num_retries < 5) { if ($num_retries < 5) {
return (10, 30, 60, 300, 600)[$num_retries]; return (10, 30, 60, 300, 600)[$num_retries];
} }
# One hour
return 60*60; # One hour
return 60 * 60;
} }
sub work { sub work {
my ($class, $job) = @_; my ($class, $job) = @_;
my $msg = $job->arg->{msg}; my $msg = $job->arg->{msg};
my $success = eval { MessageToMTA($msg, 1); 1; }; my $success = eval { MessageToMTA($msg, 1); 1; };
if (!$success) { if (!$success) {
$job->failed($@); $job->failed($@);
undef $@; undef $@;
} }
else { else {
$job->completed; $job->completed;
} }
} }
1; 1;
...@@ -21,153 +21,155 @@ use fields qw(_worker_pidfile); ...@@ -21,153 +21,155 @@ use fields qw(_worker_pidfile);
# This maps job names for Bugzilla::JobQueue to the appropriate modules. # This maps job names for Bugzilla::JobQueue to the appropriate modules.
# If you add new types of jobs, you should add a mapping here. # If you add new types of jobs, you should add a mapping here.
use constant JOB_MAP => { use constant JOB_MAP =>
send_mail => 'Bugzilla::Job::Mailer', {send_mail => 'Bugzilla::Job::Mailer', bug_mail => 'Bugzilla::Job::BugMail',};
bug_mail => 'Bugzilla::Job::BugMail',
};
# Without a driver cache TheSchwartz opens a new database connection # Without a driver cache TheSchwartz opens a new database connection
# for each email it sends. This cached connection doesn't persist # for each email it sends. This cached connection doesn't persist
# across requests. # across requests.
use constant DRIVER_CACHE_TIME => 300; # 5 minutes use constant DRIVER_CACHE_TIME => 300; # 5 minutes
# To avoid memory leak/fragmentation, a worker process won't process more than # To avoid memory leak/fragmentation, a worker process won't process more than
# MAX_MESSAGES messages. # MAX_MESSAGES messages.
use constant MAX_MESSAGES => 1000; use constant MAX_MESSAGES => 1000;
sub job_map { sub job_map {
if (!defined(Bugzilla->request_cache->{job_map})) { if (!defined(Bugzilla->request_cache->{job_map})) {
my $job_map = JOB_MAP; my $job_map = JOB_MAP;
Bugzilla::Hook::process('job_map', { job_map => $job_map }); Bugzilla::Hook::process('job_map', {job_map => $job_map});
Bugzilla->request_cache->{job_map} = $job_map; Bugzilla->request_cache->{job_map} = $job_map;
} }
return Bugzilla->request_cache->{job_map}; return Bugzilla->request_cache->{job_map};
} }
sub new { sub new {
my $class = shift; my $class = shift;
if (!Bugzilla->feature('jobqueue')) { if (!Bugzilla->feature('jobqueue')) {
ThrowUserError('feature_disabled', { feature => 'jobqueue' }); ThrowUserError('feature_disabled', {feature => 'jobqueue'});
} }
my $lc = Bugzilla->localconfig; my $lc = Bugzilla->localconfig;
# We need to use the main DB as TheSchwartz module is going
# to write to it. # We need to use the main DB as TheSchwartz module is going
my $self = $class->SUPER::new( # to write to it.
databases => [{ my $self = $class->SUPER::new(
dsn => Bugzilla->dbh_main->{private_bz_dsn}, databases => [{
user => $lc->{db_user}, dsn => Bugzilla->dbh_main->{private_bz_dsn},
pass => $lc->{db_pass}, user => $lc->{db_user},
prefix => 'ts_', pass => $lc->{db_pass},
}], prefix => 'ts_',
driver_cache_expiration => DRIVER_CACHE_TIME, }],
prioritize => 1, driver_cache_expiration => DRIVER_CACHE_TIME,
); prioritize => 1,
);
return $self;
return $self;
} }
# A way to get access to the underlying databases directly. # A way to get access to the underlying databases directly.
sub bz_databases { sub bz_databases {
my $self = shift; my $self = shift;
my @hashes = keys %{ $self->{databases} }; my @hashes = keys %{$self->{databases}};
return map { $self->driver_for($_) } @hashes; return map { $self->driver_for($_) } @hashes;
} }
# inserts a job into the queue to be processed and returns immediately # inserts a job into the queue to be processed and returns immediately
sub insert { sub insert {
my $self = shift; my $self = shift;
my $job = shift; my $job = shift;
if (!ref($job)) { if (!ref($job)) {
my $mapped_job = Bugzilla::JobQueue->job_map()->{$job}; my $mapped_job = Bugzilla::JobQueue->job_map()->{$job};
ThrowCodeError('jobqueue_no_job_mapping', { job => $job }) ThrowCodeError('jobqueue_no_job_mapping', {job => $job}) if !$mapped_job;
if !$mapped_job;
$job = new TheSchwartz::Job(
$job = new TheSchwartz::Job( funcname => $mapped_job,
funcname => $mapped_job, arg => $_[0],
arg => $_[0], priority => $_[1] || 5
priority => $_[1] || 5 );
); }
}
my $retval = $self->SUPER::insert($job);
my $retval = $self->SUPER::insert($job);
# XXX Need to get an error message here if insert fails, but # XXX Need to get an error message here if insert fails, but
# I don't see any way to do that in TheSchwartz. # I don't see any way to do that in TheSchwartz.
ThrowCodeError('jobqueue_insert_failed', { job => $job, errmsg => $@ }) ThrowCodeError('jobqueue_insert_failed', {job => $job, errmsg => $@})
if !$retval; if !$retval;
return $retval; return $retval;
} }
# To avoid memory leaks/fragmentation which tends to happen for long running # To avoid memory leaks/fragmentation which tends to happen for long running
# perl processes; check for jobs, and spawn a new process to empty the queue. # perl processes; check for jobs, and spawn a new process to empty the queue.
sub subprocess_worker { sub subprocess_worker {
my $self = shift; my $self = shift;
my $command = "$0 -d -p '" . $self->{_worker_pidfile} . "' onepass"; my $command = "$0 -d -p '" . $self->{_worker_pidfile} . "' onepass";
while (1) { while (1) {
my $time = (time); my $time = (time);
my @jobs = $self->list_jobs({ my @jobs = $self->list_jobs({
funcname => $self->{all_abilities}, funcname => $self->{all_abilities},
run_after => $time, run_after => $time,
grabbed_until => $time, grabbed_until => $time,
limit => 1, limit => 1,
}); });
if (@jobs) { if (@jobs) {
$self->debug("Spawning queue worker process"); $self->debug("Spawning queue worker process");
# Run the worker as a daemon
system $command; # Run the worker as a daemon
# And poll the PID to detect when the working has finished. system $command;
# We do this instead of system() to allow for the INT signal to
# interrup us and trigger kill_worker(). # And poll the PID to detect when the working has finished.
my $pid = read_text($self->{_worker_pidfile}, err_mode => 'quiet'); # We do this instead of system() to allow for the INT signal to
if ($pid) { # interrup us and trigger kill_worker().
sleep(3) while(kill(0, $pid)); my $pid = read_text($self->{_worker_pidfile}, err_mode => 'quiet');
} if ($pid) {
$self->debug("Queue worker process completed"); sleep(3) while (kill(0, $pid));
} else { }
$self->debug("No jobs found"); $self->debug("Queue worker process completed");
}
sleep(5);
} }
else {
$self->debug("No jobs found");
}
sleep(5);
}
} }
sub kill_worker { sub kill_worker {
my $self = Bugzilla->job_queue(); my $self = Bugzilla->job_queue();
if ($self->{_worker_pidfile} && -e $self->{_worker_pidfile}) { if ($self->{_worker_pidfile} && -e $self->{_worker_pidfile}) {
my $worker_pid = read_text($self->{_worker_pidfile}); my $worker_pid = read_text($self->{_worker_pidfile});
if ($worker_pid && kill(0, $worker_pid)) { if ($worker_pid && kill(0, $worker_pid)) {
$self->debug("Stopping worker process"); $self->debug("Stopping worker process");
system "$0 -f -p '" . $self->{_worker_pidfile} . "' stop"; system "$0 -f -p '" . $self->{_worker_pidfile} . "' stop";
}
} }
}
} }
sub set_pidfile { sub set_pidfile {
my ($self, $pidfile) = @_; my ($self, $pidfile) = @_;
$self->{_worker_pidfile} = bz_locations->{'datadir'} . $self->{_worker_pidfile}
'/worker-' . basename($pidfile); = bz_locations->{'datadir'} . '/worker-' . basename($pidfile);
} }
# Clear the request cache at the start of each run. # Clear the request cache at the start of each run.
sub work_once { sub work_once {
my $self = shift; my $self = shift;
Bugzilla->clear_request_cache(); Bugzilla->clear_request_cache();
return $self->SUPER::work_once(@_); return $self->SUPER::work_once(@_);
} }
# Never process more than MAX_MESSAGES in one batch, to avoid memory # Never process more than MAX_MESSAGES in one batch, to avoid memory
# leak/fragmentation issues. # leak/fragmentation issues.
sub work_until_done { sub work_until_done {
my $self = shift; my $self = shift;
my $count = 0; my $count = 0;
while ($count++ < MAX_MESSAGES) { while ($count++ < MAX_MESSAGES) {
$self->work_once or last; $self->work_once or last;
} }
} }
1; 1;
......
...@@ -28,8 +28,8 @@ BEGIN { eval "use parent qw(Daemon::Generic)"; } ...@@ -28,8 +28,8 @@ BEGIN { eval "use parent qw(Daemon::Generic)"; }
our $VERSION = BUGZILLA_VERSION; our $VERSION = BUGZILLA_VERSION;
# Info we need to install/uninstall the daemon. # Info we need to install/uninstall the daemon.
our $chkconfig = "/sbin/chkconfig"; our $chkconfig = "/sbin/chkconfig";
our $initd = "/etc/init.d"; our $initd = "/etc/init.d";
our $initscript = "bugzilla-queue"; our $initscript = "bugzilla-queue";
# The Daemon::Generic docs say that it uses all sorts of # The Daemon::Generic docs say that it uses all sorts of
...@@ -37,187 +37,188 @@ our $initscript = "bugzilla-queue"; ...@@ -37,187 +37,188 @@ our $initscript = "bugzilla-queue";
# only thing it uses from gd_preconfig is the "pidfile" # only thing it uses from gd_preconfig is the "pidfile"
# config parameter. # config parameter.
sub gd_preconfig { sub gd_preconfig {
my $self = shift; my $self = shift;
$self->{_run_command} = 'subprocess_worker'; $self->{_run_command} = 'subprocess_worker';
my $pidfile = $self->{gd_args}{pidfile}; my $pidfile = $self->{gd_args}{pidfile};
if (!$pidfile) { if (!$pidfile) {
$pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname} $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname} . ".pid";
. ".pid"; }
} return (pidfile => $pidfile);
return (pidfile => $pidfile);
} }
# All config other than the pidfile has to be done in gd_getopt # All config other than the pidfile has to be done in gd_getopt
# in order for it to be set up early enough. # in order for it to be set up early enough.
sub gd_getopt { sub gd_getopt {
my $self = shift; my $self = shift;
$self->SUPER::gd_getopt(); $self->SUPER::gd_getopt();
if ($self->{gd_args}{progname}) { if ($self->{gd_args}{progname}) {
$self->{gd_progname} = $self->{gd_args}{progname}; $self->{gd_progname} = $self->{gd_args}{progname};
} }
else { else {
$self->{gd_progname} = basename($0); $self->{gd_progname} = basename($0);
} }
# There are places that Daemon Generic's new() uses $0 instead of # There are places that Daemon Generic's new() uses $0 instead of
# gd_progname, which it really shouldn't, but this hack fixes it. # gd_progname, which it really shouldn't, but this hack fixes it.
$self->{_original_zero} = $0; $self->{_original_zero} = $0;
$0 = $self->{gd_progname}; $0 = $self->{gd_progname};
} }
sub gd_postconfig { sub gd_postconfig {
my $self = shift; my $self = shift;
# See the hack above in gd_getopt. This just reverses it
# in case anything else needs the accurate $0. # See the hack above in gd_getopt. This just reverses it
$0 = delete $self->{_original_zero}; # in case anything else needs the accurate $0.
$0 = delete $self->{_original_zero};
} }
sub gd_more_opt { sub gd_more_opt {
my $self = shift; my $self = shift;
return ( return (
'pidfile=s' => \$self->{gd_args}{pidfile}, 'pidfile=s' => \$self->{gd_args}{pidfile},
'n=s' => \$self->{gd_args}{progname}, 'n=s' => \$self->{gd_args}{progname},
); );
} }
sub gd_usage { sub gd_usage {
pod2usage({ -verbose => 0, -exitval => 'NOEXIT' }); pod2usage({-verbose => 0, -exitval => 'NOEXIT'});
return 0 return 0;
} }
sub gd_can_install { sub gd_can_install {
my $self = shift; my $self = shift;
my $source_file;
if (-e "/etc/SuSE-release") {
$source_file = "contrib/$initscript.suse";
}
else {
$source_file = "contrib/$initscript.rhel";
}
my $dest_file = "$initd/$initscript";
my $sysconfig = '/etc/sysconfig';
my $config_file = "$sysconfig/$initscript";
if (!-x $chkconfig or !-d $initd) {
return $self->SUPER::gd_can_install(@_);
}
my $source_file; return sub {
if ( -e "/etc/SuSE-release" ) { if (!-w $initd) {
$source_file = "contrib/$initscript.suse"; print "You must run the 'install' command as root.\n";
} else { return;
$source_file = "contrib/$initscript.rhel";
} }
my $dest_file = "$initd/$initscript"; if (-e $dest_file) {
my $sysconfig = '/etc/sysconfig'; print "$initscript already in $initd.\n";
my $config_file = "$sysconfig/$initscript"; }
else {
if (!-x $chkconfig or !-d $initd) { copy($source_file, $dest_file)
return $self->SUPER::gd_can_install(@_); or die "Could not copy $source_file to $dest_file: $!";
chmod(0755, $dest_file) or die "Could not change permissions on $dest_file: $!";
} }
return sub { system($chkconfig, '--add', $initscript);
if (!-w $initd) { print "$initscript installed.",
print "You must run the 'install' command as root.\n"; " To start the daemon, do \"$dest_file start\" as root.\n";
return;
} if (-d $sysconfig and -w $sysconfig) {
if (-e $dest_file) { if (-e $config_file) {
print "$initscript already in $initd.\n"; print "$config_file already exists.\n";
} return;
else { }
copy($source_file, $dest_file)
or die "Could not copy $source_file to $dest_file: $!"; open(my $config_fh, ">", $config_file)
chmod(0755, $dest_file) or die "Could not write to $config_file: $!";
or die "Could not change permissions on $dest_file: $!"; my $directory = abs_path(dirname($self->{_original_zero}));
} my $owner_id = (stat $self->{_original_zero})[4];
my $owner = getpwuid($owner_id);
system($chkconfig, '--add', $initscript); print $config_fh <<END;
print "$initscript installed.",
" To start the daemon, do \"$dest_file start\" as root.\n";
if (-d $sysconfig and -w $sysconfig) {
if (-e $config_file) {
print "$config_file already exists.\n";
return;
}
open(my $config_fh, ">", $config_file)
or die "Could not write to $config_file: $!";
my $directory = abs_path(dirname($self->{_original_zero}));
my $owner_id = (stat $self->{_original_zero})[4];
my $owner = getpwuid($owner_id);
print $config_fh <<END;
#!/bin/sh #!/bin/sh
BUGZILLA="$directory" BUGZILLA="$directory"
# This user must have write access to Bugzilla's data/ directory. # This user must have write access to Bugzilla's data/ directory.
USER=$owner USER=$owner
END END
close($config_fh); close($config_fh);
} }
else { else {
print "Please edit $dest_file to configure the daemon.\n"; print "Please edit $dest_file to configure the daemon.\n";
} }
} }
} }
sub gd_can_uninstall { sub gd_can_uninstall {
my $self = shift; my $self = shift;
if (-x $chkconfig and -d $initd) {
return sub {
if (!-e "$initd/$initscript") {
print "$initscript not installed.\n";
return;
}
system($chkconfig, '--del', $initscript);
print "$initscript disabled.",
" To stop it, run: $initd/$initscript stop\n";
}
}
return $self->SUPER::gd_can_install(@_); if (-x $chkconfig and -d $initd) {
return sub {
if (!-e "$initd/$initscript") {
print "$initscript not installed.\n";
return;
}
system($chkconfig, '--del', $initscript);
print "$initscript disabled.", " To stop it, run: $initd/$initscript stop\n";
}
}
return $self->SUPER::gd_can_install(@_);
} }
sub gd_check { sub gd_check {
my $self = shift; my $self = shift;
# Get a count of all the jobs currently in the queue. # Get a count of all the jobs currently in the queue.
my $jq = Bugzilla->job_queue(); my $jq = Bugzilla->job_queue();
my @dbs = $jq->bz_databases(); my @dbs = $jq->bz_databases();
my $count = 0; my $count = 0;
foreach my $driver (@dbs) { foreach my $driver (@dbs) {
$count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []); $count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []);
} }
print get_text('job_queue_depth', { count => $count }) . "\n"; print get_text('job_queue_depth', {count => $count}) . "\n";
} }
sub gd_setup_signals { sub gd_setup_signals {
my $self = shift; my $self = shift;
$self->SUPER::gd_setup_signals(); $self->SUPER::gd_setup_signals();
$SIG{TERM} = sub { $self->gd_quit_event(); } $SIG{TERM} = sub { $self->gd_quit_event(); }
} }
sub gd_quit_event { sub gd_quit_event {
Bugzilla->job_queue->kill_worker(); Bugzilla->job_queue->kill_worker();
exit(1); exit(1);
} }
sub gd_other_cmd { sub gd_other_cmd {
my ($self, $do, $locked) = @_; my ($self, $do, $locked) = @_;
if ($do eq "once") { if ($do eq "once") {
$self->{_run_command} = 'work_once'; $self->{_run_command} = 'work_once';
} elsif ($do eq "onepass") { }
$self->{_run_command} = 'work_until_done'; elsif ($do eq "onepass") {
} else { $self->{_run_command} = 'work_until_done';
$self->SUPER::gd_other_cmd($do, $locked); }
} else {
$self->SUPER::gd_other_cmd($do, $locked);
}
} }
sub gd_run { sub gd_run {
my $self = shift; my $self = shift;
$self->_do_work($self->{_run_command}); $self->_do_work($self->{_run_command});
} }
sub _do_work { sub _do_work {
my ($self, $fn) = @_; my ($self, $fn) = @_;
my $jq = Bugzilla->job_queue(); my $jq = Bugzilla->job_queue();
$jq->set_verbose($self->{debug}); $jq->set_verbose($self->{debug});
$jq->set_pidfile($self->{gd_pidfile}); $jq->set_pidfile($self->{gd_pidfile});
foreach my $module (values %{ Bugzilla::JobQueue->job_map() }) { foreach my $module (values %{Bugzilla::JobQueue->job_map()}) {
eval "use $module"; eval "use $module";
$jq->can_do($module); $jq->can_do($module);
} }
$jq->$fn; $jq->$fn;
} }
1; 1;
......
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