Page MenuHomePhabricator

AuthManager should support to create a new account for a Link PrimaryAuthenticationProvider
Open, Needs TriagePublic

Description

Currently, the following workflows exist within AuthManager (where a link provider could be involved):

  • Special:UserLogin -> authenticate (with external source) -> logged in (the external source user is already linked with a local wiki user)
  • Special:CreateAccount -> authenticate (with external source) -> account created
  • Special:UserLogin -> authenticate (with external source) -> not logged in (the external source user is not linked with a local wiki user) -> the user has to authenticate with another method, e.g. with Password

However, with the last workflow, the user currently is a bit lost now. He wanted to authenticate with the external source (e.g. Google) and was successfully authenticated. The only problem is, that the user isn't connected with a local wiki user, so as a "normal" user I would expect to get the chance to create one now. But unfortunately, I would need to manually open Special:CreateAccount or go back to Special:UserLogin to click "Create an account". That doesn't look very intuitive. Maybe we could implement a new workflow like:

  • Special:UserLogin -> authenticate (with external source) -> not logged in (the external source user is not linked with a local wiki user) -> create a new local wiki account -> logged in

While implementing this, the following use case should be considered, too:
As a wiki administrator, I maybe want to disable "normal" account creations, but want to allow new account registrations with a link provider. E.g. I've a company with Google Apps for business. So my wiki should be visible (and editable) by users with a Google Mail account (with a specific domain), only. For this I want to allow user registrations with a valid Google login, but not a "manual" registration with Username/password.

Event Timeline

I note AuthManager already supports this, and the API exposes this. It's the web UI that needs support added.

As a wiki administrator, I maybe want to disable "normal" account creations, but want to allow new account registrations with a link provider. E.g. I've a company with Google Apps for business. So my wiki should be visible (and editable) by users with a Google Mail account (with a specific domain), only. For this I want to allow user registrations with a valid Google login, but not a "manual" registration with Username/password.

This is already supported: don't include other providers (such as LocalPasswordPrimaryAuthenticationProvider or TemporaryPasswordPrimaryAuthenticationProvider) in $wgAuthManagerConfig. If you have existing local accounts that should keep working with passwords, include LocalPasswordPrimaryAuthenticationProvider but set its "localOnly" option to prevent new creations.

I note AuthManager already supports this, and the API exposes this. It's the web UI that needs support added.

As a wiki administrator, I maybe want to disable "normal" account creations, but want to allow new account registrations with a link provider. E.g. I've a company with Google Apps for business. So my wiki should be visible (and editable) by users with a Google Mail account (with a specific domain), only. For this I want to allow user registrations with a valid Google login, but not a "manual" registration with Username/password.

This is already supported: don't include other providers (such as LocalPasswordPrimaryAuthenticationProvider or TemporaryPasswordPrimaryAuthenticationProvider) in $wgAuthManagerConfig. If you have existing local accounts that should keep working with passwords, include LocalPasswordPrimaryAuthenticationProvider but set its "localOnly" option to prevent new creations.

Can you clarify, maybe off list, how this is achieved? I'm trying to accomplish exactly what you describe but can't seem to get it to work. Thanks!

You achieve it by setting $wgAuthManagerConfig in your LocalSettings.php. Or you could try to manipulate $wgAuthManagerAutoConfig.

More specifically, you might set

$wgAuthManagerConfig = [
    'preauth' => [ /* list your providers here */ ],
    'primaryauth' => [
        GoogleLogin\Auth\GooglePrimaryAuthenticationProvider::class => [
            'class' => GoogleLogin\Auth\GooglePrimaryAuthenticationProvider::class,
            'sort' => 101,
        ],
        MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class => [
            'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
            'args' => [ [
                'authoritative' => true,
                'localOnly' => true,
            ] ],
            'sort' => 100,
        ],
    ],
    'secondaryauth' => [ /* list your providers here */ ],
];

Or you might make sure GoogleLogin is the only extension that adds a PrimaryAuthenticationProvider, then set

$wgAuthManagerAutoConfig['primaryauth'] = [
    MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class => [
        'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
        'args' => [ [
            'authoritative' => true,
            'localOnly' => true,
        ] ],
        'sort' => 100,
    ],
];

to replace the primary providers added by MediaWiki core.

You achieve it by setting $wgAuthManagerConfig in your LocalSettings.php. Or you could try to manipulate $wgAuthManagerAutoConfig.

More specifically, you might set

$wgAuthManagerConfig = [
    'preauth' => [ /* list your providers here */ ],
    'primaryauth' => [
        GoogleLogin\Auth\GooglePrimaryAuthenticationProvider::class => [
            'class' => GoogleLogin\Auth\GooglePrimaryAuthenticationProvider::class,
            'sort' => 101,
        ],
        MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class => [
            'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
            'args' => [ [
                'authoritative' => true,
                'localOnly' => true,
            ] ],
            'sort' => 100,
        ],
    ],
    'secondaryauth' => [ /* list your providers here */ ],
];

Or you might make sure GoogleLogin is the only extension that adds a PrimaryAuthenticationProvider, then set

$wgAuthManagerAutoConfig['primaryauth'] = [
    MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class => [
        'class' => MediaWiki\Auth\LocalPasswordPrimaryAuthenticationProvider::class,
        'args' => [ [
            'authoritative' => true,
            'localOnly' => true,
        ] ],
        'sort' => 100,
    ],
];

to replace the primary providers added by MediaWiki core.

What is an appropriate value for preauth and secondary auth?

I pasted the $wgAuthManagerConfig example at the end of my LocalSettings.php file and I'm still getting "The supplied credentials are not associated with any user on this wiki" when I attempt to login via Google. No users have been pre-created for Google login.

