• Hi! I’m getting a very weird error that I can’t really explain.

    I have some Python code that sends a request to a custom REST API endpoint I added. I have a plugin that listens to the request and adds a new user by doing:

        $random_password = wp_generate_password(8, false);
        $user_data = array(
            'user_login' => $user_email,
            'user_pass'  => $random_password,
            'user_email' => $user_email,
            'role'       => 'subscriber',
            'first_name' => $first_name,
            'last_name'  => $last_name,
        );
    
        $user_id = wp_insert_user($user_data);
    

    where the fields are passed from the REST API HTTP request.

    For some reason the plugin works as expected if I send the request from my local machine, but it doesn’t work when the request is sent from my AWS Lambda, giving a "User insertion failed: Not enough data to create this user." error.

    When I print out the request content, the one sent from my local machine looks identical to the one sent from my AWS Lambda.

    When I print out $user_data in debug.log, the content and type of the array is identical.

    This is the code I use to print out the array:

    echo "Type of \$user_data: " . print_r(gettype($user_data)) . " -- ";
    
    if (empty($user_data)) {
        echo "\$user_data is empty." . " -- ";
    } else {
        echo "\$user_data is not empty." . " -- ";
        echo "Contents of \$user_data: " . " -- ";
        print_r($user_data);
    }
    
    if (is_wp_error($user_id))
    {
        $error_message = $user_id->get_error_message();
        print_r("User insertion failed: $error_message");
        $result = false;
    }
    else
    {
        $result = $user_id;
    }

    and this is the error I get:

    "arrayType of $user_data: 1 -- $user_data is not empty. -- Contents of $user_data:  -- Array\n(\n    [user_login] => [email protected]\n    [user_pass] => aa0fDLcG\n    [user_email] => [email protected]\n    [role] => subscriber\n    [first_name] => John\n    [last_name] => Doe\n)\nUser insertion failed: Not enough data to create this user.{\"code\":\"insert_error\",\"message\":\"Error in inserting user with the user_email [email protected]\"
    

    As it can be seen the array is not empty but I still get the Not enough data to create this user error.

    Unfortunately with my current host I don’t have access to wp-include, so I can’t debug wp_insert_user() directly from user.php.

    Thank you!

