Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable extension pausing when in "Recovery Mode" #6

Closed
wants to merge 47 commits into from

Conversation

TimothyBJacobs
Copy link
Collaborator

See #46130 for an in-depth explanation.

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work so far! I pointed out a few concerns. Here are some more general observations and thoughts (partly also touched in inline comments):

  • I think there should be a class for recovery mode functionality. WP_Recovery_Mode or something like that (should probably be a final class) - simply for the ability to better group functionality together and to make methods private that shouldn't be public.
  • No files should be included anywhere but in code that a 100% surely terminates the request itself.
  • We need to think about whether to use options or network options (in case we're in a multisite that's relevant). I think network options certainly make sense for the fallback key and salt that just replace a constant - for the rest, that's worth discussing.
  • I don't think we need the concept of protected endpoints any longer. If in recovery mode, pause extensions - if not, don't. IMO there's no need for this additional complexity, i.e. the is_protected_endpoint() should be dropped completely in favor of wp_is_recovery_mode().

+1 on using custom code for the hashing etc. - since we can't use the pluggable functions in these circumstances, that's a fine approach I'd say.

src/wp-admin/includes/class-wp-plugins-list-table.php Outdated Show resolved Hide resolved
src/wp-includes/class-wp-fatal-error-handler.php Outdated Show resolved Hide resolved
src/wp-includes/error-protection.php Outdated Show resolved Hide resolved
src/wp-includes/pluggable.php Outdated Show resolved Hide resolved
src/wp-login.php Outdated Show resolved Hide resolved
src/wp-includes/load.php Outdated Show resolved Hide resolved
src/wp-includes/load.php Outdated Show resolved Hide resolved
src/wp-includes/load.php Outdated Show resolved Hide resolved
src/wp-includes/load.php Outdated Show resolved Hide resolved
src/wp-includes/load.php Outdated Show resolved Hide resolved
felixarntz and others added 10 commits March 17, 2019 23:08
This introduces a WP_Recovery_Mode_Controller interface. Core would ship
with the email controller which handles recovery mode by sending an
email and setting a cookie. But this will allow for hosts or mu-plugins
to substitute any handler that implements that interface.

The cookie handler has also been abstracted out to a separate service
and can be reused by a custom handler. It also allows for substituting
all the cookie config values in the constructor. This should make it easier
to ship this eventually to network activated plugins as well, since a primary
concern there is that the correct cookie constants have not been populated
until after those plugins have been loaded.

This does not yet drop `is_protected_endpoint()` since that is still to be
discussed. Additionally, I have not had time yet to refactor the extension
storage to use one option as @flixos90 suggested.
- Support RECOVERY_MODE_EMAIL constant to set the email address.
- Change default rate limit to 4 hours.
- Ensure link is valid for at least as long as the rate limit duration.
- Only send recovery mode email when the errors occurs in a protected endpoint.
This allows for better reusability for alternate recovery mode controllers
and allows for better testing.
of firing an action.

This mostly reflects the Google Docs proposed version. I don't think the
recovery mode controller should at all be responsible for pausing extensions.
This makes extending the functionality through drop-ins unsafe since we are
moving unrelated functionality into that method. If we change how extensions
are recorded or want to perform different behavior, the drop-ins would be
incompatible.
@TimothyBJacobs
Copy link
Collaborator Author

User Flow & Technical Doc: https://docs.google.com/document/d/1XWQghXj0p0X8GF-Byuv4vfPQG0Rs0XoVfOepVjiMkfM/edit
From Slack:

I do think we are in a good place extensibility wise to be able to support a user email specific flow in 5.3 with our current framework and IMO could be implemented as a drop-in with some minor changes. The main thing being an exit_recovery_mode() method of some kind.

I think that putting an exit mode in the admin bar makes sense. I think it’d be quickest to add the underlying technical support for exiting recovery mode, like a method, in the patch before commit and then worry about that specific UI shortly thereafter.
From a thread in #core-phpToday at 8:55 AMView reply

Looking at a specific of the technical document, I don’t think we should overload the is_active() method on the controller. That will make it harder to support more advanced recovery mode procedures in the future. I’m fairly confident that just having a session ID will be enough to support per-user recovery mode, ie we don’t actually need to provide the user ID. If we do find that we need actual data about the user, then I think that’d be better off by implementing a specific WP_User_Specific_Recovery_Mode_Controller interface with a get_user_id(): int method ( or something similar ) that way we can properly handle recovery mode controllers that are site wide, like the current controller.
I also think we should keep the separation of concerns with the additional helper classes, instead of moving many of these to private methods. Moving them into separate classes allows for better unit testability, see Tests_Recovery_Mode, eases understanding by grouping similar functionality together, and makes it substantially easier for drop-ins or other customer recovery mode controllers to work. This will give them the helper libraries they need so they aren’t forced to reinvent the wheel or copy parts of the original controller implementation .

