[go: nahoru, domu]

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

Issue/6600 connected email found #6754

Merged
merged 43 commits into from
Oct 24, 2017

Conversation

theck13
Copy link
Contributor
@theck13 theck13 commented Oct 19, 2017

Fix

Add Connected, Email Found Screen for Google login flow as described in #6600. These changes include prompting for the WordPress account password, handling two-factor authentication for Google login, and connecting a Google account to a WordPress account.

Note

Since the ReleaseStoreModule module was removed from FluxC after this branch was created and the FluxC hash 9fae2e8 required for this pull request includes that removal, the import and singleton statements from the AppComponent file must be removed to build and test these changes.

Test

Social Disconnected / Two-Factor Disabled

  1. Clear app data.
  2. Tap Log In button.
  3. Tap Google login view.
  4. Choose Google account.
  5. Notice Email / Password screen is shown.
  6. Enter WordPress.com account password.
  7. Tap Next button.
  8. Notice Epilogue screen is shown.

Social Connected / Two-Factor Disabled

  1. Clear app data.
  2. Tap Log In button.
  3. Tap Google login view.
  4. Choose Google account.
  5. Notice Epilogue screen is shown.

Social Disconnected / Two-Factor Enabled

  1. Clear app data.
  2. Tap Log In button.
  3. Tap Google login view.
  4. Choose Google account.
  5. Notice Email / Password screen is shown.
  6. Enter WordPress.com account password.
  7. Tap Next button.
  8. Notice Two-Factor Authentication screen is shown.
  9. Enter two-factor authentication code.
  10. Tap Next button.
  11. Notice Epilogue screen is shown.

Social Connected / Two-Factor Enabled

  1. Clear app data.
  2. Tap Log In button.
  3. Tap Google login view.
  4. Choose Google account.
  5. Notice Two-Factor Authentication screen is shown.
  6. Enter two-factor authentication code.
  7. Tap Next button.
  8. Notice Epilogue screen is shown.

theck13 and others added 30 commits October 6, 2017 11:04
Copy link
Contributor
@hypest hypest left a comment

Choose a reason for hiding this comment

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

Nice job here @theck13 , 2FA seems to work nicely!

I've left quite a few comments below... this PR proved to be worthy of the time spent and is rather big :)

I think there is a problem with scenario #1 and the account connection doesn't complete. I've left a comment about that.

In general, it seems that there is newly added complexity and most of my comments try to propose changes in order to mitigate the complexity a bit. Please have a look and keep that in mind as it will probably help to understand the "spirit" of the comments.

I will most probably make a second pass on the PR as I think I've left out some parts. Thanks!

PS. Oh, I should also mention that I left a comment about another issue as well, this one about the epilogue screen sites list in scenario #2 (#6754 (comment))

break;
}

// Finish login on social connect error.
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's expand this comment to make it clear that we deliberately ignore all other error cases as non actionable and not worthy of informing the user about. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I update the comment in 545094c.

}

// Finish login on social connect error.
if (isSocialLogin) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, can isSocialLogin be false while in onSocialChanged(). I'm under the impression that this callback is only called when we are in the Social login mode, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this was another copy/paste error from Login2FaFragment. I removed that conditional in b9bf704.

switch (event.error.type) {
case UNABLE_CONNECT:
AppLog.e(T.API, "Unable to connect WordPress.com account to social account.");
doFinishLogin();
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems there is a doFinishLogin() call right after the switch anyway (wrapped in a isSocialLogin conditional that I asked about below anyway) so, is it really needed here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nope, it's not needed. I missed that when I moved the doFinishLogin statement to the isSocialLogin conditional. I remove the redundant statement in 33bc5c3.

@@ -261,6 +263,19 @@ public void loginViaSiteAddress() {
}

@Override
public void loginViaSocialAccount(String email, String idToken, String service, boolean isPasswordRequired) {
LoginEmailPasswordFragment loginEmailPasswordFragment =
LoginEmailPasswordFragment.newInstance(email, null, null, null, isPasswordRequired);
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure, do we really mean to pass null as the idToken to the new instance here or should we use the provided idToken parameter instead? I think that Scenario No1 fails to complete the account connection because of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, this is why the Social Disconnected / Two-Factor Disabled scenario failed as you mentioned. It was a copy/paste error on my part. I fixed that in f2df3e8.

@Override
protected void onLoginFinished() {
AnalyticsUtils.trackAnalyticsSignIn(mAccountStore, mSiteStore, true);
mLoginListener.loggedInViaSocialAccount(SiteUtils.getCurrentSiteIds(mSiteStore, false));
Copy link
Contributor

Choose a reason for hiding this comment

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

The "current" site list will need to be grabbed before the login actually finishes and the updated sites list is fetched from the server. See here for example.

It's not super clear but this list acts as an ignore-list at the login epilogue screen, to make the epilogue only show the brand new sites that got enabled by this login (there might be already added .org sites before this login). With the current state of the PR, the epilogue screen shows an empty list in the scenario No2 (Social connected but no 2FA).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The mLoginListener.loggedInViaSocialAccount(SiteUtils.getCurrentSiteIds(mSiteStore, false)); statement was meant to mimic similar statements like mLoginListener.loggedInViaPassword(mOldSitesIDs);. I wasn't aware the ArrayList<Integer> oldSiteIds parameter needed to be determined before onLoginFinished. Would adding a mOldSitesIds variable which is assigned when the Google login process is started be sufficient?

Copy link
Contributor

Choose a reason for hiding this comment

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

At the start of the Google login process should do it, yeap. Note that in that case the list will need to be preserved to survive rotations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added the variable and state preservation in 836e1b3.

Copy link
Contributor

Choose a reason for hiding this comment

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

Verified it working now @theck13 👍

private String mType;
private String mUserId;
private boolean isSocialLogin2fa;
private boolean isSocialLoginConnect;
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor but, these isXXX variables need their Hungarian notation :). We haven't dropped that rule yet for Java wpandroid, right?

There are more places in this PR with non-hungarian form of isXXX variables and those need adjustment as well. Thanks!

Copy link
Contributor Author
@theck13 theck13 Oct 20, 2017

Choose a reason for hiding this comment

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

There are 100+ instances of boolean [m](\w)+; and 78 instances of boolean [^m](\w)+; variables in the WordPress codebase so it's not clear what the standard should be. Let's not worry about it for now and leave it as is. Those will be cleaned up when we implement code style automation in the near future.

Copy link
Contributor
@hypest hypest Oct 23, 2017

Choose a reason for hiding this comment

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

I think this PR is not the place to debate codestyle guideline changes but I see what you mean.

Let's keep it as is here (non-hungarian notation in the isXXX fields) but I'd highly suggest we stick to the current styleguide in subsequent PRs until it gets revised. Manually enforcing the styleguide in PR review (instead of say, using checkstyle) is already laborious so, let's not add debating on top of it 😸 . Thanks!

@@ -33,6 +35,8 @@

// Login email password callbacks
void needs2fa(String email, String password);
void needs2fa(String email, String password, String idToken, String service, boolean isSocialLogin);
void needs2fa(String email, String userId, String nonceAuthenticator, String nonceBackup, String nonceSms);
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems there are 3 modes of operation in regards to asking for 2FA (please, let me know if I understood this wrongly):

  1. The plain old non-social login mode
  2. The social login+connect mode
  3. The social login (already connected) mode

I think the needs2fa method name doesn't serve its purpose enough now that there are overloaded versions of it. Let's rename each of the overloads to better portray their functionality/usage.

While at it, I'd recommend accompany them with a dedicated (and renamed) newInstance helper method in Login2FaFragment. Currently in the PR, one of the newInstances accepts the isSocialLoginConnect param but I think that's confusing. Let's break that one out to 2 separate helper methods, 3 all in all, to better match the modes. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are multiple instances which two-factor authentication is required as you mentioned and each has a different set of parameters. That seems like a good case to use overloaded methods. I didn't rename the interface methods since one is strictly non-social, one is either social or not, and one is strictly social.

Since you can get to the two-factor login screen from the email/password screen two different ways (i.e. non-social login, social login with connect), the isSocialLoginConnect parameter is needed.

Copy link
Contributor
@hypest hypest Oct 23, 2017

Choose a reason for hiding this comment

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

LoginListener interface methods are deliberately descriptive in an attempt to address Login's complexity and correspond to the different flow or subflows so, let's follow suit here. Example: See loggedInViaSignup(list) vs loggedInViaPassword(list). Those two could be defined overloaded but are not and instead, their names correspond to the login flow/subflow.

I didn't rename the interface methods since one is strictly non-social, one is either social or not, and one is strictly social.

Since you can get to the two-factor login screen from the email/password screen two different ways (i.e. non-social login, social login with connect), the isSocialLoginConnect parameter is needed.

I might be missing something here so, in an attempt to understand the code better I prepared this commit on a branch with the method names "de-overloaded". Can you have a look and check if it's missing any functionality? Let's adopt something similar if it's functionally sound. Thanks!

In particular, is there a need for that second overloaded version of needs2fa to be both for social and non-social?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the second version of the needs2fa overloaded methods is needed for social and non-social instances currently. It was designed that way so the first version could remain unaltered and files irrelevant to social login could also remained unaltered (i.e. LoginUsernamePasswordFragment).

I compromised with 954b09f.

private static final String ARG_2FA_TYPE = "ARG_2FA_TYPE";
private static final String ARG_2FA_TYPE_AUTHENTICATOR = "authenticator";
private static final String ARG_2FA_TYPE_BACKUP = "backup";
private static final String ARG_2FA_TYPE_SMS = "sms";
Copy link
Contributor

Choose a reason for hiding this comment

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

ARG_2FA_TYPE_AUTHENTICATOR, ARG_2FA_TYPE_BACKUP and ARG_2FA_TYPE_SMS don't seem to be arguments passed to the fragment so, their naming is confusing. Instead, they seem to be values for the internal mType variable. Let's rename them to drop the ARG_ prefix and move them to a different section of the file. Alternatively, we can make them an Enum for additional clarity and utility.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I update the constant names in 0f6eddf.


public static Login2FaFragment newInstance(String emailAddress, String password) {
private String mService;
private String mType;
Copy link
Contributor

Choose a reason for hiding this comment

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

The mType variable seems like the API is bleeding some of its internal mechanics. I mean, the UI or the Fragment in general doesn't need the type other than to serve the API calls. It also seems that we can always deduce the type by examining the nonce (its length) so, I'd recommend we remove the instance variable and just use some nonce-to-type helper method.

That will also remove the need to persist the instance variable on rotation, which I think is currently needed.

By the way, can't the type be just hidden in FluxC without the UI needing to specify it in the call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The mType cannot be deduced from the nonce length. The mType is determined by the code length.

The mType doesn't need to be persisted on device rotation since it is set with setAuthCodeTypeAndNonce before passing it to PushSocialAuthPayload. I can remove it from onCreate and remove the ARG_2FA_TYPE constant.

I didn't want to put that level of logic in FluxC. The network layer should not care about app-level logic. It should just take parameters, make network calls, and return responses.

Copy link
Contributor
@hypest hypest Oct 23, 2017

Choose a reason for hiding this comment

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

In principal yes, the networking lib should only handle network stuff and not that much business logic, but FluxC is not just a networking library by any measure. Instead, it tries to hide away many WP/WPCOM details, exposing simple and consistent interfaces to wpandroid and others.

In this case, the 2FA type looks like noise that the mobile client shouldn't even know/care about at all. Unless it serves some purpose to have it exposed to the user, we should hide it inside FluxC (if possible). But, this is not something this PR needs to block on anyway. So, let's keep it as is but, I would still recommend opening a ticket in FluxC to hide the 2FA type from the caller. I think some collaboration with the backend developers will also be needed to fix this. Thanks!

The mType doesn't need to be persisted on device rotation since it is set with setAuthCodeTypeAndNonce before passing it to PushSocialAuthPayload. I can remove it from onCreate and remove the ARG_2FA_TYPE constant.

Nice, yeah, let's remove it from onCreate (and newInstance I guess) since it gets overwritten anyway before the PushSocialAuthPayload call.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the unnecessary code for two-factor authentication type in 40c1b33.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, I think we overdid it here. mType doesn't need to be initialised from fragment's argument but we do need to preserve it on rotation so its value is correct when the onSocialChanged response arrives. Rotation may have happened after the PushSocialAuthPayload call but before the response comes back.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added state preservation for mType in b71176d.

case INVALID_TWO_STEP_CODE:
endProgress();

switch (mType) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I hinted in an earlier comment about this so, I think we should just deduce the type from the event.error.nonce without relying on a global instance variable, unless of course there is more to it. Is there some special reason to rely on a global var here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As I mentioned in my reply, we can't deduce the type from the nonce.

@hypest
Copy link
Contributor
hypest commented Oct 23, 2017

Made another pass @theck13 , thanks for the changes!

Among other comments, I verified that Scenario #1 is now fixed 👍 . I'll test again all the scenarios as the issues get resolved (rotation survival, the epilogue screen update).

Ping me to have another pass, thanks!

@hypest
Copy link
Contributor
hypest commented Oct 24, 2017

Left only one new comment @theck13 , thanks!

@hypest
Copy link
Contributor
hypest commented Oct 24, 2017

LGTM!

@hypest hypest merged this pull request into feature/google-login Oct 24, 2017
@hypest hypest deleted the issue/6600-connected-email-found branch October 24, 2017 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants