Manual:SessionManager and AuthManager/Updating tips

This list common situations in extensions that will need updating for SessionManager and AuthManager, and general fixes.

SessionManager

edit

Accessing session data ($_SESSION)

edit

Previously, code could access $_SESSION anywhere.

Anywhere you have a WebRequest, you have a Session via $webRequest->getSession(). If you really don't have a WebRequest, \MediaWiki\Session\SessionManager::getGlobalSession() is available.

$webRequest->getSessionData() and $webRequest->setSessionData() may be used without any backwards-compatibility testing to replace direct accesses of $_SESSION, although they're inefficient if you're getting/setting many variables (since they may have to load and unload the session with each call) and they can't be iterated over. The Session object can be iterated over.

Checking if a session is active (session_id() !== '')

edit

Previously, code would use session_id() !== '' to test if a session is active, i.e. that session cookies have been sent to the browser and session data is loaded.

Under SessionManager, the session data is always available to the Session object. To test if the session is "active" in the sense described above, call $session->isPersistent().

Starting a session (wfSetupSession())

edit

Previously, code would use wfSetupSession() to ensure that the session data was loaded and to make sure that the session cookies have been sent to the browser.

Under SessionManager, the session data is always available to the Session object. To ensure that session cookies (or equivalent) have been sent to the browser, call $session->persist().

Note that old code would often do if ( session_id() == '' ) { wfSetupSession(); }. This may be replaced with simply $session->persist().

Logged warning "User::loadFromSession called before the end of Setup.php"

edit

To avoid issues with access to the session before the session has been set up, User::loadFromSession() will no longer load the user until after the end of Setup.php. This means that early hooks and $wgExtensionFunctions callbacks cannot access $wgUser, $wgLang, and the like, nor the corresponding methods on RequestContext.

The general fix is to either remove the need or to use $user->isSafeToLoad() to test for the situation and use a default for whatever user-related thing you're trying to access.

Exception "Sessions are supposed to be disabled for this entry point"

edit

Certain entry points, such as load.php, are not supposed to be depending on the active session at all. These entry points may define MW_NO_SESSION to cause attempts to access the session to fail.

The majority of these errors are caused by attempted use of $wgLang (or, equivalently, RequestContext::getMain()->getLanguage()), since Message objects default to using that. If you're in something ResourceLoader-specific, you'll want to use the language on the ResourceLoaderContext instead of letting Message default to the session user's language.

Otherwise, you'll have to look at the backtrace and work out why your code is trying to access the session and how to avoid it. Generally, overriding the language globals is not going to be a good solution.

UserLoadFromSession hook

edit

If you were implementing the UserLoadFromSession hook before, chances are good that you'll want to implement either a SessionProvider or a PrimaryAuthenticationProvider now. See Manual:SessionManager and AuthManager#As a provider for details.

AuthManager

edit

AddNewAccount and AuthPluginAutoCreate hooks

edit

The AddNewAccount hook was called when a user was created locally, either via the web UI or the API. The AuthPluginAutoCreate hook was called when a user was auto-created. Both of these hooks have been replaced by the LocalUserCreated hook.

If you were using AddNewAccount in conjunction with the UserCreateForm hook to add new fields to the account creation form and then do something to the just-created user based on those fields, you'll likely want to implement an AuthenticationRequest that defines the fields you were adding and a SecondaryAuthenticationProvider that handles that AuthenticationRequest in its beginSecondaryAccountCreation() method.

AbortAutoAccount, AbortChangePassword, AbortLogin, and AbortNewAccount hooks

edit

If you're using one of these hooks, you'll generally want to implement a PreAuthenticationProvider or a SecondaryAuthenticationProvider.

If you were using one of these hooks, congratulations! Not many people cared about the API. Under AuthManager nothing special is needed to support account creation through the API, so when you update your use of the UserCreateForm hook you'll likely have already taken care of this as well.

These hooks were used to add fields to the login form, the user creation form, and the password change form. Under AuthManager, this is done by an AuthenticationProvider returning one or more AuthenticationRequests defining the fields.

If you were using these hooks to inject text rather than form fields, you might use the AuthChangeFormFields hook instead.

LoginPasswordResetMessage hook

edit

