diff options
Diffstat (limited to 'Bugzilla/Token.pm')
-rw-r--r-- | Bugzilla/Token.pm | 764 |
1 files changed, 400 insertions, 364 deletions
diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm index 8e51db45d..3398e236a 100644 --- a/Bugzilla/Token.pm +++ b/Bugzilla/Token.pm @@ -27,12 +27,12 @@ use JSON qw(encode_json decode_json); use base qw(Exporter); @Bugzilla::Token::EXPORT = qw(issue_api_token issue_session_token - issue_short_lived_session_token - issue_auth_delegation_token check_auth_delegation_token - check_token_data delete_token - issue_hash_token check_hash_token - issue_hash_sig check_hash_sig - set_token_extra_data get_token_extra_data); + issue_short_lived_session_token + issue_auth_delegation_token check_auth_delegation_token + check_token_data delete_token + issue_hash_token check_hash_token + issue_hash_sig check_hash_sig + set_token_extra_data get_token_extra_data); # 128 bits password: # 128 * log10(2) / log10(62) = 21.49, round up to 22. @@ -45,407 +45,439 @@ use constant TOKEN_LENGTH => 22; # Create a token used for internal API authentication sub issue_api_token { - # Generates a random token, adds it to the tokens table if one does not - # already exist, and returns the token to the caller. - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - my ($token) = $dbh->selectrow_array(" + + # Generates a random token, adds it to the tokens table if one does not + # already exist, and returns the token to the caller. + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + my ($token) = $dbh->selectrow_array(" SELECT token FROM tokens WHERE userid = ? AND tokentype = 'api_token' - AND (" . $dbh->sql_date_math('issuedate', '+', (MAX_TOKEN_AGE * 24 - 12), 'HOUR') . ") > NOW()", - undef, $user->id); - return $token // _create_token($user->id, 'api_token', ''); + AND (" + . $dbh->sql_date_math('issuedate', '+', (MAX_TOKEN_AGE * 24 - 12), 'HOUR') + . ") > NOW()", undef, $user->id); + return $token // _create_token($user->id, 'api_token', ''); } sub issue_auth_delegation_token { - my ($uri) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; - my $checksum = hmac_sha256_base64($user->id, $uri, Bugzilla->localconfig->{'site_wide_secret'}); + my ($uri) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; + my $checksum = hmac_sha256_base64($user->id, $uri, + Bugzilla->localconfig->{'site_wide_secret'}); - return _create_token($user->id, 'auth_delegation', $checksum); + return _create_token($user->id, 'auth_delegation', $checksum); } sub check_auth_delegation_token { - my ($token, $uri) = @_; - my $dbh = Bugzilla->dbh; - my $user = Bugzilla->user; + my ($token, $uri) = @_; + my $dbh = Bugzilla->dbh; + my $user = Bugzilla->user; - my ($eventdata) = $dbh->selectrow_array(" + my ($eventdata) = $dbh->selectrow_array(" SELECT eventdata FROM tokens WHERE token = ? AND tokentype = 'auth_delegation' - AND (" . $dbh->sql_date_math('issuedate', '+', (MAX_TOKEN_AGE * 24 - 12), 'HOUR') . ") > NOW()", - undef, $token); - - if ($eventdata) { - my $checksum = hmac_sha256_base64($user->id, $uri, Bugzilla->localconfig->{'site_wide_secret'}); - if ($eventdata eq $checksum) { - delete_token($token); - return 1; - } + AND (" + . $dbh->sql_date_math('issuedate', '+', (MAX_TOKEN_AGE * 24 - 12), 'HOUR') + . ") > NOW()", undef, $token); + + if ($eventdata) { + my $checksum = hmac_sha256_base64($user->id, $uri, + Bugzilla->localconfig->{'site_wide_secret'}); + if ($eventdata eq $checksum) { + delete_token($token); + return 1; } + } - return 0; + return 0; } # Creates and sends a token to create a new user account. # It assumes that the login has the correct format and is not already in use. sub issue_new_user_account_token { - my $login_name = shift; - my $dbh = Bugzilla->dbh; - my $template = Bugzilla->template; - my $vars = {}; - - # Is there already a pending request for this login name? If yes, do not throw - # an error because the user may have lost his email with the token inside. - # But to prevent using this way to mailbomb an email address, make sure - # the last request is at least 10 minutes old before sending a new email. - - my $pending_requests = $dbh->selectrow_array( - 'SELECT COUNT(*) + my $login_name = shift; + my $dbh = Bugzilla->dbh; + my $template = Bugzilla->template; + my $vars = {}; + + # Is there already a pending request for this login name? If yes, do not throw + # an error because the user may have lost his email with the token inside. + # But to prevent using this way to mailbomb an email address, make sure + # the last request is at least 10 minutes old before sending a new email. + + my $pending_requests = $dbh->selectrow_array( + 'SELECT COUNT(*) FROM tokens WHERE tokentype = ? AND ' . $dbh->sql_istrcmp('eventdata', '?') . ' AND issuedate > ' - . $dbh->sql_date_math('NOW()', '-', 10, 'MINUTE'), - undef, ('account', $login_name)); + . $dbh->sql_date_math('NOW()', '-', 10, 'MINUTE'), undef, + ('account', $login_name) + ); - ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) if $pending_requests; + ThrowUserError('too_soon_for_new_token', {'type' => 'account'}) + if $pending_requests; - my ($token, $token_ts) = _create_token(undef, 'account', $login_name); + my ($token, $token_ts) = _create_token(undef, 'account', $login_name); - $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'}; - $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); - $vars->{'token'} = $token; + $vars->{'email'} = $login_name . Bugzilla->params->{'emailsuffix'}; + $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); + $vars->{'token'} = $token; - my $message; - $template->process('account/email/request-new.txt.tmpl', $vars, \$message) - || ThrowTemplateError($template->error()); + my $message; + $template->process('account/email/request-new.txt.tmpl', $vars, \$message) + || ThrowTemplateError($template->error()); - # In 99% of cases, the user getting the confirmation email is the same one - # who made the request, and so it is reasonable to send the email in the same - # language used to view the "Create a New Account" page (we cannot use his - # user prefs as the user has no account yet!). - MessageToMTA($message); + # In 99% of cases, the user getting the confirmation email is the same one + # who made the request, and so it is reasonable to send the email in the same + # language used to view the "Create a New Account" page (we cannot use his + # user prefs as the user has no account yet!). + MessageToMTA($message); } sub IssueEmailChangeToken { - my ($user, $new_email) = @_; - my $email_suffix = Bugzilla->params->{'emailsuffix'}; - my $old_email = $user->login; + my ($user, $new_email) = @_; + my $email_suffix = Bugzilla->params->{'emailsuffix'}; + my $old_email = $user->login; - my ($token, $token_ts) = _create_token($user->id, 'emailold', $old_email . ":" . $new_email); + my ($token, $token_ts) + = _create_token($user->id, 'emailold', $old_email . ":" . $new_email); - my $newtoken = _create_token($user->id, 'emailnew', $old_email . ":" . $new_email); + my $newtoken + = _create_token($user->id, 'emailnew', $old_email . ":" . $new_email); - # Mail the user the token along with instructions for using it. + # Mail the user the token along with instructions for using it. - my $template = Bugzilla->template_inner($user->setting('lang')); - my $vars = {}; + my $template = Bugzilla->template_inner($user->setting('lang')); + my $vars = {}; - $vars->{'oldemailaddress'} = $old_email . $email_suffix; - $vars->{'newemailaddress'} = $new_email . $email_suffix; - $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); - $vars->{'token'} = $token; - # For SecureMail extension - $vars->{'to_user'} = $user; - $vars->{'emailaddress'} = $old_email . $email_suffix; + $vars->{'oldemailaddress'} = $old_email . $email_suffix; + $vars->{'newemailaddress'} = $new_email . $email_suffix; + $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); + $vars->{'token'} = $token; - my $message; - $template->process("account/email/change-old.txt.tmpl", $vars, \$message) - || ThrowTemplateError($template->error()); + # For SecureMail extension + $vars->{'to_user'} = $user; + $vars->{'emailaddress'} = $old_email . $email_suffix; - MessageToMTA($message); + my $message; + $template->process("account/email/change-old.txt.tmpl", $vars, \$message) + || ThrowTemplateError($template->error()); - $vars->{'token'} = $newtoken; - $vars->{'emailaddress'} = $new_email . $email_suffix; + MessageToMTA($message); - $message = ""; - $template->process("account/email/change-new.txt.tmpl", $vars, \$message) - || ThrowTemplateError($template->error()); + $vars->{'token'} = $newtoken; + $vars->{'emailaddress'} = $new_email . $email_suffix; - MessageToMTA($message); + $message = ""; + $template->process("account/email/change-new.txt.tmpl", $vars, \$message) + || ThrowTemplateError($template->error()); + + MessageToMTA($message); } # 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. sub IssuePasswordToken { - my $user = shift; - my $dbh = Bugzilla->dbh; + my $user = shift; + my $dbh = Bugzilla->dbh; - my $too_soon = $dbh->selectrow_array( - 'SELECT 1 FROM tokens + my $too_soon = $dbh->selectrow_array( + 'SELECT 1 FROM tokens WHERE userid = ? AND tokentype = ? AND issuedate > ' - . $dbh->sql_date_math('NOW()', '-', 10, 'MINUTE'), - undef, ($user->id, 'password')); + . $dbh->sql_date_math('NOW()', '-', 10, 'MINUTE'), undef, + ($user->id, 'password') + ); - ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon; + ThrowUserError('too_soon_for_new_token', {'type' => 'password'}) if $too_soon; - my ($token, $token_ts) = _create_token($user->id, 'password', remote_ip()); + my ($token, $token_ts) = _create_token($user->id, 'password', remote_ip()); - # Mail the user the token along with instructions for using it. - my $template = Bugzilla->template_inner($user->setting('lang')); - my $vars = {}; + # Mail the user the token along with instructions for using it. + my $template = Bugzilla->template_inner($user->setting('lang')); + my $vars = {}; - $vars->{'token'} = $token; - $vars->{'emailaddress'} = $user->email; - $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); - # The user is not logged in (else he wouldn't request a new password). - # So we have to pass this information to the template. - $vars->{'timezone'} = $user->timezone; + $vars->{'token'} = $token; + $vars->{'emailaddress'} = $user->email; + $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); - my $message = ""; - $template->process("account/password/forgotten-password.txt.tmpl", - $vars, \$message) - || ThrowTemplateError($template->error()); + # The user is not logged in (else he wouldn't request a new password). + # So we have to pass this information to the template. + $vars->{'timezone'} = $user->timezone; - MessageToMTA($message); + my $message = ""; + $template->process("account/password/forgotten-password.txt.tmpl", + $vars, \$message) + || ThrowTemplateError($template->error()); + + MessageToMTA($message); } sub issue_session_token { - my ($data, $user) = @_; - # Generates a random token, adds it to the tokens table, and returns - # the token to the caller. + my ($data, $user) = @_; + + # Generates a random token, adds it to the tokens table, and returns + # the token to the caller. - $user //= Bugzilla->user; - return _create_token($user->id, 'session', $data); + $user //= Bugzilla->user; + return _create_token($user->id, 'session', $data); } sub issue_short_lived_session_token { - my ($data, $user) = @_; - # Generates a random token, adds it to the tokens table, and returns - # the token to the caller. + my ($data, $user) = @_; + + # Generates a random token, adds it to the tokens table, and returns + # the token to the caller. - $user //= Bugzilla->user; - return _create_token($user->id ? $user->id : undef, 'session.short', $data); + $user //= Bugzilla->user; + return _create_token($user->id ? $user->id : undef, 'session.short', $data); } sub issue_hash_sig { - my ($type, $data, $salt) = @_; - $data //= ""; - $salt //= generate_random_password(16); - - my $hmac = hmac_sha256_base64( - $salt, - $type, - $data, - Bugzilla->localconfig->{site_wide_secret} - ); - return sprintf("%s|%s|%x", $salt, $hmac, length($data)); + my ($type, $data, $salt) = @_; + $data //= ""; + $salt //= generate_random_password(16); + + my $hmac = hmac_sha256_base64($salt, $type, $data, + Bugzilla->localconfig->{site_wide_secret}); + return sprintf("%s|%s|%x", $salt, $hmac, length($data)); } sub check_hash_sig { - my ($type, $sig, $data) = @_; - return 0 unless defined $sig && defined $data; - my ($salt, undef, $len) = split(/\|/, $sig, 3); - return length($data) == hex($len) && $sig eq issue_hash_sig($type, $data, $salt); + my ($type, $sig, $data) = @_; + return 0 unless defined $sig && defined $data; + my ($salt, undef, $len) = split(/\|/, $sig, 3); + return + length($data) == hex($len) && $sig eq issue_hash_sig($type, $data, $salt); } sub issue_hash_token { - my ($data, $time) = @_; - $data ||= []; - $time ||= time(); - - # For the user ID, use the actual ID if the user is logged in. - # Otherwise, use the remote IP, in case this is for something - # such as creating an account or logging in. - my $user_id = Bugzilla->user->id || remote_ip(); - - # The concatenated string is of the form - # token creation time + user ID (either ID or remote IP) + data - my @args = ($time, $user_id, @$data); - - my $token = join('*', @args); - # $token needs to be a byte string. - utf8::encode($token); - $token = hmac_sha256_base64($token, Bugzilla->localconfig->{'site_wide_secret'}); - $token =~ s/\+/-/g; - $token =~ s/\//_/g; - - # Prepend the token creation time, unencrypted, so that the token - # lifetime can be validated. - return $time . '-' . $token; + my ($data, $time) = @_; + $data ||= []; + $time ||= time(); + + # For the user ID, use the actual ID if the user is logged in. + # Otherwise, use the remote IP, in case this is for something + # such as creating an account or logging in. + my $user_id = Bugzilla->user->id || remote_ip(); + + # The concatenated string is of the form + # token creation time + user ID (either ID or remote IP) + data + my @args = ($time, $user_id, @$data); + + my $token = join('*', @args); + + # $token needs to be a byte string. + utf8::encode($token); + $token + = hmac_sha256_base64($token, Bugzilla->localconfig->{'site_wide_secret'}); + $token =~ s/\+/-/g; + $token =~ s/\//_/g; + + # Prepend the token creation time, unencrypted, so that the token + # lifetime can be validated. + return $time . '-' . $token; } sub check_hash_token { - my ($token, $data) = @_; - $data ||= []; - my ($time, $expected_token); - - if ($token) { - ($time, undef) = split(/-/, $token); - # Regenerate the token based on the information we have. - $expected_token = issue_hash_token($data, $time); - } + my ($token, $data) = @_; + $data ||= []; + my ($time, $expected_token); - if (!$token - || $expected_token ne $token - || time() - $time > MAX_TOKEN_AGE * 86400) - { - my $template = Bugzilla->template; - my $vars = {}; - $vars->{'script_name'} = basename($0); - $vars->{'token'} = issue_hash_token($data); - $vars->{'reason'} = (!$token) ? 'missing_token' : - ($expected_token ne $token) ? 'invalid_token' : - 'expired_token'; - print Bugzilla->cgi->header(); - $template->process('global/confirm-action.html.tmpl', $vars) - || ThrowTemplateError($template->error()); - exit; - } + if ($token) { + ($time, undef) = split(/-/, $token); + + # Regenerate the token based on the information we have. + $expected_token = issue_hash_token($data, $time); + } + + if (!$token + || $expected_token ne $token + || time() - $time > MAX_TOKEN_AGE * 86400) + { + my $template = Bugzilla->template; + my $vars = {}; + $vars->{'script_name'} = basename($0); + $vars->{'token'} = issue_hash_token($data); + $vars->{'reason'} + = (!$token) ? 'missing_token' + : ($expected_token ne $token) ? 'invalid_token' + : 'expired_token'; + print Bugzilla->cgi->header(); + $template->process('global/confirm-action.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + exit; + } - # If we come here, then the token is valid and not too old. - return 1; + # If we come here, then the token is valid and not too old. + return 1; } sub CleanTokenTable { - my $dbh = Bugzilla->dbh; - $dbh->do("DELETE FROM tokens WHERE " . - $dbh->sql_date_math('issuedate', '+', '?', 'HOUR') . " <= NOW()", - undef, MAX_TOKEN_AGE * 24); - $dbh->do("DELETE FROM tokens WHERE tokentype = ? AND " . - $dbh->sql_date_math('issuedate', '+', '?', 'HOUR') . " <= NOW()", - undef, 'session.short', MAX_SHORT_TOKEN_HOURS); + my $dbh = Bugzilla->dbh; + $dbh->do( + "DELETE FROM tokens WHERE " + . $dbh->sql_date_math('issuedate', '+', '?', 'HOUR') + . " <= NOW()", + undef, + MAX_TOKEN_AGE * 24 + ); + $dbh->do( + "DELETE FROM tokens WHERE tokentype = ? AND " + . $dbh->sql_date_math('issuedate', '+', '?', 'HOUR') + . " <= NOW()", + undef, 'session.short', MAX_SHORT_TOKEN_HOURS + ); } sub GenerateUniqueToken { - # Generates a unique random token. Uses generate_random_password - # 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 ($table, $column) = @_; - - my $token; - my $duplicate = 1; - my $tries = 0; - $table ||= "tokens"; - $column ||= "token"; - - my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare("SELECT 1 FROM $table WHERE $column = ?"); - - while ($duplicate) { - ++$tries; - if ($tries > 100) { - ThrowCodeError("token_generation_error"); - } - $token = generate_random_password(TOKEN_LENGTH); - $sth->execute($token); - $duplicate = $sth->fetchrow_array; + + # Generates a unique random token. Uses generate_random_password + # 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 ($table, $column) = @_; + + my $token; + my $duplicate = 1; + my $tries = 0; + $table ||= "tokens"; + $column ||= "token"; + + my $dbh = Bugzilla->dbh; + my $sth = $dbh->prepare("SELECT 1 FROM $table WHERE $column = ?"); + + while ($duplicate) { + ++$tries; + if ($tries > 100) { + ThrowCodeError("token_generation_error"); } - return $token; + $token = generate_random_password(TOKEN_LENGTH); + $sth->execute($token); + $duplicate = $sth->fetchrow_array; + } + return $token; } # Cancels a previously issued token and notifies the user. # 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. sub Cancel { - my ($token, $cancelaction, $vars) = @_; - my $dbh = Bugzilla->dbh; - $vars ||= {}; - - # Get information about the token being canceled. - trick_taint($token); - my ($db_token, $issuedate, $tokentype, $eventdata, $userid) = - $dbh->selectrow_array('SELECT token, ' . $dbh->sql_date_format('issuedate') . ', + my ($token, $cancelaction, $vars) = @_; + my $dbh = Bugzilla->dbh; + $vars ||= {}; + + # Get information about the token being canceled. + trick_taint($token); + my ($db_token, $issuedate, $tokentype, $eventdata, $userid) + = $dbh->selectrow_array( + 'SELECT token, ' + . $dbh->sql_date_format('issuedate') . ', tokentype, eventdata, userid FROM tokens - WHERE token = ?', - undef, $token); - - # Some DBs such as MySQL are case-insensitive by default so we do - # a quick comparison to make sure the tokens are indeed the same. - (defined $db_token && $db_token eq $token) - || ThrowCodeError("cancel_token_does_not_exist"); - - # If we are canceling the creation of a new user account, then there - # is no entry in the 'profiles' table. - my $user = new Bugzilla::User($userid); - - $vars->{'emailaddress'} = $userid ? $user->email : $eventdata; - $vars->{'remoteaddress'} = remote_ip(); - $vars->{'token'} = $token; - $vars->{'tokentype'} = $tokentype; - $vars->{'issuedate'} = $issuedate; - # The user is probably not logged in. - # So we have to pass this information to the template. - $vars->{'timezone'} = $user->timezone; - $vars->{'eventdata'} = $eventdata; - $vars->{'cancelaction'} = $cancelaction; - - # Notify the user via email about the cancellation. - my $template = Bugzilla->template_inner($user->setting('lang')); - - my $message; - $template->process("account/cancel-token.txt.tmpl", $vars, \$message) - || ThrowTemplateError($template->error()); + WHERE token = ?', undef, $token + ); - MessageToMTA($message); + # Some DBs such as MySQL are case-insensitive by default so we do + # a quick comparison to make sure the tokens are indeed the same. + (defined $db_token && $db_token eq $token) + || ThrowCodeError("cancel_token_does_not_exist"); - # Delete the token from the database. - delete_token($token); + # If we are canceling the creation of a new user account, then there + # is no entry in the 'profiles' table. + my $user = new Bugzilla::User($userid); + + $vars->{'emailaddress'} = $userid ? $user->email : $eventdata; + $vars->{'remoteaddress'} = remote_ip(); + $vars->{'token'} = $token; + $vars->{'tokentype'} = $tokentype; + $vars->{'issuedate'} = $issuedate; + + # The user is probably not logged in. + # So we have to pass this information to the template. + $vars->{'timezone'} = $user->timezone; + $vars->{'eventdata'} = $eventdata; + $vars->{'cancelaction'} = $cancelaction; + + # Notify the user via email about the cancellation. + my $template = Bugzilla->template_inner($user->setting('lang')); + + my $message; + $template->process("account/cancel-token.txt.tmpl", $vars, \$message) + || ThrowTemplateError($template->error()); + + MessageToMTA($message); + + # Delete the token from the database. + delete_token($token); } sub DeletePasswordTokens { - my ($userid, $reason) = @_; - my $dbh = Bugzilla->dbh; + my ($userid, $reason) = @_; + my $dbh = Bugzilla->dbh; - detaint_natural($userid); - my $tokens = $dbh->selectcol_arrayref('SELECT token FROM tokens + detaint_natural($userid); + my $tokens = $dbh->selectcol_arrayref( + 'SELECT token FROM tokens WHERE userid = ? AND tokentype = ?', - undef, ($userid, 'password')); + undef, ($userid, 'password') + ); - foreach my $token (@$tokens) { - Bugzilla::Token::Cancel($token, $reason); - } + foreach my $token (@$tokens) { + Bugzilla::Token::Cancel($token, $reason); + } } # Returns an email change token if the user has one. sub HasEmailChangeToken { - my $userid = shift; - my $dbh = Bugzilla->dbh; + my $userid = shift; + my $dbh = Bugzilla->dbh; - my $token = $dbh->selectrow_array('SELECT token FROM tokens + my $token = $dbh->selectrow_array( + 'SELECT token FROM tokens WHERE userid = ? - AND (tokentype = ? OR tokentype = ?) ' . - $dbh->sql_limit(1), - undef, ($userid, 'emailnew', 'emailold')); - return $token; + AND (tokentype = ? OR tokentype = ?) ' + . $dbh->sql_limit(1), undef, ($userid, 'emailnew', 'emailold') + ); + return $token; } # Returns the userid, issuedate and eventdata for the specified token sub GetTokenData { - my ($token) = @_; - my $dbh = Bugzilla->dbh; + my ($token) = @_; + my $dbh = Bugzilla->dbh; - return unless defined $token; - $token = clean_text($token); - trick_taint($token); + return unless defined $token; + $token = clean_text($token); + trick_taint($token); - my @token_data = $dbh->selectrow_array( - "SELECT token, userid, " . $dbh->sql_date_format('issuedate') . ", eventdata, tokentype + my @token_data = $dbh->selectrow_array( + "SELECT token, userid, " + . $dbh->sql_date_format('issuedate') + . ", eventdata, tokentype FROM tokens - WHERE token = ?", undef, $token); + WHERE token = ?", undef, $token + ); - # Some DBs such as MySQL are case-insensitive by default so we do - # a quick comparison to make sure the tokens are indeed the same. - my $db_token = shift @token_data; - return undef if (!defined $db_token || $db_token ne $token); + # Some DBs such as MySQL are case-insensitive by default so we do + # a quick comparison to make sure the tokens are indeed the same. + my $db_token = shift @token_data; + return undef if (!defined $db_token || $db_token ne $token); - return @token_data; + return @token_data; } # Deletes specified token sub delete_token { - my ($token) = @_; - my $dbh = Bugzilla->dbh; + my ($token) = @_; + my $dbh = Bugzilla->dbh; - return unless defined $token; - trick_taint($token); + return unless defined $token; + trick_taint($token); - $dbh->do("DELETE FROM tokens WHERE token = ?", undef, $token); + $dbh->do("DELETE FROM tokens WHERE token = ?", undef, $token); } # Given a token, makes sure it comes from the currently logged in user @@ -453,73 +485,73 @@ sub delete_token { # Note: this routine must not be called while tables are locked as it will try # to lock some tables itself, see CleanTokenTable(). sub check_token_data { - my ($token, $expected_action, $alternate_script) = @_; - my $user = Bugzilla->user; - my $template = Bugzilla->template; - my $cgi = Bugzilla->cgi; - - my ($creator_id, $date, $token_action) = GetTokenData($token); - unless ($creator_id - && $creator_id == $user->id - && $token_action eq $expected_action) - { - # Something is going wrong. Ask confirmation before processing. - # It is possible that someone tried to trick an administrator. - # In this case, we want to know his name! - require Bugzilla::User; - - my $vars = {}; - $vars->{'abuser'} = Bugzilla::User->new($creator_id)->identity; - $vars->{'token_action'} = $token_action; - $vars->{'expected_action'} = $expected_action; - $vars->{'script_name'} = basename($0); - $vars->{'alternate_script'} = $alternate_script || basename($0); - - # Now is a good time to remove old tokens from the DB. - CleanTokenTable(); - - # If no token was found, create a valid token for the given action. - unless ($creator_id) { - $token = issue_session_token($expected_action); - $cgi->param('token', $token); - } - - print $cgi->header(); - - $template->process('admin/confirm-action.html.tmpl', $vars) - || ThrowTemplateError($template->error()); - exit; + my ($token, $expected_action, $alternate_script) = @_; + my $user = Bugzilla->user; + my $template = Bugzilla->template; + my $cgi = Bugzilla->cgi; + + my ($creator_id, $date, $token_action) = GetTokenData($token); + unless ($creator_id + && $creator_id == $user->id + && $token_action eq $expected_action) + { + # Something is going wrong. Ask confirmation before processing. + # It is possible that someone tried to trick an administrator. + # In this case, we want to know his name! + require Bugzilla::User; + + my $vars = {}; + $vars->{'abuser'} = Bugzilla::User->new($creator_id)->identity; + $vars->{'token_action'} = $token_action; + $vars->{'expected_action'} = $expected_action; + $vars->{'script_name'} = basename($0); + $vars->{'alternate_script'} = $alternate_script || basename($0); + + # Now is a good time to remove old tokens from the DB. + CleanTokenTable(); + + # If no token was found, create a valid token for the given action. + unless ($creator_id) { + $token = issue_session_token($expected_action); + $cgi->param('token', $token); } - return 1; + + print $cgi->header(); + + $template->process('admin/confirm-action.html.tmpl', $vars) + || ThrowTemplateError($template->error()); + exit; + } + return 1; } sub set_token_extra_data { - my ($token, $data) = @_; + my ($token, $data) = @_; - $data = encode_json($data) if ref($data); + $data = encode_json($data) if ref($data); - # extra_data is MEDIUMTEXT, max 16M - if (length($data) > 16_777_215) { - ThrowCodeError('token_data_too_big'); - } + # extra_data is MEDIUMTEXT, max 16M + if (length($data) > 16_777_215) { + ThrowCodeError('token_data_too_big'); + } - Bugzilla->dbh->do( - "INSERT INTO token_data (token, extra_data) VALUES (?, ?) ON DUPLICATE KEY UPDATE extra_data = ?", - undef, $token, $data, $data); + Bugzilla->dbh->do( + "INSERT INTO token_data (token, extra_data) VALUES (?, ?) ON DUPLICATE KEY UPDATE extra_data = ?", + undef, $token, $data, $data + ); } sub get_token_extra_data { - my ($token) = @_; - trick_taint($token); - my ($data) = Bugzilla->dbh->selectrow_array( - "SELECT extra_data FROM token_data WHERE token = ?", - undef, $token); - return undef unless defined $data; - $data = encode('UTF-8', $data); - eval { - $data = decode_json($data); - }; - return $data; + my ($token) = @_; + trick_taint($token); + my ($data) + = Bugzilla->dbh->selectrow_array( + "SELECT extra_data FROM token_data WHERE token = ?", + undef, $token); + return undef unless defined $data; + $data = encode('UTF-8', $data); + eval { $data = decode_json($data); }; + return $data; } ################################################################################ @@ -529,34 +561,38 @@ sub get_token_extra_data { # Generates a unique token and inserts it into the database # Returns the token and the token timestamp sub _create_token { - my ($userid, $tokentype, $eventdata) = @_; - my $dbh = Bugzilla->dbh; + my ($userid, $tokentype, $eventdata) = @_; + my $dbh = Bugzilla->dbh; - detaint_natural($userid) if defined $userid; - trick_taint($tokentype); - trick_taint($eventdata); + detaint_natural($userid) if defined $userid; + trick_taint($tokentype); + trick_taint($eventdata); - my $is_shadow = Bugzilla->is_shadow_db; - $dbh = Bugzilla->switch_to_main_db() if $is_shadow; + my $is_shadow = Bugzilla->is_shadow_db; + $dbh = Bugzilla->switch_to_main_db() if $is_shadow; - $dbh->bz_start_transaction(); + $dbh->bz_start_transaction(); - my $token = GenerateUniqueToken(); + my $token = GenerateUniqueToken(); - $dbh->do("INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata) - VALUES (?, NOW(), ?, ?, ?)", undef, ($userid, $token, $tokentype, $eventdata)); + $dbh->do( + "INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata) + VALUES (?, NOW(), ?, ?, ?)", undef, + ($userid, $token, $tokentype, $eventdata) + ); - $dbh->bz_commit_transaction(); + $dbh->bz_commit_transaction(); - if (wantarray) { - my (undef, $token_ts, undef) = GetTokenData($token); - $token_ts = str2time($token_ts); - Bugzilla->switch_to_shadow_db() if $is_shadow; - return ($token, $token_ts); - } else { - Bugzilla->switch_to_shadow_db() if $is_shadow; - return $token; - } + if (wantarray) { + my (undef, $token_ts, undef) = GetTokenData($token); + $token_ts = str2time($token_ts); + Bugzilla->switch_to_shadow_db() if $is_shadow; + return ($token, $token_ts); + } + else { + Bugzilla->switch_to_shadow_db() if $is_shadow; + return $token; + } } 1; |