# -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Mozilla Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Bugzilla Bug Tracking System. # # The Initial Developer of the Original Code is Netscape Communications # Corporation. Portions created by Netscape are # Copyright (C) 1998 Netscape Communications Corporation. All # Rights Reserved. # # Contributor(s): Bradley Baetz # Erik Stambaugh # Max Kanat-Alexander package Bugzilla::Auth; use strict; use fields qw( _info_getter _verifier _persister ); use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Config; use Bugzilla::Auth::Login::Stack; use Bugzilla::Auth::Verify::Stack; use Bugzilla::Auth::Persist::Cookie; use Switch; sub new { my ($class, $params) = @_; my $self = fields::new($class); $params ||= {}; $params->{Login} ||= Param('user_info_class') . ',Cookie'; $params->{Verify} ||= Param('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(); 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(); 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); } $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->disabledtext) { return $self->_handle_login_result({ failure => AUTH_DISABLED, user => $user }, $type); } $user->set_authorizer($self); return $self->_handle_login_result($login_info, $type); } 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; } sub can_login { my ($self) = @_; return $self->{_info_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; } 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; } sub can_change_email { 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) { if ($self->{_info_getter}->{successful}->requires_persistence) { $self->{_persister}->persist_login($user); } } else { switch ($fail_code) { case AUTH_ERROR { ThrowCodeError($result->{error}, $result->{details}); } case AUTH_NODATA { if ($login_type == LOGIN_REQUIRED) { # This seems like as good as time as any to get rid of # old crufty junk in the logincookies table. Get rid # of any entry that hasn't been used in a month. $dbh->do("DELETE FROM logincookies WHERE " . $dbh->sql_to_days('NOW()') . " - " . $dbh->sql_to_days('lastused') . " > 30"); $self->{_info_getter}->fail_nodata($self); } # Otherwise, 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) case [AUTH_LOGINFAILED, AUTH_NO_SUCH_USER] { ThrowUserError("invalid_username_or_password"); } # The account may be disabled case 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}); } # If we get here, then we've run out of options, which # shouldn't happen. else { ThrowCodeError("authres_unhandled", { value => $fail_code }); } } } return $user; } # Returns the network address for a given IP sub get_netaddr { my $ipaddr = shift; # Check for a valid IPv4 addr which we know how to parse if (!$ipaddr || $ipaddr !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) { return undef; } my $addr = unpack("N", pack("CCCC", split(/\./, $ipaddr))); my $maskbits = Param('loginnetmask'); # Make Bugzilla ignore the IP address if loginnetmask is set to 0 return "0.0.0.0" if ($maskbits == 0); $addr >>= (32-$maskbits); $addr <<= (32-$maskbits); return join(".", unpack("CCCC", pack("N", $addr))); } 1; __END__ =head1 NAME Bugzilla::Auth - An object that authenticates the login credentials for a user. =head1 DESCRIPTION Handles authentication for Bugzilla users. Authentication from Bugzilla involves two sets of modules. One set is used to obtain the username/password (from CGI, email, etc), and the other set uses this data to authenticate against the datasource (the Bugzilla DB, LDAP, PAM, etc.). Modules for obtaining the username/password are subclasses of L, and modules for authenticating are subclasses of L. =head1 AUTHENTICATION ERROR CODES Whenever a method in the C family fails in some way, it will return a hashref containing at least a single key called C. C will point to an integer error code, and depending on the error code the hashref may contain more data. The error codes are explained here below. =head2 C Insufficient login data was provided by the user. This may happen in several cases, such as cookie authentication when the cookie is not present. =head2 C An error occurred when trying to use the login mechanism. The hashref will also contain an C element, which is the name of an error from C