Skip to content

[3.0]: Upgrading from 2.1 stops in DropTimeOffset, and TFA logins and old background tasks break afterwards #9521

Description

@albertlast

Basic Information

Upgrading a real SMF 2.1 forum to 3.0 stops at DropTimeOffset, and two further defects surface immediately after the upgrade finishes. All three are engine-independent; the PostgreSQL-only blockers on the same journey are #9519.

I found these by restoring the committed SMF 2.1.7 baseline from #9330 (403 members, 6 000 messages, 24 boards) and running upgrade.php over it.

1. DropTimeOffset reads a column the query does not select.

Sources/Maintenance/Migration/v3_0/DropTimeOffset.php selects time_offset:

$request = $this->query(
    'SELECT DISTINCT time_offset
    FROM {db_prefix}members
    WHERE timezone = {empty}',
);

while ($row = Db::$db->fetch_assoc($request)) {
    if (isset($offsets[$row['offset']])) {      // line 70
        continue;
    }

    $offset = (int) ($forum_utc_offset + $row['time_offset'] * 3600);
    ...
    $offsets[$row['time_offset']] = ...;         // line 86 reads $row['offset']

Two of the three reads use $row['offset'], which does not exist. $offsets therefore ends up with a single entry keyed by the empty string, and the UPDATE built from it binds '' to a {float:…} placeholder:

Wrong value type sent to the database. Floating point number expected. (d41d8cd98f00b204e9800998ecf8427e)

d41d8cd98f00b204e9800998ecf8427e is md5(''), which is where the empty key surfaces.

2. The same migration then fails on the placeholder names.

With the column name corrected, the next statement is:

foreach ($offsets as $offset => $tzid) {
    $set .= ' WHEN time_offset = {float:' . md5($offset) . '} THEN {string:' . md5($tzid) . '}';

PHP casts numeric string array keys to integers, so $offset is an int and, under declare(strict_types=1):

md5(): Argument #1 ($string) must be of type string, int given

Both need fixing before a forum with time offsets can be upgraded at all — and a 2.1 forum where nobody set a timezone is the normal case, since 2.1 stored offsets rather than zones.

3. After the upgrade, a member with two-factor authentication cannot log in.

The password is accepted and the redirect to the second factor happens, and then:

302 /index.php?action=login2
302 /index.php?action=login2;sa=check;member=1
500 /index.php?action=logintfa
Cannot assign null to property SMF\User::$dataset of type SMF\UserDataset

That is Sources/User.php:4265, $this->dataset = $profile['dataset'];, reached with a profile that has no dataset key. Two more are logged alongside it:

2: Undefined array key "linktree"
2: foreach() argument must be of type array|object, null given

The account is simply locked out — there is no way past the second factor. #9302 touches LoginTFA.php, but only to change parent::validatePasswordFlood() to Security::validatePasswordFlood() on the wrong-code path, so it does not cover this.

4. Background tasks queued under 2.1 keep their old class names.

smf_background_tasks survives the upgrade untouched, so rows written by 2.1 still name 2.1 classes:

Invalid background task specified: class CreatePost_Notify_Background not found
Invalid background task specified: class Update_TLD_Regex not found

3.0 calls that first one SMF\Tasks\CreatePost_Notify. The rows are never removed, so the task runner rediscovers them on every pass and logs the same error indefinitely.

Steps to reproduce

  1. Restore the 2.1.7 baseline from [2.1][Testing] Add a Docker development environment and a baseline forum for upgrade testing #9330, or point a 3.0 checkout at any real 2.1 database where members have a non-zero time_offset and an empty timezone.
  2. Run upgrade.php.
  3. Correct the two DropTimeOffset defects and run it again.
  4. Log in as a member who had two-factor authentication enabled on 2.1.
  5. Watch smf_log_errors for a few minutes.

Expected result

The upgrader runs to the end. Members log in, second factor included. Nothing accumulates in the error log.

Actual result

  1. The upgrader stops in DropTimeOffset with the floating point message.
  2. It stops in the same migration with the md5() type error.
  3. action=logintfa is a 500 and the account cannot get in.
  4. "Invalid background task specified" repeats forever.

Version/Git revision

3.0 Alpha 4, release-3.0 at bfbca5b

Database Engine

All

Database Version

MySQL 8.4; also reproduced on PostgreSQL 17 for (1) and (2)

PHP Version

8.4.24

Logs

# 1
PHP Warning:  Undefined array key "offset" in .../Migration/v3_0/DropTimeOffset.php on line 70
PHP Warning:  Undefined array key "offset" in .../Migration/v3_0/DropTimeOffset.php on line 86
Wrong value type sent to the database. Floating point number expected. (d41d8cd98f00b204e9800998ecf8427e)

# 2
+++ Removing time_offset column from members table... failed with error:
    "md5(): Argument #1 ($string) must be of type string, int given"

# 3
Cannot assign null to property SMF\User::$dataset of type SMF\UserDataset

# 4
SELECT DISTINCT task_class FROM smf_background_tasks;
  CreatePost_Notify_Background
  \SMF\Tasks\Utf8EntityDecode
  SMF\Tasks\FetchSMfiles
  SMF\Tasks\UpdateSpoofDetectorNames
  SMF\Tasks\SendDigests

Additional Information

Found while testing #9517; none of these are related to it — DropTimeOffset.php, User.php, LoginTFA.php and TaskRunner.php are byte-identical between release-3.0 and that branch. Once (1) and (2) are patched the upgrade completes on MySQL and the forum works.

Related: #9519 (the PostgreSQL-only blockers on the same upgrade), #9330 (the 2.1 baseline used to reproduce this).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions