summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Auth/Persist/Cookie.pm
blob: 06661101b7bcf7f1a8ce4c966db4cf10fa430489 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# -*- 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): Terry Weissman <terry@mozilla.org>
#                 Dan Mosedale <dmose@mozilla.org>
#                 Joe Robins <jmrobins@tgix.com>
#                 Dave Miller <justdave@syndicomm.com>
#                 Christopher Aillon <christopher@aillon.com>
#                 Gervase Markham <gerv@gerv.net>
#                 Christian Reis <kiko@async.com.br>
#                 Bradley Baetz <bbaetz@acm.org>
#                 Erik Stambaugh <erik@dasbistro.com>
#                 Max Kanat-Alexander <mkanat@bugzilla.org>

package Bugzilla::Auth::Persist::Cookie;
use strict;
use fields qw();

use Bugzilla::Constants;
use Bugzilla::Util;
use Bugzilla::Token;

use List::Util qw(first);
use List::MoreUtils qw(any);

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;

    $dbh->bz_start_transaction();

    my $login_cookie = 
        Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');

    my $ip_addr = remote_ip();
    trick_taint($ip_addr);
    my $restrict = $input_params->{Bugzilla_restrictlogin} ? 1 : 0;

    $dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused, restrict_ipaddr)
              VALUES (?, ?, ?, NOW(), ?)",
              undef, $login_cookie, $user->id, $ip_addr, $restrict);

    # 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();

    # 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->remove_cookie('github_secret');
    $cgi->remove_cookie('Bugzilla_login_request_cookie');
    $cgi->send_cookie(-name => 'Bugzilla_login',
                      -value => $user->id,
                      %cookieargs);
    $cgi->send_cookie(-name => 'Bugzilla_logincookie',
                      -value => $login_cookie,
                      %cookieargs);

    my $securemail_groups = Bugzilla->can('securemail_groups') ? Bugzilla->securemail_groups : [ 'admin' ];

    if (any { $user->in_group($_) } 'mozilla-employee-confidential', @$securemail_groups) {
        my $auth_method = eval { ref($user->authorizer->successful_info_getter) } // 'unknown';

        Bugzilla->audit(sprintf "successful login of %s from %s using \"%s\", authenticated by %s",
                        $user->login, $ip_addr, $cgi->user_agent // '', $auth_method);
    }
    
    return $login_cookie;
}

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);
    }
    else {
        push(@login_cookies, $cgi->cookie("Bugzilla_logincookie"));
    }

    # 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;