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