summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Auth/Login/WWW/CGI.pm
blob: 5030691e3647f20f65db0a828db5a3829343323b (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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# -*- 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>

package Bugzilla::Auth::Login::WWW::CGI;

use strict;

use Bugzilla::Config;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Util;
use Bugzilla::Token;

sub login {
    my ($class, $type) = @_;

    # 'NORMAL' logins depend on the 'requirelogin' param
    if ($type == LOGIN_NORMAL) {
        $type = Param('requirelogin') ? LOGIN_REQUIRED : LOGIN_OPTIONAL;
    }

    my $cgi = Bugzilla->cgi;
    my $dbh = Bugzilla->dbh;

    # First, try the actual login method against form variables
    my $username = trim($cgi->param("Bugzilla_login"));
    my $passwd = $cgi->param("Bugzilla_password");
    
    $cgi->delete('Bugzilla_login', 'Bugzilla_password');

    # Perform the actual authentication, get the method name from the class name
    my ($authmethod, $authres, $userid, $extra, $info) =
      Bugzilla::Auth->authenticate($username, $passwd);

    if ($authres == AUTH_OK) {
        # Login via username/password was correct and valid, so create
        # and send out the login cookies
        my $ipaddr = $cgi->remote_addr;
        unless ($cgi->param('Bugzilla_restrictlogin') ||
                Param('loginnetmask') == 32) {
            $ipaddr = Bugzilla::Auth::get_netaddr($ipaddr);
        }

        # The IP address is valid, at least for comparing with itself in a
        # subsequent login
        trick_taint($ipaddr);

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

        $dbh->do("INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
                 VALUES (?, ?, ?, NOW())",
                 undef,
                 $logincookie, $userid, $ipaddr);

        # Remember cookie only if admin has told so
        # or admin didn't forbid it and user told to remember.
        if ((Param('rememberlogin') eq 'on') ||
            ((Param('rememberlogin') ne 'off') &&
             $cgi->param('Bugzilla_remember') &&
             ($cgi->param('Bugzilla_remember') eq 'on'))) {
            $cgi->send_cookie(-name => 'Bugzilla_login',
                              -value => $userid,
                              -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');
            $cgi->send_cookie(-name => 'Bugzilla_logincookie',
                              -value => $logincookie,
                              -expires => 'Fri, 01-Jan-2038 00:00:00 GMT');

        }
        else {
            $cgi->send_cookie(-name => 'Bugzilla_login',
                              -value => $userid);
            $cgi->send_cookie(-name => 'Bugzilla_logincookie',
                              -value => $logincookie);
        }
    }
    elsif ($authres == AUTH_NODATA) {
        # No data from the form, so try to login via cookies
        $username = $cgi->cookie("Bugzilla_login");
        $passwd = $cgi->cookie("Bugzilla_logincookie");

        require Bugzilla::Auth::Login::WWW::CGI::Cookie;
        my $authmethod = "Cookie";

        ($authres, $userid, $extra) =
          Bugzilla::Auth::Login::WWW::CGI::Cookie->authenticate($username, $passwd);

        # If the data for the cookie was incorrect, then treat that as
        # NODATA. This could occur if the user's IP changed, for example.
        # Give them un-loggedin access if allowed (checked below)
        $authres = AUTH_NODATA if $authres == AUTH_LOGINFAILED;
    }

    # Now check the result

    # An error may have occurred with the login mechanism
    if ($authres == AUTH_ERROR) {
        ThrowCodeError("auth_err",
                       { authmethod => lc($authmethod),
                         userid => $userid,
                         auth_err_tag => $extra,
                         info => $info
                       });
    }

    # We can load the page if the login was ok, or there was no data
    # but a login wasn't required
    if ($authres == AUTH_OK ||
        ($authres == AUTH_NODATA && $type == LOGIN_OPTIONAL)) {

        # login succeded, so we're done
        return $userid;
    }

    # No login details were given, but we require a login if the
    # page does
    if ($authres == AUTH_NODATA && $type == LOGIN_REQUIRED) {

        # Redirect to SSL if required
        if (Param('sslbase') ne '' and Param('ssl') ne 'never') {
            $cgi->require_https(Param('sslbase'));
        }
    
        # Throw up the login page

        print Bugzilla->cgi->header();

        my $template = Bugzilla->template;
        $template->process("account/auth/login.html.tmpl",
                           { 'target' => $cgi->url(-relative=>1),
                             'caneditaccount' => Bugzilla::Auth->can_edit('new'),
                             'has_db' => Bugzilla::Auth->has_db,
                           }
                          )
          || ThrowTemplateError($template->error());

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

        exit;
    }

    # 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)
    if ($authres == AUTH_LOGINFAILED) {
        ThrowUserError("invalid_username_or_password");
    }

    # The account may be disabled
    if ($authres == AUTH_DISABLED) {
        clear_browser_cookies();
        # and throw a user error
        ThrowUserError("account_disabled",
                       {'disabled_reason' => $extra});
    }

    # If we get here, then we've run out of options, which shouldn't happen
    ThrowCodeError("authres_unhandled", { authres => $authres, 
                                          type => $type });
}

# This auth style allows the user to log out.
sub can_logout { return 1; }

# Logs user out, according to the option provided; this consists of
# removing entries from logincookies for the specified $user.
sub logout {
    my ($class, $user, $option) = @_;
    my $dbh = Bugzilla->dbh;
    my $cgi = Bugzilla->cgi;
    $option = LOGOUT_ALL unless defined $option;

    if ($option == 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 $cookie;
    foreach (@{$cgi->{'Bugzilla_cookie_list'}}) {
        if ($_->name() eq 'Bugzilla_logincookie') {
            $cookie = $_->value();
            last;
        }
    }
    $cookie ||= $cgi->cookie("Bugzilla_logincookie");
    trick_taint($cookie);

    # 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
    if ($option == LOGOUT_KEEP_CURRENT) {
        $dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?",
                 undef, $cookie, $user->id);
    } elsif ($option == LOGOUT_CURRENT) {
        $dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?",
                 undef, $cookie, $user->id);
    } else {
        die("Invalid option $option supplied to logout()");
    }

    if ($option != LOGOUT_KEEP_CURRENT) {
        clear_browser_cookies();
        Bugzilla->logout_request();
    }
}

sub clear_browser_cookies {
    my $cgi = Bugzilla->cgi;
    $cgi->remove_cookie('Bugzilla_login');
    $cgi->remove_cookie('Bugzilla_logincookie');
}

1;

__END__

=head1 NAME

Bugzilla::Auth::Login::WWW::CGI - CGI-based logins for Bugzilla

=head1 SUMMARY

This is a L<login module|Bugzilla::Auth/"LOGIN"> for Bugzilla. Users connecting
from a CGI script use this module to authenticate. Logouts are also handled here.

=head1 BEHAVIOUR

Users are first authenticated against the default authentication handler,
using the CGI parameters I<Bugzilla_login> and I<Bugzilla_password>.

If no data is present for that, then cookies are tried, using
L<Bugzilla::Auth::Login::WWW::CGI::Cookie>.

=head1 SEE ALSO

L<Bugzilla::Auth>