Token.pm 9.12 KB
Newer Older
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
# -*- 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):    Myk Melez <myk@mozilla.org>

################################################################################
# Module Initialization
################################################################################

# Make it harder for us to do dangerous things in Perl.
use diagnostics;
use strict;

# Bundle the functions in this file together into the "Token" package.
package Token;

33 34
use Date::Format;

35 36 37
# This module requires that its caller have said "require CGI.pl" to import
# relevant functions from that script and its companion globals.pl.

38 39 40 41 42 43 44
################################################################################
# Constants
################################################################################

# The maximum number of days a token will remain valid.
my $maxtokenage = 3;

45 46 47 48
################################################################################
# Functions
################################################################################

49 50 51
sub IssueEmailChangeToken {
    my ($userid, $old_email, $new_email) = @_;

52 53 54
    my $token_ts = time();
    my $issuedate = time2str("%Y-%m-%d %H:%M", $token_ts);

55 56 57 58 59 60 61 62 63
    # Generate a unique token and insert it into the tokens table.
    # We have to lock the tokens table before generating the token, 
    # since the database must be queried for token uniqueness.
    &::SendSQL("LOCK TABLES tokens WRITE");
    my $token = GenerateUniqueToken();
    my $quotedtoken = &::SqlQuote($token);
    my $quoted_emails = &::SqlQuote($old_email . ":" . $new_email);
    &::SendSQL("INSERT INTO tokens ( userid , issuedate , token , 
                                     tokentype , eventdata )
64
                VALUES             ( $userid , '$issuedate' , $quotedtoken , 
65 66 67 68 69
                                     'emailold' , $quoted_emails )");
    my $newtoken = GenerateUniqueToken();
    $quotedtoken = &::SqlQuote($newtoken);
    &::SendSQL("INSERT INTO tokens ( userid , issuedate , token , 
                                     tokentype , eventdata )
70
                VALUES             ( $userid , '$issuedate' , $quotedtoken , 
71 72 73 74 75 76 77 78 79 80
                                     'emailnew' , $quoted_emails )");
    &::SendSQL("UNLOCK TABLES");

    # Mail the user the token along with instructions for using it.

    my $template = $::template;
    my $vars = $::vars;

    $vars->{'oldemailaddress'} = $old_email . &::Param('emailsuffix');
    $vars->{'newemailaddress'} = $new_email . &::Param('emailsuffix');
81 82 83
    
    $vars->{'max_token_age'} = $maxtokenage;
    $vars->{'token_ts'} = $token_ts;
84

85
    $vars->{'token'} = $token;
86 87 88
    $vars->{'emailaddress'} = $old_email . &::Param('emailsuffix');

    my $message;
89 90
    $template->process("account/email/change-old.txt.tmpl", $vars, \$message)
      || &::ThrowTemplateError($template->error());
91 92 93 94 95

    open SENDMAIL, "|/usr/lib/sendmail -t -i";
    print SENDMAIL $message;
    close SENDMAIL;

96
    $vars->{'token'} = $newtoken;
97 98 99
    $vars->{'emailaddress'} = $new_email . &::Param('emailsuffix');

    $message = "";
100 101
    $template->process("account/email/change-new.txt.tmpl", $vars, \$message)
      || &::ThrowTemplateError($template->error());
102 103 104 105 106 107 108

    open SENDMAIL, "|/usr/lib/sendmail -t -i";
    print SENDMAIL $message;
    close SENDMAIL;

}

109 110 111 112 113 114 115 116 117 118 119
sub IssuePasswordToken {
    # Generates a random token, adds it to the tokens table, and sends it
    # to the user with instructions for using it to change their password.

    my ($loginname) = @_;

    # Retrieve the user's ID from the database.
    my $quotedloginname = &::SqlQuote($loginname);
    &::SendSQL("SELECT userid FROM profiles WHERE login_name = $quotedloginname");
    my ($userid) = &::FetchSQLData();

120 121 122
    my $token_ts = time();
    my $issuedate = time2str("%Y-%m-%d %H:%M", $token_ts);

123 124 125
    # Generate a unique token and insert it into the tokens table.
    # We have to lock the tokens table before generating the token, 
    # since the database must be queried for token uniqueness.
126
    &::SendSQL("LOCK TABLES tokens WRITE");
127 128 129 130
    my $token = GenerateUniqueToken();
    my $quotedtoken = &::SqlQuote($token);
    my $quotedipaddr = &::SqlQuote($::ENV{'REMOTE_ADDR'});
    &::SendSQL("INSERT INTO tokens ( userid , issuedate , token , tokentype , eventdata )
131
                VALUES      ( $userid , '$issuedate' , $quotedtoken , 'password' , $quotedipaddr )");
132 133 134
    &::SendSQL("UNLOCK TABLES");

    # Mail the user the token along with instructions for using it.
135 136 137 138 139 140 141
    
    my $template = $::template;
    my $vars = $::vars;

    $vars->{'token'} = $token;
    $vars->{'emailaddress'} = $loginname . &::Param('emailsuffix');

142 143 144
    $vars->{'max_token_age'} = $maxtokenage;
    $vars->{'token_ts'} = $token_ts;

145
    my $message = "";
146 147
    $template->process("account/password/forgotten-password.txt.tmpl", 
                                                               $vars, \$message)
148 149 150 151 152
      || &::ThrowTemplateError($template->error());

    open SENDMAIL, "|/usr/lib/sendmail -t -i";
    print SENDMAIL $message;
    close SENDMAIL;
153 154 155 156

}


157 158 159
sub CleanTokenTable {
    &::SendSQL("LOCK TABLES tokens WRITE");
    &::SendSQL("DELETE FROM tokens 
160
                WHERE TO_DAYS(NOW()) - TO_DAYS(issuedate) >= " . $maxtokenage);
161 162 163 164
    &::SendSQL("UNLOCK TABLES");
}


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
sub GenerateUniqueToken {
    # Generates a unique random token.  Uses &GenerateRandomPassword 
    # for the tokens themselves and checks uniqueness by searching for
    # the token in the "tokens" table.  Gives up if it can't come up
    # with a token after about one hundred tries.

    my $token;
    my $duplicate = 1;
    my $tries = 0;
    while ($duplicate) {

        ++$tries;
        if ($tries > 100) {
            &::DisplayError("Something is seriously wrong with the token generation system.");
            exit;
        }

        $token = &::GenerateRandomPassword();
        &::SendSQL("SELECT userid FROM tokens WHERE token = " . &::SqlQuote($token));
        $duplicate = &::FetchSQLData();
    }

    return $token;

}


sub Cancel {
    # Cancels a previously issued token and notifies the system administrator.
    # This should only happen when the user accidentally makes a token request
    # or when a malicious hacker makes a token request on behalf of a user.
    
    my ($token, $cancelaction) = @_;

    # Quote the token for inclusion in SQL statements.
    my $quotedtoken = &::SqlQuote($token);
    
    # Get information about the token being cancelled.
    &::SendSQL("SELECT  issuedate , tokentype , eventdata , login_name , realname
                FROM    tokens, profiles 
                WHERE   tokens.userid = profiles.userid
                AND     token = $quotedtoken");
    my ($issuedate, $tokentype, $eventdata, $loginname, $realname) = &::FetchSQLData();

    # Get the email address of the Bugzilla maintainer.
    my $maintainer = &::Param('maintainer');

    # Format the user's real name and email address into a single string.
    my $username = $realname ? $realname . " <" . $loginname . ">" : $loginname;

215 216
    my $template = $::template;
    my $vars = $::vars;
217

218 219 220
    $vars->{'emailaddress'} = $username;
    $vars->{'maintainer'} = $maintainer;
    $vars->{'remoteaddress'} = $::ENV{'REMOTE_ADDR'};
221
    $vars->{'token'} = $token;
222 223 224 225
    $vars->{'tokentype'} = $tokentype;
    $vars->{'issuedate'} = $issuedate;
    $vars->{'eventdata'} = $eventdata;
    $vars->{'cancelaction'} = $cancelaction;
226

227
    # Notify the user via email about the cancellation.
228

229
    my $message;
230 231
    $template->process("account/cancel-token.txt.tmpl", $vars, \$message)
      || &::ThrowTemplateError($template->error());
232 233 234

    open SENDMAIL, "|/usr/lib/sendmail -t -i";
    print SENDMAIL $message;
235 236 237
    close SENDMAIL;

    # Delete the token from the database.
238
    &::SendSQL("LOCK TABLES tokens WRITE");
239 240 241 242 243
    &::SendSQL("DELETE FROM tokens WHERE token = $quotedtoken");
    &::SendSQL("UNLOCK TABLES");
}

sub HasPasswordToken {
244
    # Returns a password token if the user has one.
245 246 247
    
    my ($userid) = @_;
    
248 249
    &::SendSQL("SELECT token FROM tokens 
                WHERE userid = $userid AND tokentype = 'password' LIMIT 1");
250 251 252 253 254
    my ($token) = &::FetchSQLData();
    
    return $token;
}

255 256 257 258 259
sub HasEmailChangeToken {
    # Returns an email change token if the user has one. 
    
    my ($userid) = @_;
    
260 261 262
    &::SendSQL("SELECT token FROM tokens WHERE userid = $userid " . 
               "AND (tokentype = 'emailnew' OR tokentype = 'emailold') " . 
               "LIMIT 1");
263 264 265 266 267 268
    my ($token) = &::FetchSQLData();
    
    return $token;
}


269
1;