This hook was intended to change the message when a post-login password reset was triggered. However, it was soon replaced even in the old code by allowing the use of LoginForm::mAbortLoginErrorMsg in conjunction with LoginForm::RESET_PASS. Under AuthManager, mid-login password reset is triggered by signalling ResetPasswordSecondaryAuthenticationProvider, and that signal necessarily includes a custom message.

This hook was used to give a more specific error message when a user was renamed. Under AuthManager, the PrimaryAuthenticationProvider would return the error message directly instead.

This hook will no longer be called for logins via the API, but will still be used for logins via Special:UserLogin. Extensions that want to process all logins should use the UserLoggedIn hook instead.

Also, extensions that are trying to track all logins should consider what they can do about SessionProviders that bypass login entirely, such as Extension:OAuth.

LoginAuthenticateAudit and PrefsPasswordAudit hooks

edit

These hooks are replaced by AuthManagerLoginAuthenticateAudit and ChangeAuthenticationDataAudit.

For extension developers, add your PrimaryAuthenticationProvider to $wgAuthManagerAutoConfig['primaryauth'] instead.

For system administrators configuring the wiki, the equivalent to configure authentication for your wiki is either $wgAuthManagerAutoConfig (populated by extensions as they're loaded) or $wgAuthManagerConfig (for entirely manual configuration).

$wgAuth

edit

For system administrators configuring the wiki, the equivalent to configure authentication for your wiki is either $wgAuthManagerAutoConfig (populated by extensions as they're loaded) or $wgAuthManagerConfig (for entirely manual configuration).

For MediaWiki coders, the $wgAuth global was used to access the active AuthPlugin. The various methods of $wgAuth from the perspective of an external caller have been replaced as follows:

  • userExists()AuthManager::singleton()->userExists()
  • allowPropChange()AuthManager::singleton()->allowsPropertyChange()
  • canCreateAccounts()AuthManager::singleton()->canCreateAccounts()
  • allowPasswordChange() and setPassword() have no direct replacements, since there are now many different kinds of authentication credentials rather than just passwords. Setting the password to null for a "system" account should now be done by using User::newSystemUser().
  • allowSetLocalPassword() is replaced by either not using LocalPasswordPrimaryAuthenticationProvider or by configuring it with loginOnly = true.
  • setDomain(), getDomain(), validDomain(), and domainList() are no longer necessary. Anything that cares should have the domain as part of its AuthenticationRequests.
  • getUserInstance() has no replacement. The methods of the AuthPluginUser class are replaced as follows, where $user is the User object that would have been passed to getUserInstance():
    • getId()$user->getId() or CentralIdLookup
    • isLocked()$user->isLocked()
    • isHidden()$user->isHidden()
    • resetAuthToken()SessionManager::singleton()->invalidateSessionsForUser( $user )

The remaining methods were generally for internal use in LoginForm. Under AuthManager, it's even more likely that an attempt to roll your own replacement for SpecialUserLogin, SpecialCreateAccount, ApiClientLogin, and ApiAMCreateAccount is a bad idea.

The AuthPlugin and AuthPluginUser classes

edit

If you were implementing an AuthPlugin before, you'll generally implement a subclass of AbstractPasswordPrimaryAuthenticationProvider now. Specific replacements for AuthPlugin methods and related hooks are:

  • The AuthPluginSetup hook → Adding an entry to $wgAuthManagerAutoConfig.
  • AuthPlugin::userExists()PrimaryAuthenticationProvider::testUserExists()
  • AuthPlugin::authenticate()PrimaryAuthenticationProvider::beginPrimaryAuthentication(), and use a PasswordAuthenticationRequest to get the username and password.
  • AuthPlugin::modifyUITemplate() → return appropriate AuthenticationRequests from AuthenticationProvider::getAuthenticationRequests(), and tweak the field definitions with the AuthChangeFormFields hook.
  • AuthPlugin::setDomain(), AuthPlugin::getDomain(), AuthPlugin::validDomain(), and AuthPlugin::domainList() → Use a PasswordDomainAuthenticationRequest or something similar instead of the normal PasswordAuthenticationRequest.
  • AuthPlugin::updateUser() → the UserLoggedIn hook
  • AuthPlugin::autoCreate() has no direct replacement. The extension should never create local accounts itself under AuthManager, it should instead return a successful AuthenticationResponse with the name of the user to be created that doesn't already exist locally.
  • AuthPlugin::allowPropChange()PrimaryAuthenticationProvider::providerAllowsPropertyChange()
  • AuthPlugin::allowPasswordChange()PrimaryAuthenticationProvider::providerAllowsAuthenticationDataChange() when called with a PasswordAuthenticationRequest. Note that it might also be called for other AuthenticationRequest types!
  • AuthPlugin::allowSetLocalPassword() has no direct replacement. If the wiki's sysadmin wants to disallow setting passwords in the user table, they can either not use LocalPasswordPrimaryAuthenticationProvider or configure it with loginOnly = true.
  • AuthPlugin::setPassword()PrimaryAuthenticationProvider::providerAllowsAuthenticationDataChange() (when called with a PasswordAuthenticationRequest) to determine if the password being set is acceptable, and PrimaryAuthenticationProvider::providerChangeAuthenticationData() (when called with a PasswordAuthenticationRequest) to actually perform the change.
  • AuthPlugin::updateExternalDB() → the UserSaveSettings hook
  • AuthPlugin::updateExternalDBGroups() → the UserGroupsChanged hook
  • AuthPlugin::canCreateAccounts() → returning PrimaryAuthenticationProvider::TYPE_NONE from PrimaryAuthenticationProvider::accountCreationType(). Note other providers might still allow creating accounts.
  • AuthPlugin::addUser() for normal account creation → PrimaryAuthenticationProvider::testUserForCreation() to check the user name, PrimaryAuthenticationProvider::testForAccountCreation() to pre-validate the user, then PrimaryAuthenticationProvider::beginPrimaryAccountCreation() to create it and PrimaryAuthenticationProvider::finishAccountCreation() if you need to do something after the local user was added to the database. PrimaryAuthenticationProvider::postAccountCreation() can be used to update for creations by other providers.
  • AuthPlugin::addUser() for auto-creation → PrimaryAuthenticationProvider::testUserForCreation() to check the user name, PrimaryAuthenticationProvider::testForAccountCreation() to pre-validate the user, PrimaryAuthenticationProvider::autoCreatedAccount() to do anything after the user is created.
  • AuthPlugin::strict() and AuthPlugin::strictUserAuth() → Don't return ABSTAIN from PrimaryAuthenticationProvider::beginPrimaryAuthentication().
  • AuthPlugin::initUser() → the LocalUserCreated hook
  • AuthPlugin::getCanonicalName() has no replacement. You can use PrimaryAuthenticationProvider::testUserForCreation() to reject certain names, but if you have strong restrictions on the name you might want to implement a linking PrimaryAuthenticationProvider instead of trying to force MediaWiki into using only names compatible with your backend.
    • The reasoning here is that, with multiple providers, it's too easy to get into a situation where providers' requirements conflict. For a silly example, what if one provider wants all-uppercase names while another wants all-lowercase?
  • AuthPlugin::getUserInstance() has no replacement.
  • AuthPluginUser::getId() → implement a CentralIdLookup subclass.
  • AuthPluginUser::isLocked() → the UserIsLocked hook
  • AuthPluginUser::isHidden() → the UserIsHidden hook
  • AuthPluginUser::resetAuthToken()SessionProvider::invalidateSessionsForUser()

The ApiCreateAccount and ApiLogin classes

edit

If you're concerned with these, it probably means you were using hooks such as APIGetAllowedParams to inject parameters into these modules. Under AuthManager, ApiCreateAccount is no longer used and ApiLogin is only supposed to be used for BotPasswords login which isn't going to be hitting your extension when executed. The replacements, ApiAMCreateAccount and ApiClientLogin, use all the same AuthManager code as SpecialUserLogin and SpecialCreateAccount and so do not need any special treatment by your extension.

If you were subclassing them for some reason, don't. Trying to roll your own authentication or account creation is likely to just blow up on you.

The LoginForm, SpecialChangePassword, SpecialPasswordReset, SpecialUserlogout, and SpecialChangeEmail classes

edit

If you were subclassing or instantiating these to try to implement your own login, account creation, or the like, don't. Trying to roll your own authentication or account creation is likely to just blow up on you.

Chances are that whatever you were doing before to hack around LoginForm is now supported directly by AuthManager. If it's not, it would probably be best to discuss enhancing AuthManager than trying to hack around it.

User class password-setting methods

edit

These are User::setPassword(), User::setInternalPassword(), and User::setNewpassword(), and also User::isPasswordReminderThrottled() since that would only really be useful in conjunction with setNewpassword().

If you were passing null to User::setPassword() to try to disable login for a user, you'll probably want to look at User::newSystemUser() instead.

Otherwise, in general you should probably let the existing UI and API classes handle it instead of trying to roll your own, since the user may not even have a password anymore.

User class password-checking methods

edit

These are User::checkPassword() and User::checkTemporaryPassword().

If you're looking at this, it probably means you were formerly implementing some security-sensitive operation where you had the user enter their password to confirm it's really them rather than someone who found their account logged in on a public computer or a lost laptop. Under AuthManager, however, the user might not even have a password, and the confirming it's really them might want to also include things like the second factor in a two-factor authentication mechanism. So the options are:

  • Incorporate the whole multi-step authentication flow within everything that needs to do this.
  • Consider it good if they used Special:UserLogin during this session within the last X seconds.
  • Come up with a third option.

AuthManager currently takes the second option. The basic method to perform the check is to call AuthManager::securitySensitiveOperationStatus(). If it returns AuthManager::SEC_REAUTH you should redirect to Special:UserLogin with 'returnto' and 'returntoquery' set appropriately and 'force' set to your operation. If it returns AuthManager::SEC_FAIL (or really, anything other than AuthManager::SEC_OK), you should return an error indicating that the operation is not allowed.

In a special page, the easiest method is to override SpecialPage::getLoginSecurityLevel(), or you can call SpecialPage::checkLoginSecurityLevel() directly. If you subclass AuthManagerSpecialPage, this is already done for you. All these will handle redirecting or erroring out for you.

In an API module, the simplest method is to call ApiAuthManagerHelper::securitySensitiveOperation(), which will take care of returning the correct API error response if the status is not AuthManager::SEC_OK.

On the other hand, if you're looking at this because you were implementing your own authentication flow, don't. Trying to roll your own authentication or account creation is likely to just blow up on you.

User::addNewUserLogEntry() and User::addNewUserLogEntryAutoCreate()

edit

These methods do nothing under AuthManager, since AuthManager handles adding the necessary log entries itself.

If you were using them as part of your own account creation mechanism, you should rethink what you were doing since that's even less likely to work out well than it was pre-AuthManager. If you really need to be generally creating accounts, your best bet is probably to do them as auto-creations by calling AuthManager::autoCreateUser().

If you were just using this as an easy way to add some other log message, you'll have to start doing it the usual way. Sorry.

User::createNew() and User::addToDatabase()

edit

If you're simply creating a user to be attached to logged actions made by your extension, look at User::newSystemUser().

If you're using them as part of your own account creation mechanism, you should rethink what you were doing since that's even less likely to work out well than it was pre-AuthManager. If you really need to be generally creating real user accounts, your best bet is probably to do them as auto-creations by calling AuthManager::autoCreateUser().

Logging into the API using action=login

edit

Logging into an account via API action=login with the account's main password isn't going to work reliably anymore with AuthManager: if the remote wiki has captchas, or requires two-factor auth, or your password simply expires, logins will fail until you manually log in and fix things.

Depending on what exactly you're doing, there are several possible solutions:

  • If you're accessing the local wiki, stop and use PHP interfaces instead of screwing around trying to hit the API.
  • If you're only accessing other wikis that share a database cluster, as is the case with Wikimedia wikis, and you're just doing queries, again it would be best to use PHP interfaces to access the other wikis' databases directly to query what you need.
  • If you're accessing other wikis that share cross-wiki notifications to send notifications to users, use cross-wiki notifications from PHP.
  • If you're accessing other wikis where your extension is (or can be required to be) installed, your best bet would probably be to put together a SessionProvider that uses a shared secret to validate an HTTP Authorization header to make your API requests simply be logged in as your extension's user. This is not terribly different from how Extension:OAuth works, but simpler since it only needs to handle one statically-configured user with one statically-configured shared secret.
  • If you're accessing other wikis where Extension:OAuth, Extension:SSLClientAuthentication, or some similar extension is in use, use that to make your API requests simply be logged in as your extension's user.
  • And if even that is not possible, you'll probably have to fall back on creating the extension's user manually on the target wiki, setting up a bot password for your extension's user, and using that to log in with API action=login.