diff options
author | Robin H. Johnson <robbat2@gentoo.org> | 2019-11-30 23:12:11 -0800 |
---|---|---|
committer | Robin H. Johnson <robbat2@gentoo.org> | 2019-12-01 14:53:51 -0800 |
commit | 70780e40e5586c6882e33dd65a3dc3f31031a321 (patch) | |
tree | 51fc3608bd44e7b92d07a976ca3112fd5d87d843 /Bugzilla/Auth | |
parent | Merge commit '3395d78cc8b0bd660e56f73a2689d495f2a22628' into bugstest (diff) | |
download | bugzilla-70780e40e5586c6882e33dd65a3dc3f31031a321.tar.gz bugzilla-70780e40e5586c6882e33dd65a3dc3f31031a321.tar.bz2 bugzilla-70780e40e5586c6882e33dd65a3dc3f31031a321.zip |
Gentoo-local version of 7f3a749d7bd78a3e4aee163f562d7e95b0954b44 w/ Perl-Tidy-20180220
Reformat all code using Perl-Tidy v20180220 and .perltidyrc from
matching upstream 7f3a749d7bd78a3e4aee163f562d7e95b0954b44 commit.
Signed-off-by: Robin H. Johnson <robbat2@gentoo.org>
Diffstat (limited to 'Bugzilla/Auth')
-rw-r--r-- | Bugzilla/Auth/Login.pm | 18 | ||||
-rw-r--r-- | Bugzilla/Auth/Login/APIKey.pm | 35 | ||||
-rw-r--r-- | Bugzilla/Auth/Login/CGI.pm | 118 | ||||
-rw-r--r-- | Bugzilla/Auth/Login/Cookie.pm | 185 | ||||
-rw-r--r-- | Bugzilla/Auth/Login/Env.pm | 27 | ||||
-rw-r--r-- | Bugzilla/Auth/Login/Stack.pm | 126 | ||||
-rw-r--r-- | Bugzilla/Auth/Persist/Cookie.pm | 267 | ||||
-rw-r--r-- | Bugzilla/Auth/Verify.pm | 198 | ||||
-rw-r--r-- | Bugzilla/Auth/Verify/DB.pm | 146 | ||||
-rw-r--r-- | Bugzilla/Auth/Verify/LDAP.pm | 253 | ||||
-rw-r--r-- | Bugzilla/Auth/Verify/RADIUS.pm | 58 | ||||
-rw-r--r-- | Bugzilla/Auth/Verify/Stack.pm | 107 |
12 files changed, 801 insertions, 737 deletions
diff --git a/Bugzilla/Auth/Login.pm b/Bugzilla/Auth/Login.pm index a5f089777..68c7464f2 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 63e35578a..79c16825e 100644 --- a/Bugzilla/Auth/Login/APIKey.pm +++ b/Bugzilla/Auth/Login/APIKey.pm @@ -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; diff --git a/Bugzilla/Auth/Login/CGI.pm b/Bugzilla/Auth/Login/CGI.pm index 6003d62a5..0824f1ebd 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 = 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; diff --git a/Bugzilla/Auth/Login/Cookie.pm b/Bugzilla/Auth/Login/Cookie.pm index c09f08d24..1983dbd4c 100644 --- a/Bugzilla/Auth/Login/Cookie.pm +++ b/Bugzilla/Auth/Login/Cookie.pm @@ -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; diff --git a/Bugzilla/Auth/Login/Env.pm b/Bugzilla/Auth/Login/Env.pm index 653df2bb3..5fc33921b 100644 --- a/Bugzilla/Auth/Login/Env.pm +++ b/Bugzilla/Auth/Login/Env.pm @@ -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; diff --git a/Bugzilla/Auth/Login/Stack.pm b/Bugzilla/Auth/Login/Stack.pm index dc35998e4..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 2d1291f3b..af6b0d77d 100644 --- a/Bugzilla/Auth/Persist/Cookie.pm +++ b/Bugzilla/Auth/Persist/Cookie.pm @@ -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; diff --git a/Bugzilla/Auth/Verify.pm b/Bugzilla/Auth/Verify.pm index 9dc83273b..2e49175d7 100644 --- a/Bugzilla/Auth/Verify.pm +++ b/Bugzilla/Auth/Verify.pm @@ -19,113 +19,125 @@ 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; diff --git a/Bugzilla/Auth/Verify/DB.pm b/Bugzilla/Auth/Verify/DB.pm index 28a9310c9..951aaaf9f 100644 --- a/Bugzilla/Auth/Verify/DB.pm +++ b/Bugzilla/Auth/Verify/DB.pm @@ -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; diff --git a/Bugzilla/Auth/Verify/LDAP.pm b/Bugzilla/Auth/Verify/LDAP.pm index e37f55793..c92a38909 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_retreive_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_retreive_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 their 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 283d9b466..2cbde0404 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; |