Viewing 12 replies - 1 through 12 (of 12 total)
  • Moderator bcworkz

    (@bcworkz)

    The only way to get the “Not enough data…” error is if $userdata is either empty or it’s not an array. If you’re sure you’ve passed the correctly formed array to wp_insert_user(), the only way $userdata could be seen as empty or not an array is if some filter code somewhere altered $userdata. For example a function hooked to the ‘wp_pre_insert_user_data’ filter.

    Such a function could come from your theme or any plugin. As a test you should switch to a default Twenty* theme and deactivate all plugins except what you need to implement your API endpoint. If this API code is part of your theme, temporarily copy the code over to the default theme you choose to use.

    I don’t know why such a filter callback would behave differently depending on the source of the request, but it’s possible to do so. Maybe it’s some kind of poorly considered security measure?

    Thread Starter jacopol

    (@jacopol)

    Thank you for you answer. Which kind of poorly considered security measure are you thinking about?
    Trying again tonight gives the same error even when sending the request from my local machine. Not a step forward, but makes more sense.

    Since this is how the wp_insert_user() looks like when giving that error

    	$data = apply_filters( 'wp_pre_insert_user_data', $data, $update, ( $update ? $user_id : null ), $userdata );
    
    	if ( empty( $data ) || ! is_array( $data ) ) {
    		return new WP_Error( 'empty_data', __( 'Not enough data to create this user.' ) );
    	}

    I tried to intercept $data right before calling wp_insert_user() with this

    add_filter( 'wp_pre_insert_user_data', function( $data ) {
       error_log( print_r( $data, true ) );
    }, 20 );

    and the printout looks reasonable and definitely not empty

    [user_pass] => $P$BNIhibiz/od63ARGcF5NfjAKEVHaxV0
    [user_nicename] => johndoejohndoe-com
    [user_email] => [email protected]
    [user_url] =>
    [user_registered] => 2024-02-01 20:26:07
    [user_activation_key] =>
    [display_name] => John Doe
    [user_login] => [email protected]

    I tried to add the missing fields (user_url and user_activation_key) but it made no difference.

    Moderator bcworkz

    (@bcworkz)

    Some odd attempt at security is the only explanation I could think of to explain differing behavior based on the source of the request. I do not have any particular measure in mind. A typical referrer based security measure might be seen when a request comes from China or Russia instead of the expected Western world. But such a distinction doesn’t make much sense from local machine vs. AWS.

    Trying again tonight gives the same error even when sending the request from my local machine.

    That kinda makes the entire security measure theory moot, doesn’t it? ??

    You’ve probably seen this, this is the code immediately after the “wp_pre_insert_user_data” filter:

    	if ( empty( $data ) || ! is_array( $data ) ) {
    		return new WP_Error( 'empty_data', __( 'Not enough data to create this user.' ) );
    	}

    It’s the only place this error message is generated. As you can see, only if $data is empty or is not an array will the error be returned.

    Maybe another filter callback is acting on $data after your callback logs the data? Try replacing the 20 you use for $priority with PHP_INT_MAX. This helps to ensure your callback is the very last one to execute.

    If you still get an array with valid content and yet the error is returned, I’ve no explanation. Behavior contradicts the code we see in use.

    Thread Starter jacopol

    (@jacopol)

    Thank you for your answer.

    My local machine is based in Sweden, while the AWS is in the US. I guess that could explain any block based on location?

    The security is entirely handled by my host, and I don’t have access to the config files. The host also uses Jetpack, but I got the same behaviour with Jetpack deactivated, so I don’t think that’s the cause of the problem.

    As you suggested I tried with PHP_INT_MAX in the filter and I got the same results. I also made sure empty( $data ) and is_array( $data ) are returning the correct values with the intercepted $data.

    On my staging website, that has all the plugins in sync with my main production website (where I’m getting the issue), I can still submit successfully the request from my local machine, although the AWS doesn’t work there either.

    As you suggested, all this kind of makes me think there is some weird blocks my host is using, for security reasons, on programmatic user insertion. I initially was thinking of differences in encoding (or something similar) that somehow broke $data, but if intercepting it with PHP_MAX_INT gives a correctly composed array, the issue must not be in my plugin.

    Moderator bcworkz

    (@bcworkz)

    You say the data appears correct. Does your print_r() output explicitly say that $data is an array and not an object? And assuming it’s an array, it wouldn’t be nested within another? The output would mention “Array” more than once if nested.

    Even though you cannot access core WP files to debug the function, there’s little code there to prevent user insertion or to debug. After the filter and the empty array check, the only relevant new user code (not updating an existing user) is:

    $wpdb->insert( $wpdb->users, $data );
    $user_id = (int) $wpdb->insert_id;
    $user = new WP_User( $user_id );

    Nothing to prevent insertion other than a DB error, which would result in a very different error message. The empty array check has to somehow be triggered despite $data not being empty.

    Did you try my earlier suggestion? This:

    As a test you should switch to a default Twenty* theme and deactivate all plugins except what you need to implement your API endpoint. If this API code is part of your theme, temporarily copy the code over to the default theme you choose to use.

    Additionally, there could be a must-use plugin that could interfere. The only way to disable must-use plugins is to rename the folder they reside in.

    Would you be able to test your plugin on another WP installation hosted elsewhere, or on a local WP installation? If your plugin works elsewhere, then it’s evident there’s something odd about the current site’s installation or host. Maybe the wp_insert_user() function isn’t what we think it is. Even if you cannot access it to debug, can you at least read the file to examine its code? Or download a copy for local examination?

    Why is there a custom API endpoint for this anyway? Why not use the built-in /users endpoint for this? Have you tried adding a new user through the built-in endpoint?

    The following shouldn’t result in the error message we see, and it’s probably of no consequence, but you are inserting a new user whose data in part includes:
    [user_pass] => $P$BNIhibiz/od63ARGcF5NfjAKEVHaxV0
    That’s a hashed password. A hash is correct when updating an existing user, but with a new user the password should be in plain text. I suppose WP would just hash the string as provided and then happily insert the user. The user would not be able to log in with what they thought was their password, but it should not cause any other errors.

    Thread Starter jacopol

    (@jacopol)

    This is what I’m using to print for debugging. I added conditions to directly check what the if case causing the error is evaluating.

    add_filter( 'wp_pre_insert_user_data', function( $data ) {
      error_log( print_r( $data, true ) );
      error_log( is_array($data) ? 'is array' : 'is NOT array' );
      error_log( empty($data) ? 'is empty' : 'is NOT empty' );
    }, PHP_INT_MAX );

    This is what’s printed

    [04-Feb-2024 20:40:57 UTC] Array
    (
        [user_pass] => $P$B6jXtADMIdzWpoij5dafcvqfLLgJxy0
        [user_nicename] => jacopoprovaprova-com
        [user_email] => [email protected]
        [user_url] => 
        [user_registered] => 2024-02-04 20:40:52
        [user_activation_key] => 
        [display_name] => Jacopo Prova
        [user_login] => [email protected]
    )
    
    [04-Feb-2024 20:40:57 UTC] is array
    [04-Feb-2024 20:40:57 UTC] is NOT empty

    So everything looks correctly composed to me, and, as long as my host is not doing some custom development over the WP open source code, I don’t see how that if case is triggering the error.

    As I mentioned, my staging website still works when the request is arriving from my local machine. My staging website uses a different theme (the standard Twenty* one) while my production uses the Intentionally Left Blank theme, since I use Oxygen builder that overwrites it. The plugin on my production website broke (for requests coming from my local machine) with the theme not changing since the plugin was working, so I don’t think the issue is related to the theme. But I can do some more test there.

    On my local WP installation my plugin works. Also there I have the Twenty* theme (although a different one). Unfortunately my host doesn’t provide access to wp-includes/ so I don’t have a way to check if any custom code was added to the open source WP installation. I contacted them regarding the issue and I’m waiting for an answer.

    The API endpoint I’m using is a custom one I added. I have some custom tables I add rows to when receiving the POST request, so my plugin (listening to the custom endpoint) takes care of everything. In some cases, depending on the body of the request, I have to add the new user. So it was easier to just call wp_insert_user() instead of invoking the WP REST API just for that.

    Thank you for the password hint. I simply used wp_generate_password() assuming is was compatible. It’s not important since the password is just a place holder.

    Moderator bcworkz

    (@bcworkz)

    Apologies if I’ve gone over ground that had already been covered. I’m resisting the need to re-read this lengthy topic to refresh my not so great memory.

    In summary, it sounds like your endpoint works great everywhere but production, and it fails in production regardless of theme or plugin state. So there must be something different about production that cannot be replicated elsewhere.

    I agree that it’d be extremely unusual for a host to modify a core WP installation. However, many hosts do install a must-use plugin that they need to have WP be better compatible with their server configuration. Typically these just deal with minor technicalities that do not affect basic operation. I cannot imagine such a plugin would influence user related functionality, but a small possibility does exist.

    I do have one additional thought regarding testing different theme and plugin configurations. Sometimes caching, either client and/or server side, can confuse such investigations. This would normally apply to conventional web pages. I don’t think API responses would normally be cached, but again, there is the possibility none the less.

    The fact your filter callback logs valid data and yet the “Not enough data…” error is triggered is baffling. I did confirm that after the ‘wp_pre_insert_user_data’ filter is indeed the only place in all of core WP where the error message occurs in its PHP code. The message could not possibly be fired from any other process unless a theme or plugin process has replicated the exact same message.

    I’m left with no plausible explanation or theory for the behavior you are observing, other than there’s something different about your production installation. The lack of access to the core code in use makes it difficult if not impossible to investigate further in adequate detail.

    As you said, the password thing is unimportant, but it’s odd you’d get a hashed value from wp_generate_password(). Unless you requested that it generate a very long password (default length is just 12) and it just randomly happened to look like a hash value. The function does have a filter so it’s conceivable a plugin or theme is altering the normal return value. Even as production code it probably is unimportant. You’d either use a user provided value or use any random value and the user would need to set their password later.

    Thread Starter jacopol

    (@jacopol)

    No worries, I appreciate your help with this and don’t expect you to read back at all the previous messages.

    Just as quick recap:
    1. Production website: plugin always broken, independently from request source. Request from local machine broke and no theme or plugin was updated since the last working request sent from local machine.
    2. Staging website: plugin works only if request is sent from my local machine. Different theme wrt production site, but that shouldn’t matter because of 1. Plugins are synced to the production site.

    I had some insightful back and forth with my host’s support. They worked on my staging website.

    There was an idea about some corrupted state of the Theme My Login plugin I’m using to handle registration. The state of the plugin was, allegedly, reset by the host support, but it only worked for the first request sent from my AWS Lambda, and broke again right at the second one. Now it’s back to the same behaviour described in 2.

    The only stable solution I found is to directly run the query to add new users to my users and usermeta tables via wpdb::query. I will add the checks done by wp_insert_user() to ensure the fields are properly sanitized, but I will skip the filters. Those are hard to track and seems to be the only source of issue here.

    I’m not sure what skipping those filters entitles, I’m no expert WP developer at all, but I’ll try to read a bit more about it and act accordingly.

    Thread Starter jacopol

    (@jacopol)

    One thing I don’t understand, about the possible explanations related to the corrupted state of Theme My Login and the issue with the hashed password: How are these only affecting the requests sent from my AWS Lambda (in my staging website)? The cached Theme My Login state is not depending on the HTTP request source and the password is internally generated by the plugin, so completely independent from the HTTP request.

    Thread Starter jacopol

    (@jacopol)

    Regarding the unexplained password hashing. Theme my login has this filter:

    function tml_ms_filter_pre_insert_user_data( $data = array() ) {
    	if ( tml_allow_user_passwords() && ! empty( $_POST['user_pass1'] ) ) {
    		$data['user_pass'] = wp_hash_password( $_POST['user_pass1'] );
    	}
    	return $data;
    }

    that is hooked to wp_pre_insert_user_data. So I guess that explains it.

    Moderator bcworkz

    (@bcworkz)

    If directly inserting the user via $wpdb methods works for you, I’d say do it! Be sure all the data is validated and sanitized before saving it. Usually all the filters in wp_insert_user() are for is just to give theme and plugin devs an opportunity to modify how the submitted data is handled. By omitting the filters you’re likely preventing other plugins from modifying the process. Whether that has any impact at all varies from installation to installation.

    There are some instances where WP core itself hooks into certain filters. Omitting one of these filters in a process could have an adverse effect on functionality. I don’t know if any filters in wp_insert_user() are used by WP core or not, but I think it’s pretty unlikely. You could apply the same filters in your own process. It’ll likely make little difference as long as you don’t do the empty or not array error check ??

    I’m afraid I’ve confused the password hash topic. I neglected to consider the difference between data passed to wp_insert_user() and the data we see in the ‘wp_pre_insert_user_data’ filter. For new users only, we pass plaintext to the function. But by the time the filter fires, the password had already been hashed beforehand. I’m not sure why TML is redoing the password hash, but it’s fine.

    Thread Starter jacopol

    (@jacopol)

    Yes I’ll go with the $wpdb methods since it’s the only way this is stable. I’m replicating the sanitization and filters from wp_insert_user() so I should be safe. Also, the endpoint is never exposed publicly and it’s only accessed by the AWS Lambda that is triggered by users purchasing a product, so the environment where all this happens should be relatively controlled. Also considering I do all the checks on application password and what not way before the user insertion is ever needed.

    For the few filters I’m skipping, I checked and nothing is really done on those, except for wp_pre_insert_user_data of course, where TML does the password hashing. Since I take explicit care fo the hashing myself, everything should be good.

    Thank you for your help! I’ll update here if I ever manage to fix this without the current workaround.

Viewing 12 replies - 1 through 12 (of 12 total)
  • The topic ‘wp_insert_user() failing but only from AWS Lambda.’ is closed to new replies.