1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4# 5# This Source Code Form is "Incompatible With Secondary Licenses", as 6# defined by the Mozilla Public License, v. 2.0. 7 8package Bugzilla::Auth::Persist::Cookie; 9 10use 5.10.1; 11use strict; 12use warnings; 13 14use fields qw(); 15 16use Bugzilla::Constants; 17use Bugzilla::Util; 18use Bugzilla::Token; 19 20use List::Util qw(first); 21 22sub new { 23 my ($class) = @_; 24 my $self = fields::new($class); 25 return $self; 26} 27 28sub persist_login { 29 my ($self, $user) = @_; 30 my $dbh = Bugzilla->dbh; 31 my $cgi = Bugzilla->cgi; 32 my $input_params = Bugzilla->input_params; 33 34 my $ip_addr; 35 if ($input_params->{'Bugzilla_restrictlogin'}) { 36 $ip_addr = remote_ip(); 37 # The IP address is valid, at least for comparing with itself in a 38 # subsequent login 39 trick_taint($ip_addr); 40 } 41 42 $dbh->bz_start_transaction(); 43 44 my $login_cookie = 45 Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie'); 46 47 $dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused) 48 VALUES (?, ?, ?, NOW())", 49 undef, $login_cookie, $user->id, $ip_addr); 50 51 # Issuing a new cookie is a good time to clean up the old 52 # cookies. 53 $dbh->do("DELETE FROM logincookies WHERE lastused < " 54 . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', 55 MAX_LOGINCOOKIE_AGE, 'DAY')); 56 57 $dbh->bz_commit_transaction(); 58 59 # We do not want WebServices to generate login cookies. 60 # All we need is the login token for User.login. 61 return $login_cookie if i_am_webservice(); 62 63 # Prevent JavaScript from accessing login cookies. 64 my %cookieargs = ('-httponly' => 1); 65 66 # Remember cookie only if admin has told so 67 # or admin didn't forbid it and user told to remember. 68 if ( Bugzilla->params->{'rememberlogin'} eq 'on' || 69 (Bugzilla->params->{'rememberlogin'} ne 'off' && 70 $input_params->{'Bugzilla_remember'} && 71 $input_params->{'Bugzilla_remember'} eq 'on') ) 72 { 73 # Not a session cookie, so set an infinite expiry 74 $cookieargs{'-expires'} = 'Fri, 01-Jan-2038 00:00:00 GMT'; 75 } 76 if (Bugzilla->params->{'ssl_redirect'}) { 77 # Make these cookies only be sent to us by the browser during 78 # HTTPS sessions, if we're using SSL. 79 $cookieargs{'-secure'} = 1; 80 } 81 82 $cgi->send_cookie(-name => 'Bugzilla_login', 83 -value => $user->id, 84 %cookieargs); 85 $cgi->send_cookie(-name => 'Bugzilla_logincookie', 86 -value => $login_cookie, 87 %cookieargs); 88} 89 90sub logout { 91 my ($self, $param) = @_; 92 93 my $dbh = Bugzilla->dbh; 94 my $cgi = Bugzilla->cgi; 95 my $input = Bugzilla->input_params; 96 $param = {} unless $param; 97 my $user = $param->{user} || Bugzilla->user; 98 my $type = $param->{type} || LOGOUT_ALL; 99 100 if ($type == LOGOUT_ALL) { 101 $dbh->do("DELETE FROM logincookies WHERE userid = ?", 102 undef, $user->id); 103 return; 104 } 105 106 # The LOGOUT_*_CURRENT options require the current login cookie. 107 # If a new cookie has been issued during this run, that's the current one. 108 # If not, it's the one we've received. 109 my @login_cookies; 110 my $cookie = first {$_->name eq 'Bugzilla_logincookie'} 111 @{$cgi->{'Bugzilla_cookie_list'}}; 112 if ($cookie) { 113 push(@login_cookies, $cookie->value); 114 } 115 elsif ($cookie = $cgi->cookie('Bugzilla_logincookie')) { 116 push(@login_cookies, $cookie); 117 } 118 119 # If we are a webservice using a token instead of cookie 120 # then add that as well to the login cookies to delete 121 if (my $login_token = $user->authorizer->login_token) { 122 push(@login_cookies, $login_token->{'login_token'}); 123 } 124 125 # Make sure that @login_cookies is not empty to not break SQL statements. 126 push(@login_cookies, '') unless @login_cookies; 127 128 # These queries use both the cookie ID and the user ID as keys. Even 129 # though we know the userid must match, we still check it in the SQL 130 # as a sanity check, since there is no locking here, and if the user 131 # logged out from two machines simultaneously, while someone else 132 # logged in and got the same cookie, we could be logging the other 133 # user out here. Yes, this is very very very unlikely, but why take 134 # chances? - bbaetz 135 map { trick_taint($_) } @login_cookies; 136 @login_cookies = map { $dbh->quote($_) } @login_cookies; 137 if ($type == LOGOUT_KEEP_CURRENT) { 138 $dbh->do("DELETE FROM logincookies WHERE " . 139 $dbh->sql_in('cookie', \@login_cookies, 1) . 140 " AND userid = ?", 141 undef, $user->id); 142 } elsif ($type == LOGOUT_CURRENT) { 143 $dbh->do("DELETE FROM logincookies WHERE " . 144 $dbh->sql_in('cookie', \@login_cookies) . 145 " AND userid = ?", 146 undef, $user->id); 147 } else { 148 die("Invalid type $type supplied to logout()"); 149 } 150 151 if ($type != LOGOUT_KEEP_CURRENT) { 152 clear_browser_cookies(); 153 } 154 155} 156 157sub clear_browser_cookies { 158 my $cgi = Bugzilla->cgi; 159 $cgi->remove_cookie('Bugzilla_login'); 160 $cgi->remove_cookie('Bugzilla_logincookie'); 161 $cgi->remove_cookie('sudo'); 162} 163 1641; 165