summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Auth
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Auth')
-rw-r--r--Bugzilla/Auth/Login.pm18
-rw-r--r--Bugzilla/Auth/Login/APIKey.pm45
-rw-r--r--Bugzilla/Auth/Login/CGI.pm118
-rw-r--r--Bugzilla/Auth/Login/Cookie.pm221
-rw-r--r--Bugzilla/Auth/Login/Env.pm29
-rw-r--r--Bugzilla/Auth/Login/Stack.pm126
-rw-r--r--Bugzilla/Auth/Persist/Cookie.pm284
-rw-r--r--Bugzilla/Auth/Verify.pm202
-rw-r--r--Bugzilla/Auth/Verify/DB.pm152
-rw-r--r--Bugzilla/Auth/Verify/LDAP.pm253
-rw-r--r--Bugzilla/Auth/Verify/RADIUS.pm58
-rw-r--r--Bugzilla/Auth/Verify/Stack.pm107
12 files changed, 842 insertions, 771 deletions
diff --git a/Bugzilla/Auth/Login.pm b/Bugzilla/Auth/Login.pm
index 49d670d93..6b0810035 100644
--- a/Bugzilla/Auth/Login.pm
+++ b/Bugzilla/Auth/Login.pm
@@ -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;
diff --git a/Bugzilla/Auth/Login/APIKey.pm b/Bugzilla/Auth/Login/APIKey.pm
index 25c2a3555..43032c584 100644
--- a/Bugzilla/Auth/Login/APIKey.pm
+++ b/Bugzilla/Auth/Login/APIKey.pm
@@ -26,41 +26,42 @@ use constant can_logout => 0;
use fields qw(app_id);
sub set_app_id {
- my ($self, $app_id) = @_;
- $self->{app_id} = $app_id;
+ my ($self, $app_id) = @_;
+ $self->{app_id} = $app_id;
}
sub app_id {
- my ($self) = @_;
- return $self->{app_id};
+ my ($self) = @_;
+ return $self->{app_id};
}
# 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();
- $self->set_app_id($api_key->app_id);
+ # 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();
+ $self->set_app_id($api_key->app_id);
+
+ return {user_id => $api_key->user_id};
}
1;
diff --git a/Bugzilla/Auth/Login/CGI.pm b/Bugzilla/Auth/Login/CGI.pm
index a813529d5..1b3b1f69e 100644
--- a/Bugzilla/Auth/Login/CGI.pm
+++ b/Bugzilla/Auth/Login/CGI.pm
@@ -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 = Bugzilla->localconfig->{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 = Bugzilla->localconfig->{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;
diff --git a/Bugzilla/Auth/Login/Cookie.pm b/Bugzilla/Auth/Login/Cookie.pm
index 9a94fe019..79456c383 100644
--- a/Bugzilla/Auth/Login/Cookie.pm
+++ b/Bugzilla/Auth/Login/Cookie.pm
@@ -23,145 +23,148 @@ 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, $is_internal);
-
- 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;
- }
- trick_taint($login_cookie) if $login_cookie;
- $self->cookie($login_cookie);
-
- # If the call is for a web service, and an api token is provided, check
- # it is valid.
- if (i_am_webservice()) {
- if (exists Bugzilla->input_params->{Bugzilla_api_token}) {
- my $api_token = Bugzilla->input_params->{Bugzilla_api_token};
- my ($token_user_id, undef, undef, $token_type)
- = Bugzilla::Token::GetTokenData($api_token);
- if (!defined $token_type
- || $token_type ne 'api_token'
- || $user_id != $token_user_id)
- {
- ThrowUserError('auth_invalid_token', { token => $api_token });
- }
- $is_internal = 1;
- }
- elsif ($login_cookie && Bugzilla->usage_mode == USAGE_MODE_REST) {
- # REST requires an api-token when using cookie authentication
- # fall back to a non-authenticated request
- $login_cookie = '';
- }
+ my ($self) = @_;
+ my $cgi = Bugzilla->cgi;
+ my $dbh = Bugzilla->dbh;
+ my ($user_id, $login_cookie, $is_internal);
+
+ 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;
+ }
+ trick_taint($login_cookie) if $login_cookie;
+ $self->cookie($login_cookie);
+
+ # If the call is for a web service, and an api token is provided, check
+ # it is valid.
+ if (i_am_webservice()) {
+ if (exists Bugzilla->input_params->{Bugzilla_api_token}) {
+ my $api_token = Bugzilla->input_params->{Bugzilla_api_token};
+ my ($token_user_id, undef, undef, $token_type)
+ = Bugzilla::Token::GetTokenData($api_token);
+ if ( !defined $token_type
+ || $token_type ne 'api_token'
+ || $user_id != $token_user_id)
+ {
+ ThrowUserError('auth_invalid_token', {token => $api_token});
}
+ $is_internal = 1;
+ }
+ elsif ($login_cookie && Bugzilla->usage_mode == USAGE_MODE_REST) {
+
+ # REST requires an api-token when using cookie authentication
+ # fall back to a non-authenticated request
+ $login_cookie = '';
+ }
}
+ }
- # If 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 no cookies were provided, we also look for a login token
+ # passed in the parameters of a webservice
+ my $token = $self->login_token;
+ if ($token && (!$login_cookie || !$user_id)) {
+ ($user_id, $login_cookie) = ($token->{'user_id'}, $token->{'login_token'});
+ }
+
+ my $ip_addr = remote_ip();
- my $ip_addr = remote_ip();
+ if ($login_cookie && $user_id) {
- if ($login_cookie && $user_id) {
- # Anything goes for these params - they're just strings which
- # we're going to verify against the db
- trick_taint($ip_addr);
- trick_taint($login_cookie);
- detaint_natural($user_id);
+ # Anything goes for these params - they're just strings which
+ # we're going to verify against the db
+ trick_taint($ip_addr);
+ trick_taint($login_cookie);
+ detaint_natural($user_id);
- my $db_cookie =
- $dbh->selectrow_array('SELECT cookie
+ my $db_cookie = $dbh->selectrow_array(
+ 'SELECT cookie
FROM logincookies
WHERE cookie = ?
AND userid = ?
AND (restrict_ipaddr = 0 OR ipaddr = ?)',
- undef, ($login_cookie, $user_id, $ip_addr));
-
- # If the cookie is valid, return a valid username.
- if (defined $db_cookie && $login_cookie eq $db_cookie) {
-
- # forbid logging in with a cookie if only api-keys are allowed
- if (i_am_webservice() && !$is_internal) {
- my $user = Bugzilla::User->new({ id => $user_id, cache => 1 });
- if ($user->settings->{api_key_only}->{value} eq 'on') {
- ThrowUserError('invalid_cookies_or_token');
- }
- }
-
- # 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');
+ undef, ($login_cookie, $user_id, $ip_addr)
+ );
+
+ # If the cookie is valid, return a valid username.
+ if (defined $db_cookie && $login_cookie eq $db_cookie) {
+
+ # forbid logging in with a cookie if only api-keys are allowed
+ if (i_am_webservice() && !$is_internal) {
+ my $user = Bugzilla::User->new({id => $user_id, cache => 1});
+ if ($user->settings->{api_key_only}->{value} eq 'on') {
+ ThrowUserError('invalid_cookies_or_token');
}
+ }
+
+ # 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};
}
sub cookie {
- my ($self, $val) = @_;
- $self->{_cookie} = $val if @_ > 1;
+ my ($self, $val) = @_;
+ $self->{_cookie} = $val if @_ > 1;
- return $self->{_cookie};
+ return $self->{_cookie};
}
1;
diff --git a/Bugzilla/Auth/Login/Env.pm b/Bugzilla/Auth/Login/Env.pm
index a4de8c638..edcc269bb 100644
--- a/Bugzilla/Auth/Login/Env.pm
+++ b/Bugzilla/Auth/Login/Env.pm
@@ -16,29 +16,32 @@ use base 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 $dbh = Bugzilla->dbh;
+ my ($self) = @_;
+ my $dbh = Bugzilla->dbh;
- 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;
diff --git a/Bugzilla/Auth/Login/Stack.pm b/Bugzilla/Auth/Login/Stack.pm
index d44ebbd46..7786f26c8 100644
--- a/Bugzilla/Auth/Login/Stack.pm
+++ b/Bugzilla/Auth/Login/Stack.pm
@@ -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;
diff --git a/Bugzilla/Auth/Persist/Cookie.pm b/Bugzilla/Auth/Persist/Cookie.pm
index 57473f551..65e6a1541 100644
--- a/Bugzilla/Auth/Persist/Cookie.pm
+++ b/Bugzilla/Auth/Persist/Cookie.pm
@@ -21,150 +21,166 @@ use List::Util qw(first);
use List::MoreUtils qw(any);
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;
-
- $dbh->bz_start_transaction();
-
- my $login_cookie =
- Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
-
- my $ip_addr = remote_ip();
- trick_taint($ip_addr);
- my $restrict = $input_params->{Bugzilla_restrictlogin} ? 1 : 0;
-
- $dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused, restrict_ipaddr)
- VALUES (?, ?, ?, NOW(), ?)",
- undef, $login_cookie, $user->id, $ip_addr, $restrict);
-
- # 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();
-
- # 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->remove_cookie('github_secret');
- $cgi->remove_cookie('Bugzilla_login_request_cookie');
- $cgi->send_cookie(-name => 'Bugzilla_login',
- -value => $user->id,
- %cookieargs);
- $cgi->send_cookie(-name => 'Bugzilla_logincookie',
- -value => $login_cookie,
- %cookieargs);
-
- my $securemail_groups = Bugzilla->can('securemail_groups') ? Bugzilla->securemail_groups : [ 'admin' ];
-
- if (any { $user->in_group($_) } 'mozilla-employee-confidential', @$securemail_groups) {
- my $auth_method = eval { ref($user->authorizer->successful_info_getter) } // 'unknown';
-
- Bugzilla->audit(sprintf "successful login of %s from %s using \"%s\", authenticated by %s",
- $user->login, $ip_addr, $cgi->user_agent // '', $auth_method);
- }
-
- return $login_cookie;
+ my ($self, $user) = @_;
+ my $dbh = Bugzilla->dbh;
+ my $cgi = Bugzilla->cgi;
+ my $input_params = Bugzilla->input_params;
+
+ $dbh->bz_start_transaction();
+
+ my $login_cookie
+ = Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
+
+ my $ip_addr = remote_ip();
+ trick_taint($ip_addr);
+ my $restrict = $input_params->{Bugzilla_restrictlogin} ? 1 : 0;
+
+ $dbh->do(
+ "INSERT INTO logincookies (cookie, userid, ipaddr, lastused, restrict_ipaddr)
+ VALUES (?, ?, ?, NOW(), ?)", undef, $login_cookie, $user->id,
+ $ip_addr, $restrict
+ );
+
+ # 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();
+
+ # 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->remove_cookie('github_secret');
+ $cgi->remove_cookie('Bugzilla_login_request_cookie');
+ $cgi->send_cookie(-name => 'Bugzilla_login', -value => $user->id, %cookieargs);
+ $cgi->send_cookie(
+ -name => 'Bugzilla_logincookie',
+ -value => $login_cookie,
+ %cookieargs
+ );
+
+ my $securemail_groups
+ = Bugzilla->can('securemail_groups')
+ ? Bugzilla->securemail_groups
+ : ['admin'];
+
+ if (any { $user->in_group($_) } 'mozilla-employee-confidential',
+ @$securemail_groups)
+ {
+ my $auth_method
+ = eval { ref($user->authorizer->successful_info_getter) } // 'unknown';
+
+ Bugzilla->audit(
+ sprintf "successful login of %s from %s using \"%s\", authenticated by %s",
+ $user->login, $ip_addr, $cgi->user_agent // '', $auth_method);
+ }
+
+ return $login_cookie;
}
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);
- }
- else {
- push(@login_cookies, $cgi->cookie("Bugzilla_logincookie"));
- }
-
- # 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);
+ }
+ else {
+ push(@login_cookies, $cgi->cookie("Bugzilla_logincookie"));
+ }
+
+ # 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;
diff --git a/Bugzilla/Auth/Verify.pm b/Bugzilla/Auth/Verify.pm
index 5895534cd..20782e633 100644
--- a/Bugzilla/Auth/Verify.pm
+++ b/Bugzilla/Auth/Verify.pm
@@ -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} };
- # 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({ id => $user_id, cache => 1 });
-
- # Now that we have a valid User, we need to see if any data has to be
- # updated.
- my $user_updated = 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);
- $user_updated = 1;
+ # 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 ($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);
- $user_updated = 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}
+ };
+
+ # 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});
}
- $user->update() if $user_updated;
- 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({id => $user_id, cache => 1});
+
+ # Now that we have a valid User, we need to see if any data has to be
+ # updated.
+ my $user_updated = 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);
+ $user_updated = 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);
+ $user_updated = 1;
+ }
+ $user->update() if $user_updated;
+
+ return {user => $user};
}
1;
diff --git a/Bugzilla/Auth/Verify/DB.pm b/Bugzilla/Auth/Verify/DB.pm
index e46d1cd82..9251fa893 100644
--- a/Bugzilla/Auth/Verify/DB.pm
+++ b/Bugzilla/Auth/Verify/DB.pm
@@ -19,97 +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};
- return { failure => AUTH_NODATA } unless defined $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 ($user->account_is_locked_out) {
+ return {failure => AUTH_LOCKOUT, user => $user};
+ }
- if ($entered_password_crypted ne $real_password_crypted) {
- # Record the login failure
- $user->note_login_failure();
+ my $password = $login_data->{password};
+ return {failure => AUTH_NODATA} unless defined $login_data->{password};
+ my $real_password_crypted = $user->cryptpassword;
- # Immediately check if we are locked out
- if ($user->account_is_locked_out) {
- return { failure => AUTH_LOCKOUT, user => $user,
- just_locked_out => 1 };
- }
+ # Using the internal crypted password as the salt,
+ # crypt the password the user entered.
+ my $entered_password_crypted = bz_crypt($password, $real_password_crypted);
- return { failure => AUTH_LOGINFAILED,
- failure_count => scalar(@{ $user->account_ip_login_failures }),
- };
- }
+ if ($entered_password_crypted ne $real_password_crypted) {
- # 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 $pwqc = Bugzilla->passwdqc;
- unless ($pwqc->validate_password($password)) {
- my $reason = $pwqc->reason;
- Bugzilla->audit(sprintf "%s logged in with a weak password (reason: %s)", $user->login, $reason);
- $user->set_password_change_required(1);
- $user->set_password_change_reason(
- "You must change your password for the following reason: $reason"
- );
- $user->update();
- }
- }
+ # Record the login failure
+ $user->note_login_failure();
- # 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();
-
- # 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;
- if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/) {
- # 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();
+ # Immediately check if we are locked out
+ if ($user->account_is_locked_out) {
+ return {failure => AUTH_LOCKOUT, user => $user, just_locked_out => 1};
}
- if (i_am_webservice() && $user->settings->{api_key_only}->{value} eq 'on') {
- # api-key verification happens in Auth/Login/APIKey
- # token verification happens in Auth/Login/Cookie
- # if we get here from an api call then we must be using user/pass
- return {
- failure => AUTH_ERROR,
- user_error => 'invalid_auth_method',
- };
+ 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 $pwqc = Bugzilla->passwdqc;
+ unless ($pwqc->validate_password($password)) {
+ my $reason = $pwqc->reason;
+ Bugzilla->audit(sprintf "%s logged in with a weak password (reason: %s)",
+ $user->login, $reason);
+ $user->set_password_change_required(1);
+ $user->set_password_change_reason(
+ "You must change your password for the following reason: $reason");
+ $user->update();
}
-
- return $login_data;
+ }
+
+ # 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();
+
+ # 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;
+ if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/) {
+
+ # 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 (i_am_webservice() && $user->settings->{api_key_only}->{value} eq 'on') {
+
+ # api-key verification happens in Auth/Login/APIKey
+ # token verification happens in Auth/Login/Cookie
+ # if we get here from an api call then we must be using user/pass
+ return {failure => AUTH_ERROR, user_error => 'invalid_auth_method',};
+ }
+
+ 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;
diff --git a/Bugzilla/Auth/Verify/LDAP.pm b/Bugzilla/Auth/Verify/LDAP.pm
index de88f9a43..e9aca17e8 100644
--- a/Bugzilla/Auth/Verify/LDAP.pm
+++ b/Bugzilla/Auth/Verify/LDAP.pm
@@ -13,7 +13,7 @@ use warnings;
use base qw(Bugzilla::Auth::Verify);
use fields qw(
- ldap
+ ldap
);
use Bugzilla::Constants;
@@ -28,126 +28,139 @@ use constant admin_can_create_account => 0;
use constant user_can_create_account => 0;
sub check_credentials {
- my ($self, $params) = @_;
- my $dbh = Bugzilla->dbh;
-
- # We need to bind anonymously to the LDAP server. This is
- # because we need to get the Distinguished Name of the user trying
- # to log in. Some servers (such as iPlanet) allow you to have unique
- # uids spread out over a subtree of an area (such as "People"), so
- # just appending the Base DN to the uid isn't sufficient to get the
- # user's DN. For servers which don't work this way, there will still
- # be no harm done.
+ my ($self, $params) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ # We need to bind anonymously to the LDAP server. This is
+ # because we need to get the Distinguished Name of the user trying
+ # to log in. Some servers (such as iPlanet) allow you to have unique
+ # uids spread out over a subtree of an area (such as "People"), so
+ # just appending the Base DN to the uid isn't sufficient to get the
+ # user's DN. For servers which don't work this way, there will still
+ # be no harm done.
+ $self->_bind_ldap_for_search();
+
+ # Now, we verify that the user exists, and get a LDAP Distinguished
+ # Name for the user.
+ my $username = $params->{username};
+ my $dn_result
+ = $self->ldap->search(_bz_search_params($username), attrs => ['dn']);
+ return {
+ failure => AUTH_ERROR,
+ error => "ldap_search_error",
+ details => {errstr => $dn_result->error, username => $username}
+ }
+ if $dn_result->code;
+
+ return {failure => AUTH_NO_SUCH_USER} if !$dn_result->count;
+
+ my $dn = $dn_result->shift_entry->dn;
+
+ # Check the password.
+ my $pw_result = $self->ldap->bind($dn, password => $params->{password});
+ return {failure => AUTH_LOGINFAILED} if $pw_result->code;
+
+ # And now we fill in the user's details.
+
+ # First try the search as the (already bound) user in question.
+ my $user_entry;
+ my $error_string;
+ my $detail_result = $self->ldap->search(_bz_search_params($username));
+ if ($detail_result->code) {
+
+ # Stash away the original error, just in case
+ $error_string = $detail_result->error;
+ }
+ else {
+ $user_entry = $detail_result->shift_entry;
+ }
+
+ # If that failed (either because the search failed, or returned no
+ # results) then try re-binding as the initial search user, but only
+ # if the LDAPbinddn parameter is set.
+ if (!$user_entry && Bugzilla->params->{"LDAPbinddn"}) {
$self->_bind_ldap_for_search();
- # Now, we verify that the user exists, and get a LDAP Distinguished
- # Name for the user.
- my $username = $params->{username};
- my $dn_result = $self->ldap->search(_bz_search_params($username),
- attrs => ['dn']);
- return { failure => AUTH_ERROR, error => "ldap_search_error",
- details => {errstr => $dn_result->error, username => $username}
- } if $dn_result->code;
-
- return { failure => AUTH_NO_SUCH_USER } if !$dn_result->count;
-
- my $dn = $dn_result->shift_entry->dn;
-
- # Check the password.
- my $pw_result = $self->ldap->bind($dn, password => $params->{password});
- return { failure => AUTH_LOGINFAILED } if $pw_result->code;
-
- # And now we fill in the user's details.
-
- # First try the search as the (already bound) user in question.
- my $user_entry;
- my $error_string;
- my $detail_result = $self->ldap->search(_bz_search_params($username));
- if ($detail_result->code) {
- # Stash away the original error, just in case
- $error_string = $detail_result->error;
- } else {
- $user_entry = $detail_result->shift_entry;
+ $detail_result = $self->ldap->search(_bz_search_params($username));
+ if (!$detail_result->code) {
+ $user_entry = $detail_result->shift_entry;
}
+ }
- # If that failed (either because the search failed, or returned no
- # results) then try re-binding as the initial search user, but only
- # if the LDAPbinddn parameter is set.
- if (!$user_entry && Bugzilla->params->{"LDAPbinddn"}) {
- $self->_bind_ldap_for_search();
-
- $detail_result = $self->ldap->search(_bz_search_params($username));
- if (!$detail_result->code) {
- $user_entry = $detail_result->shift_entry;
- }
+ # If we *still* don't have anything in $user_entry then give up.
+ return {
+ failure => AUTH_ERROR,
+ error => "ldap_search_error",
+ details => {errstr => $error_string, username => $username}
}
+ if !$user_entry;
- # If we *still* don't have anything in $user_entry then give up.
- return { failure => AUTH_ERROR, error => "ldap_search_error",
- details => {errstr => $error_string, username => $username}
- } if !$user_entry;
+ my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
+ if ($mail_attr) {
+ if (!$user_entry->exists($mail_attr)) {
+ return {
+ failure => AUTH_ERROR,
+ error => "ldap_cannot_retrieve_attr",
+ details => {attr => $mail_attr}
+ };
+ }
- my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
- if ($mail_attr) {
- if (!$user_entry->exists($mail_attr)) {
- return { failure => AUTH_ERROR,
- error => "ldap_cannot_retrieve_attr",
- details => {attr => $mail_attr} };
- }
+ my @emails = $user_entry->get_value($mail_attr);
- my @emails = $user_entry->get_value($mail_attr);
+ # Default to the first email address returned.
+ $params->{bz_username} = $emails[0];
- # Default to the first email address returned.
- $params->{bz_username} = $emails[0];
+ if (@emails > 1) {
- if (@emails > 1) {
- # Cycle through the adresses and check if they're Bugzilla logins.
- # Use the first one that returns a valid id.
- foreach my $email (@emails) {
- if ( login_to_id($email) ) {
- $params->{bz_username} = $email;
- last;
- }
- }
+ # Cycle through the adresses and check if they're Bugzilla logins.
+ # Use the first one that returns a valid id.
+ foreach my $email (@emails) {
+ if (login_to_id($email)) {
+ $params->{bz_username} = $email;
+ last;
}
-
- } else {
- $params->{bz_username} = $username;
+ }
}
- $params->{realname} ||= $user_entry->get_value("displayName");
- $params->{realname} ||= $user_entry->get_value("cn");
+ }
+ else {
+ $params->{bz_username} = $username;
+ }
+
+ $params->{realname} ||= $user_entry->get_value("displayName");
+ $params->{realname} ||= $user_entry->get_value("cn");
- $params->{extern_id} = $username;
+ $params->{extern_id} = $username;
- return $params;
+ return $params;
}
sub _bz_search_params {
- my ($username) = @_;
- $username = escape_filter_value($username);
- return (base => Bugzilla->params->{"LDAPBaseDN"},
- scope => "sub",
- filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"}
- . "=$username)"
- . Bugzilla->params->{"LDAPfilter"} . ')');
+ my ($username) = @_;
+ $username = escape_filter_value($username);
+ return (
+ base => Bugzilla->params->{"LDAPBaseDN"},
+ scope => "sub",
+ filter => '(&('
+ . Bugzilla->params->{"LDAPuidattribute"}
+ . "=$username)"
+ . Bugzilla->params->{"LDAPfilter"} . ')'
+ );
}
sub _bind_ldap_for_search {
- my ($self) = @_;
- my $bind_result;
- if (Bugzilla->params->{"LDAPbinddn"}) {
- my ($LDAPbinddn,$LDAPbindpass) =
- split(":",Bugzilla->params->{"LDAPbinddn"});
- $bind_result =
- $self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
- }
- else {
- $bind_result = $self->ldap->bind();
- }
- ThrowCodeError("ldap_bind_failed", {errstr => $bind_result->error})
- if $bind_result->code;
+ my ($self) = @_;
+ my $bind_result;
+ if (Bugzilla->params->{"LDAPbinddn"}) {
+ my ($LDAPbinddn, $LDAPbindpass) = split(":", Bugzilla->params->{"LDAPbinddn"});
+ $bind_result = $self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
+ }
+ else {
+ $bind_result = $self->ldap->bind();
+ }
+ ThrowCodeError("ldap_bind_failed", {errstr => $bind_result->error})
+ if $bind_result->code;
}
# We can't just do this in new(), because we're not allowed to throw any
@@ -156,27 +169,27 @@ sub _bind_ldap_for_search {
# to fix his mistake. (Because Bugzilla->login always calls
# Bugzilla::Auth->new, and almost every page calls Bugzilla->login.)
sub ldap {
- my ($self) = @_;
- return $self->{ldap} if $self->{ldap};
-
- my @servers = split(/[\s,]+/, Bugzilla->params->{"LDAPserver"});
- ThrowCodeError("ldap_server_not_defined") unless @servers;
-
- foreach (@servers) {
- $self->{ldap} = new Net::LDAP(trim($_));
- last if $self->{ldap};
- }
- ThrowCodeError("ldap_connect_failed", { server => join(", ", @servers) })
- unless $self->{ldap};
-
- # try to start TLS if needed
- if (Bugzilla->params->{"LDAPstarttls"}) {
- my $mesg = $self->{ldap}->start_tls();
- ThrowCodeError("ldap_start_tls_failed", { error => $mesg->error() })
- if $mesg->code();
- }
-
- return $self->{ldap};
+ my ($self) = @_;
+ return $self->{ldap} if $self->{ldap};
+
+ my @servers = split(/[\s,]+/, Bugzilla->params->{"LDAPserver"});
+ ThrowCodeError("ldap_server_not_defined") unless @servers;
+
+ foreach (@servers) {
+ $self->{ldap} = new Net::LDAP(trim($_));
+ last if $self->{ldap};
+ }
+ ThrowCodeError("ldap_connect_failed", {server => join(", ", @servers)})
+ unless $self->{ldap};
+
+ # try to start TLS if needed
+ if (Bugzilla->params->{"LDAPstarttls"}) {
+ my $mesg = $self->{ldap}->start_tls();
+ ThrowCodeError("ldap_start_tls_failed", {error => $mesg->error()})
+ if $mesg->code();
+ }
+
+ return $self->{ldap};
}
1;
diff --git a/Bugzilla/Auth/Verify/RADIUS.pm b/Bugzilla/Auth/Verify/RADIUS.pm
index ad0778e39..a2a54b944 100644
--- a/Bugzilla/Auth/Verify/RADIUS.pm
+++ b/Bugzilla/Auth/Verify/RADIUS.pm
@@ -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;
diff --git a/Bugzilla/Auth/Verify/Stack.pm b/Bugzilla/Auth/Verify/Stack.pm
index 3e5db3cec..9a9412915 100644
--- a/Bugzilla/Auth/Verify/Stack.pm
+++ b/Bugzilla/Auth/Verify/Stack.pm
@@ -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;