The latest from Florian seems to indicate that the current release of Google Login (0.4.0, which I'm using) no longer supports account creation via Google. Does your example take that into account and assume use of an earlier version that does support this feature, or does it modify AuthManager in such a way that it works around that limitation even in the latest version?

Florian - is it possible to use the extension to authenticate with other forms of Google Identity not tied to Google+?

I think it would be better to keep this task about the issue described by Florian (AuthManager supports creating a new local account for an existing remote account via Special:CreateAccount, but not via Special:UserLogin). For support requests, you can use Discourse, wikitech-l, IRC etc.

@Tgr: I remember you (or was it someone else) "somewhere" said, that there's "big" problem here:
a user, who authenticated with an external source and was redirected to a "create new account" page afterwards (as no local user was found in the database)

How can we now ensure, that a person (who might not be the current user anymore, e.g. on a shared computer or something) who finishes the creation process is really the person who originally started the authentication. Especially given, if the user authenticates, closes the browser tab (and feels save now), and if another user opens the browser, opens the wiki and clicks on login/create account and will see the "finish account creation" page.

Do I remember that correctly?

One idea I have just now (during writing this) is, that we could've a client-side token, which is only served to the user the first time after authenticating and going to the finish account creation page. Only, if this token is passed to the account creation request, the creation request is expected to be valid and the account is created. This can, however, have other security implications, and I'm not sure where we could save such a token, which also needs to be deleted at some point (especially, when the user does _not_ finish the account creation process). We could probably start a job in the background which looks for outdated tokens and deletes it, whenever a user logs in or something. But that already sound weird.

Yeah, that's one of the problems although I don't think it's a huge one. The security implications are spelled out in more detail at https://www.mediawiki.org/wiki/Manual:SessionManager_and_AuthManager/Linked_accounts#Security. As for tokens, we do reset login state when someone clicks the login link, but it's pretty hard to prevent the attacker from using the "open recently closed tab" browser feature, or back arrow navigation or something similar. (On some browsers that will even restore the Javascript/DOM state of the closed tab.) I guess for JS-enabled browsers we could clear state when navigating away/closing the tab but it seems hacky. But as said on the wiki page, that's considered a somewhat implausible attack.

IIRC account autocreation was disabled mainly because the UX is untested and probably not great (e.g. T136710). Maybe I am forgetting something, it was a while ago (and maybe @Anomie still remembers)
but I don't think there was any specific bug / missing feature blocking this, just someone testing it and doing a UX review. (You can reenable by uncommenting ConfirmLinkSecondaryAuthenticationProvider in $wgAuthManagerAutoConfig, or doing the equivalent in local config.)

@Tgr: I tested the UI a bit, however, if I'm not mistaken, there's one important feature missing: The creation of a new account? The message given to the user (that the authentication data was correct, but that there's no loal user account linked with it yet) already mentions, that the user should login or _create_ an account with which the external account should be linked, to. However, the UI just allows me to login, not to create a new account?

image.png (587×348 px, 26 KB)

Or do I miss something here? From what I see from the code (I, however, haven't looked that deeply), there's only a login authentication request passed to the restart authentication response, isn't it? So the create account is missing?

@Florian thanks, I am starting to remember now :)

So there multiple possible ways to do account creation on login:

  • the external identity provider can just decide to treat everyone with a valid remote identity as an existing user and autocreate an account for them. This requires the provider to be able to map remote identities to local usernames; you then just return that username in a PASS response and it will be autocreated if it does not exist. This would work nicely with email addresses as usernames (might involve changing $wgInvalidUsernameCharacters) or realnames as usernames (if that's a thing returned by the external identity provider). This is not really account linking as far as AuthManager is concerned, and should be well-supported. This is how most other web applications work I think, howewer those tend to rely on the displayed username being easy to change (so they can use the email as the fundamental user identifier, generate a username from the email prefix or the realname or whatever, use a very streamline registration process where registration is not any different from login, and leave it to the user to change their nick later if they want). MediaWiki uses the username as a primary identifier which makes this awkward. Also this will use account autocreation and not account creation and so bypass anything that is set up in the registration workflow (such as account creation throttling, or a form to get extra data) - that might or might not be a problem, depending on what the site owner wants.
  • The way AuthManager supports account linking is that a successful login that does not result in a local username is stored for linking, so when you proceed to login / create account in some other valid way, the original primary auth provider extension will be called back and told that these accounts can be linked. CreateFromLoginAuthenticationRequest is used in this case to proxy that information (the maybeLink field with a list of remote identities to maybe link). I think this was not finished, that the state gets reset when switching from login to account creation (by clicking on the "Join <project>" button); I might be misremembering though. In any case this was meant for a "login using google, if that fails use the normal username + password registration field" workflow, so if you don't have any other provider than GoogleLogin this does not make too much sense.
  • The middle road would be for the external identity provider extension to signal that the user is identified but needs a username and needs to pass through normal account creation; this is not properly implemented. The intended mechanism is for the extension to return a PASS result with no username and the createRequest property set, but
    • this will just drop you back to the beginning of the login/registration process with a warning, instead of automatically starting the registration. (Or would that be a bad idea? not sure, it might be disorienting for the user to switch from login to registration without warning.)
    • As above, I don't think there is a mechanism to preserve state (the value of createRequest needs to be passed in when restarting account creation).
    • The logic specified in AuthManager::beginAccountCreation about CreateFromLoginAuthenticationRequest should be implemented in Special:CreateAccount
    • It is left to the external identity provider to actually handle such requests (detect that there is a CreateFromLoginAuthenticationRequest with the createRequest property set to something handled by that provider, display a form for the username etc). That should probably be done in core.

All I remember at the moment is that all the core parts have unit tests at the AuthManager level that should cover all those workflows. And since the API modules are a pretty thin interface to the AuthManager interface, it should work that way too.

I'm currently trying to implement account creation / linking on the login page and am running into this issue. (https://github.com/mohe2015/AuthManagerOAuth/issues/20 for more details). My current implementation is prone to a race condition if I understand correctly and I don't know a way to fix this. Any help would be really appreciated.

In particular the third bullet point "The middle road would be for the external identity provider extension to signal that the user is identified but needs a username and needs to pass through normal account creation" would probably the best way for me.

I'm not confident to implement this myself and would love to see some progress here.

@Dev.mohe if you don't want to automatically link the account to an existing local user with the same name, you can return a PASS with no username and its linkRequest property set to some AuthenticationRequest. The user will be prompted to log in; if they successfully log in with their local username and password, the link request will be passed to beginAccountLink() and you can connect the local account to the OAuth provider. This is not the same workflow that you mention, but does avoid race conditions.

(FWIW, WSOAuth is fairly similar to the extension you are working on.)