summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Auth.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Bugzilla/Auth.pm')
-rw-r--r--Bugzilla/Auth.pm451
1 files changed, 228 insertions, 223 deletions
diff --git a/Bugzilla/Auth.pm b/Bugzilla/Auth.pm
index 58ac248c5..c40b3582e 100644
--- a/Bugzilla/Auth.pm
+++ b/Bugzilla/Auth.pm
@@ -12,9 +12,9 @@ use strict;
use warnings;
use fields qw(
- _info_getter
- _verifier
- _persister
+ _info_getter
+ _verifier
+ _persister
);
use Bugzilla::Constants;
@@ -30,276 +30,281 @@ use URI;
use URI::QueryParam;
sub new {
- my ($class, $params) = @_;
- my $self = fields::new($class);
+ my ($class, $params) = @_;
+ my $self = fields::new($class);
- $params ||= {};
- $params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie,APIKey';
- $params->{Verify} ||= Bugzilla->params->{'user_verify_class'};
+ $params ||= {};
+ $params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie,APIKey';
+ $params->{Verify} ||= Bugzilla->params->{'user_verify_class'};
- $self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
- $self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
- # If we ever have any other login persistence methods besides cookies,
- # this could become more configurable.
- $self->{_persister} = new Bugzilla::Auth::Persist::Cookie();
+ $self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
+ $self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
- return $self;
+ # If we ever have any other login persistence methods besides cookies,
+ # this could become more configurable.
+ $self->{_persister} = new Bugzilla::Auth::Persist::Cookie();
+
+ return $self;
}
sub login {
- my ($self, $type) = @_;
- my $dbh = Bugzilla->dbh;
-
- # Get login info from the cookie, form, environment variables, etc.
- my $login_info = $self->{_info_getter}->get_login_info();
+ my ($self, $type) = @_;
+ my $dbh = Bugzilla->dbh;
- if ($login_info->{failure}) {
- return $self->_handle_login_result($login_info, $type);
- }
+ # Get login info from the cookie, form, environment variables, etc.
+ my $login_info = $self->{_info_getter}->get_login_info();
- # Now verify his username and password against the DB, LDAP, etc.
- if ($self->{_info_getter}->{successful}->requires_verification) {
- $login_info = $self->{_verifier}->check_credentials($login_info);
- if ($login_info->{failure}) {
- return $self->_handle_login_result($login_info, $type);
- }
- $login_info =
- $self->{_verifier}->{successful}->create_or_update_user($login_info);
- }
- else {
- $login_info = $self->{_verifier}->create_or_update_user($login_info);
- }
+ if ($login_info->{failure}) {
+ return $self->_handle_login_result($login_info, $type);
+ }
+ # Now verify his username and password against the DB, LDAP, etc.
+ if ($self->{_info_getter}->{successful}->requires_verification) {
+ $login_info = $self->{_verifier}->check_credentials($login_info);
if ($login_info->{failure}) {
- return $self->_handle_login_result($login_info, $type);
- }
-
- # Make sure the user isn't disabled.
- my $user = $login_info->{user};
- if (!$user->is_enabled) {
- return $self->_handle_login_result({ failure => AUTH_DISABLED,
- user => $user }, $type);
- }
- $user->set_authorizer($self);
-
- # trigger multi-factor auth
- if ($self->{_info_getter}->{successful}->requires_verification
- && $user->mfa
- && !Bugzilla->sudoer
- && !i_am_webservice()
- ) {
- my $params = Bugzilla->input_params;
- my $cgi = Bugzilla->cgi;
- my $uri = URI->new($cgi->self_url);
- foreach my $param (qw( Bugzilla_remember Bugzilla_restrictlogin GoAheadAndLogIn )) {
- $uri->query_param_delete($param);
- }
- $user->mfa_provider->verify_prompt({
- user => $user,
- type => $type,
- reason => 'Logging in as ' . $user->identity,
- restrictlogin => $params->{Bugzilla_restrictlogin},
- remember => $params->{Bugzilla_remember},
- url => $uri->as_string,
- postback => {
- action => 'token.cgi',
- token_field => 't',
- fields => {
- a => 'mfa_l',
- },
- }
- });
+ return $self->_handle_login_result($login_info, $type);
}
-
-
-
+ $login_info
+ = $self->{_verifier}->{successful}->create_or_update_user($login_info);
+ }
+ else {
+ $login_info = $self->{_verifier}->create_or_update_user($login_info);
+ }
+
+ if ($login_info->{failure}) {
return $self->_handle_login_result($login_info, $type);
+ }
+
+ # Make sure the user isn't disabled.
+ my $user = $login_info->{user};
+ if (!$user->is_enabled) {
+ return $self->_handle_login_result({failure => AUTH_DISABLED, user => $user},
+ $type);
+ }
+ $user->set_authorizer($self);
+
+ # trigger multi-factor auth
+ if ( $self->{_info_getter}->{successful}->requires_verification
+ && $user->mfa
+ && !Bugzilla->sudoer
+ && !i_am_webservice())
+ {
+ my $params = Bugzilla->input_params;
+ my $cgi = Bugzilla->cgi;
+ my $uri = URI->new($cgi->self_url);
+ foreach
+ my $param (qw( Bugzilla_remember Bugzilla_restrictlogin GoAheadAndLogIn ))
+ {
+ $uri->query_param_delete($param);
+ }
+ $user->mfa_provider->verify_prompt({
+ user => $user,
+ type => $type,
+ reason => 'Logging in as ' . $user->identity,
+ restrictlogin => $params->{Bugzilla_restrictlogin},
+ remember => $params->{Bugzilla_remember},
+ url => $uri->as_string,
+ postback =>
+ {action => 'token.cgi', token_field => 't', fields => {a => 'mfa_l',},}
+ });
+ }
+
+
+ return $self->_handle_login_result($login_info, $type);
}
sub mfa_verified {
- my ($self, $user, $event) = @_;
- require Bugzilla::Auth::Login::CGI;
+ my ($self, $user, $event) = @_;
+ require Bugzilla::Auth::Login::CGI;
- my $params = Bugzilla->input_params;
- $self->{_info_getter}->{successful} = Bugzilla::Auth::Login::CGI->new();
- $params->{Bugzilla_restrictlogin} = $event->{restrictlogin} if defined $event->{restrictlogin};
- $params->{Bugzilla_remember} = $event->{remember} if defined $event->{remember};
+ my $params = Bugzilla->input_params;
+ $self->{_info_getter}->{successful} = Bugzilla::Auth::Login::CGI->new();
+ $params->{Bugzilla_restrictlogin} = $event->{restrictlogin}
+ if defined $event->{restrictlogin};
+ $params->{Bugzilla_remember} = $event->{remember} if defined $event->{remember};
- $self->_handle_login_result({ user => $user }, $event->{type});
+ $self->_handle_login_result({user => $user}, $event->{type});
}
sub successful_info_getter {
- my ($self) = @_;
+ my ($self) = @_;
- return $self->{_info_getter}->{successful};
+ return $self->{_info_getter}->{successful};
}
sub can_change_password {
- my ($self) = @_;
- my $verifier = $self->{_verifier}->{successful};
- $verifier ||= $self->{_verifier};
- my $getter = $self->{_info_getter}->{successful};
- $getter = $self->{_info_getter}
- if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
- return $verifier->can_change_password &&
- $getter->user_can_create_account;
+ my ($self) = @_;
+ my $verifier = $self->{_verifier}->{successful};
+ $verifier ||= $self->{_verifier};
+ my $getter = $self->{_info_getter}->{successful};
+ $getter = $self->{_info_getter}
+ if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
+ return $verifier->can_change_password && $getter->user_can_create_account;
}
sub can_login {
- my ($self) = @_;
- my $getter = $self->{_info_getter}->{successful};
- $getter = $self->{_info_getter}
- if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
- return $getter->can_login;
+ my ($self) = @_;
+ my $getter = $self->{_info_getter}->{successful};
+ $getter = $self->{_info_getter}
+ if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
+ return $getter->can_login;
}
sub can_logout {
- my ($self) = @_;
- my $getter = $self->{_info_getter}->{successful};
- # If there's no successful getter, we're not logged in, so of
- # course we can't log out!
- return 0 unless $getter;
- return $getter->can_logout;
+ my ($self) = @_;
+ my $getter = $self->{_info_getter}->{successful};
+
+ # If there's no successful getter, we're not logged in, so of
+ # course we can't log out!
+ return 0 unless $getter;
+ return $getter->can_logout;
}
sub login_token {
- my ($self) = @_;
- my $getter = $self->{_info_getter}->{successful};
- if ($getter && $getter->isa('Bugzilla::Auth::Login::Cookie')) {
- return $getter->login_token;
- }
- return undef;
+ my ($self) = @_;
+ my $getter = $self->{_info_getter}->{successful};
+ if ($getter && $getter->isa('Bugzilla::Auth::Login::Cookie')) {
+ return $getter->login_token;
+ }
+ return undef;
}
sub user_can_create_account {
- my ($self) = @_;
- my $verifier = $self->{_verifier}->{successful};
- $verifier ||= $self->{_verifier};
- my $getter = $self->{_info_getter}->{successful};
- $getter = $self->{_info_getter}
- if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
- return $verifier->user_can_create_account
- && $getter->user_can_create_account;
+ my ($self) = @_;
+ my $verifier = $self->{_verifier}->{successful};
+ $verifier ||= $self->{_verifier};
+ my $getter = $self->{_info_getter}->{successful};
+ $getter = $self->{_info_getter}
+ if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
+ return $verifier->user_can_create_account && $getter->user_can_create_account;
}
sub extern_id_used {
- my ($self) = @_;
- return $self->{_info_getter}->extern_id_used
- || $self->{_verifier}->extern_id_used;
+ my ($self) = @_;
+ return $self->{_info_getter}->extern_id_used
+ || $self->{_verifier}->extern_id_used;
}
sub can_change_email {
- return $_[0]->user_can_create_account;
+ return $_[0]->user_can_create_account;
}
sub _handle_login_result {
- my ($self, $result, $login_type) = @_;
- my $dbh = Bugzilla->dbh;
-
- my $user = $result->{user};
- my $fail_code = $result->{failure};
-
- if (!$fail_code) {
- # We don't persist logins over GET requests in the WebService,
- # because the persistance information can't be re-used again.
- # (See Bugzilla::WebService::Server::JSONRPC for more info.)
- if ($self->{_info_getter}->{successful}->requires_persistence
- and !(
- Bugzilla->request_cache->{auth_no_automatic_login}
- || Bugzilla->request_cache->{dont_persist_session}
- )
- ) {
- $user->{_login_token} = $self->{_persister}->persist_login($user);
- }
- }
- elsif ($fail_code == AUTH_ERROR) {
- if ($result->{user_error}) {
- ThrowUserError($result->{user_error}, $result->{details});
- }
- else {
- ThrowCodeError($result->{error}, $result->{details});
- }
+ my ($self, $result, $login_type) = @_;
+ my $dbh = Bugzilla->dbh;
+
+ my $user = $result->{user};
+ my $fail_code = $result->{failure};
+
+ if (!$fail_code) {
+
+ # We don't persist logins over GET requests in the WebService,
+ # because the persistance information can't be re-used again.
+ # (See Bugzilla::WebService::Server::JSONRPC for more info.)
+ if (
+ $self->{_info_getter}->{successful}->requires_persistence
+ and !(
+ Bugzilla->request_cache->{auth_no_automatic_login}
+ || Bugzilla->request_cache->{dont_persist_session}
+ )
+ )
+ {
+ $user->{_login_token} = $self->{_persister}->persist_login($user);
}
- elsif ($fail_code == AUTH_NODATA) {
- $self->{_info_getter}->fail_nodata($self)
- if $login_type == LOGIN_REQUIRED;
-
- # If we're not LOGIN_REQUIRED, we just return the default user.
- $user = Bugzilla->user;
+ }
+ elsif ($fail_code == AUTH_ERROR) {
+ if ($result->{user_error}) {
+ ThrowUserError($result->{user_error}, $result->{details});
}
- # The username/password may be wrong
- # Don't let the user know whether the username exists or whether
- # the password was just wrong. (This makes it harder for a cracker
- # to find account names by brute force)
- elsif ($fail_code == AUTH_LOGINFAILED or $fail_code == AUTH_NO_SUCH_USER) {
- my $remaining_attempts = MAX_LOGIN_ATTEMPTS
- - ($result->{failure_count} || 0);
- ThrowUserError("invalid_username_or_password",
- { remaining => $remaining_attempts });
- }
- # The account may be disabled
- elsif ($fail_code == AUTH_DISABLED) {
- $self->{_persister}->logout();
- # XXX This is NOT a good way to do this, architecturally.
- $self->{_persister}->clear_browser_cookies();
- # and throw a user error
- ThrowUserError("account_disabled",
- {'disabled_reason' => $result->{user}->disabledtext});
- }
- elsif ($fail_code == AUTH_LOCKOUT) {
- my $attempts = $user->account_ip_login_failures;
-
- # We want to know when the account will be unlocked. This is
- # determined by the 5th-from-last login failure (or more/less than
- # 5th, if MAX_LOGIN_ATTEMPTS is not 5).
- my $determiner = $attempts->[scalar(@$attempts) - MAX_LOGIN_ATTEMPTS];
- my $unlock_at = datetime_from($determiner->{login_time},
- Bugzilla->local_timezone);
- $unlock_at->add(minutes => LOGIN_LOCKOUT_INTERVAL);
-
- # If we were *just* locked out, notify the maintainer about the
- # lockout.
- if ($result->{just_locked_out}) {
- # We're sending to the maintainer, who may be not a Bugzilla
- # account, but just an email address. So we use the
- # installation's default language for sending the email.
- my $default_settings = Bugzilla::User::Setting::get_defaults();
- my $template = Bugzilla->template_inner(
- $default_settings->{lang}->{default_value});
- my $address = $attempts->[0]->{ip_addr};
- # Note: inet_aton will only resolve IPv4 addresses.
- # For IPv6 we'll need to use inet_pton which requires Perl 5.12.
- my $n = inet_aton($address);
- if ($n) {
- my $host = gethostbyaddr($n, AF_INET);
- $address = "$host ($address)" if $host;
- }
- my $vars = {
- locked_user => $user,
- attempts => $attempts,
- unlock_at => $unlock_at,
- address => $address,
- };
- my $message;
- $template->process('email/lockout.txt.tmpl', $vars, \$message)
- || ThrowTemplateError($template->error);
- MessageToMTA($message);
- Bugzilla->audit(sprintf(
- '<%s> triggered lockout of %s after %s attempts',
- $address, $user->login, scalar(@$attempts)
- ));
- }
-
- $unlock_at->set_time_zone($user->timezone);
- ThrowUserError('account_locked',
- { ip_addr => $determiner->{ip_addr}, unlock_at => $unlock_at });
- }
- # If we get here, then we've run out of options, which shouldn't happen.
else {
- ThrowCodeError("authres_unhandled", { value => $fail_code });
+ ThrowCodeError($result->{error}, $result->{details});
}
+ }
+ elsif ($fail_code == AUTH_NODATA) {
+ $self->{_info_getter}->fail_nodata($self) if $login_type == LOGIN_REQUIRED;
+
+ # If we're not LOGIN_REQUIRED, we just return the default user.
+ $user = Bugzilla->user;
+ }
+
+ # The username/password may be wrong
+ # Don't let the user know whether the username exists or whether
+ # the password was just wrong. (This makes it harder for a cracker
+ # to find account names by brute force)
+ elsif ($fail_code == AUTH_LOGINFAILED or $fail_code == AUTH_NO_SUCH_USER) {
+ my $remaining_attempts = MAX_LOGIN_ATTEMPTS - ($result->{failure_count} || 0);
+ ThrowUserError("invalid_username_or_password",
+ {remaining => $remaining_attempts});
+ }
+
+ # The account may be disabled
+ elsif ($fail_code == AUTH_DISABLED) {
+ $self->{_persister}->logout();
+
+ # XXX This is NOT a good way to do this, architecturally.
+ $self->{_persister}->clear_browser_cookies();
+
+ # and throw a user error
+ ThrowUserError("account_disabled",
+ {'disabled_reason' => $result->{user}->disabledtext});
+ }
+ elsif ($fail_code == AUTH_LOCKOUT) {
+ my $attempts = $user->account_ip_login_failures;
+
+ # We want to know when the account will be unlocked. This is
+ # determined by the 5th-from-last login failure (or more/less than
+ # 5th, if MAX_LOGIN_ATTEMPTS is not 5).
+ my $determiner = $attempts->[scalar(@$attempts) - MAX_LOGIN_ATTEMPTS];
+ my $unlock_at
+ = datetime_from($determiner->{login_time}, Bugzilla->local_timezone);
+ $unlock_at->add(minutes => LOGIN_LOCKOUT_INTERVAL);
+
+ # If we were *just* locked out, notify the maintainer about the
+ # lockout.
+ if ($result->{just_locked_out}) {
+
+ # We're sending to the maintainer, who may be not a Bugzilla
+ # account, but just an email address. So we use the
+ # installation's default language for sending the email.
+ my $default_settings = Bugzilla::User::Setting::get_defaults();
+ my $template
+ = Bugzilla->template_inner($default_settings->{lang}->{default_value});
+ my $address = $attempts->[0]->{ip_addr};
+
+ # Note: inet_aton will only resolve IPv4 addresses.
+ # For IPv6 we'll need to use inet_pton which requires Perl 5.12.
+ my $n = inet_aton($address);
+ if ($n) {
+ my $host = gethostbyaddr($n, AF_INET);
+ $address = "$host ($address)" if $host;
+ }
+ my $vars = {
+ locked_user => $user,
+ attempts => $attempts,
+ unlock_at => $unlock_at,
+ address => $address,
+ };
+ my $message;
+ $template->process('email/lockout.txt.tmpl', $vars, \$message)
+ || ThrowTemplateError($template->error);
+ MessageToMTA($message);
+ Bugzilla->audit(sprintf(
+ '<%s> triggered lockout of %s after %s attempts',
+ $address, $user->login, scalar(@$attempts)
+ ));
+ }
+
+ $unlock_at->set_time_zone($user->timezone);
+ ThrowUserError('account_locked',
+ {ip_addr => $determiner->{ip_addr}, unlock_at => $unlock_at});
+ }
+
+ # If we get here, then we've run out of options, which shouldn't happen.
+ else {
+ ThrowCodeError("authres_unhandled", {value => $fail_code});
+ }
- return $user;
+ return $user;
}
1;