# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # # This Source Code Form is "Incompatible With Secondary Licenses", as # defined by the Mozilla Public License, v. 2.0. package Bugzilla::Auth::Persist::Cookie; use 5.10.1; use strict; use fields qw(); use Bugzilla::Constants; use Bugzilla::Util; use Bugzilla::Token; use List::Util qw(first); sub new { 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); } 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(); } } sub clear_browser_cookies { my $cgi = Bugzilla->cgi; $cgi->remove_cookie('Bugzilla_login'); $cgi->remove_cookie('Bugzilla_logincookie'); $cgi->remove_cookie('sudo'); } 1;