@felixarntz
Copy link
Member

Looking at a specific of the technical document, I don’t think we should overload the is_active() method on the controller. That will make it harder to support more advanced recovery mode procedures in the future. I’m fairly confident that just having a session ID will be enough to support per-user recovery mode, ie we don’t actually need to provide the user ID.

Given the current approach, this completely makes sense. Let's have a is_active() and separate get_session_id() method. Later, we could potentially store a session ID in user meta to associate them, if needed.

I also think we should keep the separation of concerns with the additional helper classes, instead of moving many of these to private methods.

Yes. The reason I made them private methods in the doc was mostly to have a better understanding when reading about them - I see it as a starting point to abstract from further. The multiple classes approach is better organized, but a bit harder to grasp at first glance.

@nerrad
Copy link

nerrad commented Mar 18, 2019

For first iteration (release in 5.2) I think this should be disabled for WordPress multisite. There's ux/ui workflows that need to be considered in a multisite environment and typically those working with multisite have a better handle on dealing with php/plugin upgrades etc. so the urgency for these protections in that environment isn't as great.

Copy link
Member

@felixarntz felixarntz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a more thorough review and pointed out some details. Most of it is looking good.

A few more general things:

  • Let's keep the WP_Fatal_Error_Handler class as generic as it can be. Storing errors, redirecting, etc. already ties in with recovery mode and how we envision it, so these things should be covered by the recovery mode controller. The only connection should be the fatal error handler calling the handle_fatal_error() method.
  • Let's be conservative and not expose too much code through functions that we only call in one location. We use class structures here, and the feature is in a first iteration, so we don't want to expose too much for outside usage here.
  • The same applies to overriding. People can override the fatal error handler, and thus technically already have the ability to override in whichever way they want. For the first iteration, let's not get more granular, i.e. not allow to override the recovery mode handler specifically. In is_recovery_mode_active(), we can check a constant that MU plugins can define - that allows for customizing that bit, while not restricting us in what we can do to our class infrastructure later. We don't want to corner ourselves.
  • Function/method descriptions in doc-blocks should start with a third-person verb rather than infinitive. :)

src/wp-admin/includes/class-wp-plugins-list-table.php Outdated Show resolved Hide resolved
src/wp-admin/includes/class-wp-plugins-list-table.php Outdated Show resolved Hide resolved
src/wp-admin/includes/class-wp-plugins-list-table.php Outdated Show resolved Hide resolved
src/wp-admin/includes/class-wp-plugins-list-table.php Outdated Show resolved Hide resolved
src/wp-includes/error-protection.php Outdated Show resolved Hide resolved
src/wp-includes/error-protection.php Outdated Show resolved Hide resolved
src/wp-includes/error-protection.php Outdated Show resolved Hide resolved
tests/phpunit/tests/user/capabilities.php Show resolved Hide resolved
but save the error to the same option. Drop multisite option handling.
This can be reintroduced safely in a later version when we want to
support Multisite.
@felixarntz
Copy link
Member

Just taking a note here that we should wrap the calls in wp-settings.php into a ! is_multisite(), as the feature will be disabled for multisite in v1.

Now `WP_Recovery_Mode` is the object responsible for managing recovery mode.
WP_Fatal_Error_Handler passes off control to `WP_Recovery_Mode::handle_error()`
when a fatal error occurs.

`WP_Recovery_Mode_Email_Controller` is renamed to `WP_Recovery_Mode_Email`.
This keeps the line between general Recovery Mode processing, like
handling storing errors and doing redirects, separate from the method
of notifying the user to enter recovery mode.

Adjusted redirect_protected functionality to account for when headers
have already been sent ( display errors being on ). This right now just
displays the default error template. We might want to adjust that.

Adds a admin menu bar link to exit recovery mode.

The cookie check can be overridden by using the `WP_RECOVERY_MODE_SESSION_ID`
constant, but this probably shouldn't be encouraged.
…very

mode and have an active session ID before use.

This blocks mistakes from 3rd party code loading the storage
instance too early.
),
array(
$url,
'TBD',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBD!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is pending this issue: WordPress/servehappy#40 :)

src/wp-login.php Outdated Show resolved Hide resolved
@felixarntz
Copy link
Member

Merged against latest trunk, where the basic fatal error handler is already committed now.

@felixarntz
Copy link
Member

Done! https://core.trac.wordpress.org/changeset/44973

Thanks for this truly outstanding work @TimothyBJacobs! 🎉

@felixarntz felixarntz closed this Mar 21, 2019
@TimothyBJacobs
Copy link
Collaborator Author

Thank you! The recovery mode code wouldn't be anywhere near where it is without your extensive help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants