From c56ead9cf603e6f3ec29f0eafe0d14919bfd748e Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Mon, 9 Aug 2021 22:21:04 -0500 Subject: [PATCH 01/13] Add compromised password failure type Added COMPROMISED_PASSWORD as failure type and handle it in the factory method for AuthException. --- .../src/main/java/com/simperium/client/AuthException.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Simperium/src/main/java/com/simperium/client/AuthException.java b/Simperium/src/main/java/com/simperium/client/AuthException.java index 49fba542..9b3ac321 100644 --- a/Simperium/src/main/java/com/simperium/client/AuthException.java +++ b/Simperium/src/main/java/com/simperium/client/AuthException.java @@ -6,6 +6,7 @@ public class AuthException extends SimperiumException { static public final String GENERIC_FAILURE_MESSAGE = "Invalid username or password"; static public final String EXISTING_USER_FAILURE_MESSAGE = "Account already exists"; + static public final String COMPROMISED_PASSWORD_MESSAGE = "Password has been compromised"; static public final int ERROR_STATUS_CODE = -1; static public final int INVALID_ACCOUNT_CODE = 0x0; @@ -14,7 +15,7 @@ public class AuthException extends SimperiumException { public final FailureType failureType; public enum FailureType { - INVALID_ACCOUNT, EXISTING_ACCOUNT + INVALID_ACCOUNT, EXISTING_ACCOUNT, COMPROMISED_PASSWORD } public AuthException(FailureType code, String message){ @@ -39,6 +40,8 @@ public static AuthException exceptionForStatusCode(int statusCode, Throwable cau switch (statusCode) { case 409: return new AuthException(FailureType.EXISTING_ACCOUNT, EXISTING_USER_FAILURE_MESSAGE, cause); + case 401: + return new AuthException(FailureType.COMPROMISED_PASSWORD, COMPROMISED_PASSWORD_MESSAGE, cause); default: return new AuthException(FailureType.INVALID_ACCOUNT, GENERIC_FAILURE_MESSAGE, cause); } From c8d8ccdb9138cae9cdcdc153b9ee716d9b9d868b Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Mon, 9 Aug 2021 22:21:32 -0500 Subject: [PATCH 02/13] Delete unused constants in AuthException --- Simperium/src/main/java/com/simperium/client/AuthException.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Simperium/src/main/java/com/simperium/client/AuthException.java b/Simperium/src/main/java/com/simperium/client/AuthException.java index 9b3ac321..b730a0e5 100644 --- a/Simperium/src/main/java/com/simperium/client/AuthException.java +++ b/Simperium/src/main/java/com/simperium/client/AuthException.java @@ -9,8 +9,6 @@ public class AuthException extends SimperiumException { static public final String COMPROMISED_PASSWORD_MESSAGE = "Password has been compromised"; static public final int ERROR_STATUS_CODE = -1; - static public final int INVALID_ACCOUNT_CODE = 0x0; - static public final int EXISTING_ACCOUNT_CODE = 0x1; public final FailureType failureType; From d3796964cde2e4124bf5de8454bfea41ee594409 Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Mon, 9 Aug 2021 22:26:41 -0500 Subject: [PATCH 03/13] Change visibility from private to protected in CredentialsActivity Change the visibility of methods and fields in CredentialsActivity so subclasses can use them if they want to extend the behavior of authorization with Simperium. --- .../android/CredentialsActivity.java | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java b/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java index 13d4640c..e70b6cd6 100644 --- a/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java +++ b/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java @@ -47,24 +47,24 @@ import static org.apache.http.protocol.HTTP.UTF_8; public class CredentialsActivity extends AppCompatActivity { - private static final Pattern PATTERN_NEWLINES_RETURNS_TABS = Pattern.compile("[\n\r\t]"); - private static final String EXTRA_AUTOMATE_LOGIN = "EXTRA_AUTOMATE_LOGIN"; - private static final String EXTRA_PASSWORD = "EXTRA_PASSWORD"; - private static final String STATE_EMAIL = "STATE_EMAIL"; - private static final String STATE_PASSWORD = "STATE_PASSWORD"; - private static final int DELAY_AUTOMATE_LOGIN = 600; - private static final int PASSWORD_LENGTH_LOGIN = 4; - private static final int PASSWORD_LENGTH_MINIMUM = 8; + protected static final Pattern PATTERN_NEWLINES_RETURNS_TABS = Pattern.compile("[\n\r\t]"); + protected static final String EXTRA_AUTOMATE_LOGIN = "EXTRA_AUTOMATE_LOGIN"; + protected static final String EXTRA_PASSWORD = "EXTRA_PASSWORD"; + protected static final String STATE_EMAIL = "STATE_EMAIL"; + protected static final String STATE_PASSWORD = "STATE_PASSWORD"; + protected static final int DELAY_AUTOMATE_LOGIN = 600; + protected static final int PASSWORD_LENGTH_LOGIN = 4; + protected static final int PASSWORD_LENGTH_MINIMUM = 8; protected ProgressDialogFragment mProgressDialogFragment; - private AppCompatButton mButton; - private Simperium mSimperium; - private TextInputLayout mInputEmail; - private TextInputLayout mInputPassword; - private boolean mIsLogin; + protected AppCompatButton mButton; + protected Simperium mSimperium; + protected TextInputLayout mInputEmail; + protected TextInputLayout mInputPassword; + protected boolean mIsLogin; - private AuthResponseListener mAuthListener = new AuthResponseListener() { + protected AuthResponseListener mAuthListener = new AuthResponseListener() { @Override public void onFailure(final User user, final AuthException error) { runOnUiThread( @@ -323,13 +323,13 @@ protected void onSaveInstanceState(@NonNull Bundle outState) { outState.putString(STATE_PASSWORD, getEditTextString(mInputPassword)); } - private void clearPassword() { + protected void clearPassword() { if (mInputPassword.getEditText() != null) { mInputPassword.getEditText().getText().clear(); } } - private void copyToClipboard(String url) { + protected void copyToClipboard(String url) { Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); try { @@ -347,23 +347,23 @@ private void copyToClipboard(String url) { } } - private String getEditTextString(@NonNull TextInputLayout inputLayout) { + protected String getEditTextString(@NonNull TextInputLayout inputLayout) { return inputLayout.getEditText() != null ? inputLayout.getEditText().getText().toString() : ""; } - private void hideDialogProgress() { + protected void hideDialogProgress() { if (mProgressDialogFragment != null && !mProgressDialogFragment.isHidden()) { mProgressDialogFragment.dismiss(); mProgressDialogFragment = null; } } - private boolean isBrowserInstalled() { + protected boolean isBrowserInstalled() { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.simperium_url))); return (intent.resolveActivity(getPackageManager()) != null); } - private boolean isValidEmail(String text) { + protected boolean isValidEmail(String text) { return Patterns.EMAIL_ADDRESS.matcher(text).matches(); } @@ -371,11 +371,11 @@ private boolean isValidEmail(String text) { // - Meets minimum length requirement based on login (PASSWORD_LENGTH_LOGIN) and signup (PASSWORD_LENGTH_MINIMUM) // - Does not have new lines, returns, or tabs (PATTERN_NEWLINES_RETURNS_TABS) // - Does not match email address - private boolean isValidPassword(String email, String password) { + protected boolean isValidPassword(String email, String password) { return isValidPasswordLength(mIsLogin) && !PATTERN_NEWLINES_RETURNS_TABS.matcher(password).find() && !email.contentEquals(password); } - private boolean isValidPasswordLength(boolean isLogin) { + protected boolean isValidPasswordLength(boolean isLogin) { return mInputPassword.getEditText() != null && (isLogin ? getEditTextString(mInputPassword).length() >= PASSWORD_LENGTH_LOGIN : @@ -385,11 +385,11 @@ private boolean isValidPasswordLength(boolean isLogin) { // Use old password requirements for login validation: // - Meets minimum length requirement (PASSWORD_LENGTH_LOGIN) - private boolean isValidPasswordLogin() { + protected boolean isValidPasswordLogin() { return isValidPasswordLength(mIsLogin); } - private void setButtonState() { + protected void setButtonState() { mButton.setEnabled( mInputEmail.getEditText() != null && mInputPassword.getEditText() != null && @@ -398,13 +398,13 @@ private void setButtonState() { ); } - private void setEditTextString(@NonNull TextInputLayout inputLayout, String text) { + protected void setEditTextString(@NonNull TextInputLayout inputLayout, String text) { if (inputLayout.getEditText() != null ) { inputLayout.getEditText().setText(text); } } - private void showDialogError(String message) { + protected void showDialogError(String message) { hideDialogProgress(); Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); new AlertDialog.Builder(context) @@ -414,7 +414,7 @@ private void showDialogError(String message) { .show(); } - private void showDialogErrorExistingAccount() { + protected void showDialogErrorExistingAccount() { hideDialogProgress(); Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); new AlertDialog.Builder(context) @@ -438,7 +438,7 @@ public void onClick(DialogInterface dialog, int which) { .show(); } - private void showDialogErrorLoginReset() { + protected void showDialogErrorLoginReset() { hideDialogProgress(); final Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); new AlertDialog.Builder(context) @@ -467,7 +467,7 @@ public void onClick(DialogInterface dialog, int which) { .show(); } - private void showDialogErrorBrowser(final String url) { + protected void showDialogErrorBrowser(final String url) { final Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); new AlertDialog.Builder(context) .setTitle(R.string.simperium_dialog_title_error_browser) @@ -484,7 +484,7 @@ public void onClick(DialogInterface dialog, int which) { .show(); } - private void startLogin() { + protected void startLogin() { final String email = getEditTextString(mInputEmail); final String password = getEditTextString(mInputPassword); @@ -498,7 +498,7 @@ private void startLogin() { } } - private void startSignup() { + protected void startSignup() { final String email = getEditTextString(mInputEmail); final String password = getEditTextString(mInputPassword); From ebf1074b3d11210aafb48da67ba36027e4282d0d Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Tue, 10 Aug 2021 14:28:12 -0500 Subject: [PATCH 04/13] Bump up targetSdkVersion to match 30 which google play is asking for Even though this is a library, it is important to keep the dependencies and target SDK updated with the app. According to Google, they will stop supporting apps which target SDK version 29 or lower. --- Simperium/build.gradle | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Simperium/build.gradle b/Simperium/build.gradle index 2dca98b8..8f1cc980 100644 --- a/Simperium/build.gradle +++ b/Simperium/build.gradle @@ -24,12 +24,11 @@ repositories { } android { - buildToolsVersion "28.0.3" - compileSdkVersion 28 + compileSdkVersion 30 defaultConfig { minSdkVersion 23 - targetSdkVersion 28 + targetSdkVersion 30 versionName project.version // Calculating PIN for certificate: OU=Domain Control Validated, CN=*.simperium.com From e2343d1b5bec277806e52877d3dbd4212090b7b8 Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Tue, 10 Aug 2021 14:28:32 -0500 Subject: [PATCH 05/13] Bump library version to 0.11.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d829219b..910f4994 100644 --- a/build.gradle +++ b/build.gradle @@ -35,5 +35,5 @@ def gitDescribe() { } def static gitVersion() { - '0.10.0' + '0.11.0' } From 4eb7556d99390bfd05dd946abdfd6ad14c6aa1a8 Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Tue, 10 Aug 2021 14:54:48 -0500 Subject: [PATCH 06/13] Change sendRequest to handle non-json results in case of error The previous callback to handle the response of requests was using the JSON parser by default. The problem was that in responses where the code wasn't 200, the response was not a JSON object. Thus, we changed the callback to be a StringCallback and parse the JSON object in case the response is 200. Also, a Throwable is sent in case of error with the body as message of the Throwable. --- .../simperium/android/AsyncAuthClient.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Simperium/src/main/java/com/simperium/android/AsyncAuthClient.java b/Simperium/src/main/java/com/simperium/android/AsyncAuthClient.java index b5bd22a2..fa628c06 100644 --- a/Simperium/src/main/java/com/simperium/android/AsyncAuthClient.java +++ b/Simperium/src/main/java/com/simperium/android/AsyncAuthClient.java @@ -6,11 +6,9 @@ import android.util.Log; import com.koushikdutta.async.http.AsyncHttpClient; -import com.koushikdutta.async.http.AsyncHttpClient.JSONObjectCallback; import com.koushikdutta.async.http.AsyncHttpPost; import com.koushikdutta.async.http.AsyncHttpResponse; import com.koushikdutta.async.http.body.JSONObjectBody; -import com.koushikdutta.async.parser.JSONObjectParser; import com.simperium.BuildConfig; import com.simperium.client.AuthException; import com.simperium.client.AuthProvider; @@ -109,14 +107,14 @@ public AsyncHttpPost buildRequest(String path, JSONObject body) { private void sendRequest(String path, JSONObject body, final AuthResponseHandler handler) { - mClient.execute(buildRequest(path, body), new JSONObjectParser(), new JSONObjectCallback() { - @Override - public void onCompleted(Exception e, AsyncHttpResponse source, JSONObject result) { + mClient.executeString(buildRequest(path, body), new AsyncHttpClient.StringCallback() { + @Override + public void onCompleted(Exception e, AsyncHttpResponse asyncHttpResponse, String s) { int responseCode = AuthException.ERROR_STATUS_CODE; - if (source != null) { - responseCode = source.code(); + if (asyncHttpResponse != null) { + responseCode = asyncHttpResponse.code(); } if (e != null) { @@ -126,12 +124,16 @@ public void onCompleted(Exception e, AsyncHttpResponse source, JSONObject result } if (responseCode == 200) { - handler.onResponse(result); - return; + try { + JSONObject object = new JSONObject(s); + handler.onResponse(object); + return; + } catch (JSONException jsonException) { + handler.onError(AuthException.defaultException()); + } } - handler.onError(AuthException.exceptionForStatusCode(responseCode)); - + handler.onError(AuthException.exceptionForStatusCode(responseCode, new Throwable(s))); } }); } From 67d14493f1b3f595e57f486858acd012f4669867 Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Tue, 10 Aug 2021 14:56:57 -0500 Subject: [PATCH 07/13] Handle two kind of results for 401 responses during authentication For authentication, we can get responses with 401 for invalid credentials or compromised password. We recognize such responses by the value of theri bodies which is obtained through the Throwable's message. --- .../main/java/com/simperium/client/AuthException.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Simperium/src/main/java/com/simperium/client/AuthException.java b/Simperium/src/main/java/com/simperium/client/AuthException.java index b730a0e5..7c7c13d3 100644 --- a/Simperium/src/main/java/com/simperium/client/AuthException.java +++ b/Simperium/src/main/java/com/simperium/client/AuthException.java @@ -2,11 +2,14 @@ import com.simperium.SimperiumException; +import java.util.Objects; + public class AuthException extends SimperiumException { static public final String GENERIC_FAILURE_MESSAGE = "Invalid username or password"; static public final String EXISTING_USER_FAILURE_MESSAGE = "Account already exists"; static public final String COMPROMISED_PASSWORD_MESSAGE = "Password has been compromised"; + static public final String INVALID_LOGIN_BODY = "invalid login"; static public final int ERROR_STATUS_CODE = -1; @@ -39,7 +42,13 @@ public static AuthException exceptionForStatusCode(int statusCode, Throwable cau case 409: return new AuthException(FailureType.EXISTING_ACCOUNT, EXISTING_USER_FAILURE_MESSAGE, cause); case 401: - return new AuthException(FailureType.COMPROMISED_PASSWORD, COMPROMISED_PASSWORD_MESSAGE, cause); + // Code 401 can be obtain because credentials are wrong or the user's password has been compromised + // To differentiate both responses, we check the response's body + if (cause != null && Objects.equals(cause.getMessage(), INVALID_LOGIN_BODY)) { + return new AuthException(FailureType.INVALID_ACCOUNT, GENERIC_FAILURE_MESSAGE, cause); + } else { + return new AuthException(FailureType.COMPROMISED_PASSWORD, COMPROMISED_PASSWORD_MESSAGE, cause); + } default: return new AuthException(FailureType.INVALID_ACCOUNT, GENERIC_FAILURE_MESSAGE, cause); } From 71c5f56e5222f825e54afdb96162014300e5fd3d Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Tue, 10 Aug 2021 15:00:04 -0500 Subject: [PATCH 08/13] Simplify logic in exceptionForStatusCode --- .../src/main/java/com/simperium/client/AuthException.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Simperium/src/main/java/com/simperium/client/AuthException.java b/Simperium/src/main/java/com/simperium/client/AuthException.java index 7c7c13d3..e870f76a 100644 --- a/Simperium/src/main/java/com/simperium/client/AuthException.java +++ b/Simperium/src/main/java/com/simperium/client/AuthException.java @@ -44,9 +44,7 @@ public static AuthException exceptionForStatusCode(int statusCode, Throwable cau case 401: // Code 401 can be obtain because credentials are wrong or the user's password has been compromised // To differentiate both responses, we check the response's body - if (cause != null && Objects.equals(cause.getMessage(), INVALID_LOGIN_BODY)) { - return new AuthException(FailureType.INVALID_ACCOUNT, GENERIC_FAILURE_MESSAGE, cause); - } else { + if (cause != null && !Objects.equals(cause.getMessage(), INVALID_LOGIN_BODY)) { return new AuthException(FailureType.COMPROMISED_PASSWORD, COMPROMISED_PASSWORD_MESSAGE, cause); } default: From 322e9868927cbe1e46033d076c868f54d70f0fbc Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Tue, 10 Aug 2021 15:56:09 -0500 Subject: [PATCH 09/13] Add dialog to show in case the login response is a compromised password In case the login response is a failure type COMPROMISED_PASSWORD, a dialog is shown so that the users can change their password. --- .../android/CredentialsActivity.java | 32 +++++++++++++++++++ Simperium/src/main/res/values/strings.xml | 5 ++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java b/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java index e70b6cd6..e0ce9c8d 100644 --- a/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java +++ b/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java @@ -75,6 +75,9 @@ public void run() { case EXISTING_ACCOUNT: showDialogErrorExistingAccount(); break; + case COMPROMISED_PASSWORD: + showCompromisedPasswordDialog(); + break; case INVALID_ACCOUNT: default: showDialogError(getString( @@ -484,6 +487,35 @@ public void onClick(DialogInterface dialog, int which) { .show(); } + protected void showCompromisedPasswordDialog() { + hideDialogProgress(); + final Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); + new AlertDialog.Builder(context) + .setTitle(R.string.simperium_compromised_password) + .setMessage(R.string.simperium_compromised_password_message) + .setNegativeButton(R.string.simperium_not_now, null) + .setPositiveButton(R.string.simperium_change_password, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + try { + String url = getString(com.simperium.R.string.simperium_dialog_button_reset_url, URLEncoder.encode(getEditTextString(mInputEmail), UTF_8)); + + if (isBrowserInstalled()) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); + clearPassword(); + } else { + showDialogErrorBrowser(url); + } + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Unable to parse URL", e); + } + } + } + ) + .show(); + } + protected void startLogin() { final String email = getEditTextString(mInputEmail); final String password = getEditTextString(mInputPassword); diff --git a/Simperium/src/main/res/values/strings.xml b/Simperium/src/main/res/values/strings.xml index 7336f6f7..42e21fb9 100644 --- a/Simperium/src/main/res/values/strings.xml +++ b/Simperium/src/main/res/values/strings.xml @@ -34,7 +34,10 @@ Email Password App data, everywhere it\'s needed. + Compromised Password + This password has appeared in a data breach, which puts your account at high risk of compromise. It is recommended that you change your password immediately. + Not Now + Change Password @string/app_name https://simperium.com/ - From 89d4699852b13b2de9884cf25140d28e4dca68ad Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Tue, 10 Aug 2021 16:01:09 -0500 Subject: [PATCH 10/13] Set up fields and methods to private again --- .../android/CredentialsActivity.java | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java b/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java index e0ce9c8d..05008683 100644 --- a/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java +++ b/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java @@ -47,24 +47,24 @@ import static org.apache.http.protocol.HTTP.UTF_8; public class CredentialsActivity extends AppCompatActivity { - protected static final Pattern PATTERN_NEWLINES_RETURNS_TABS = Pattern.compile("[\n\r\t]"); - protected static final String EXTRA_AUTOMATE_LOGIN = "EXTRA_AUTOMATE_LOGIN"; - protected static final String EXTRA_PASSWORD = "EXTRA_PASSWORD"; - protected static final String STATE_EMAIL = "STATE_EMAIL"; - protected static final String STATE_PASSWORD = "STATE_PASSWORD"; - protected static final int DELAY_AUTOMATE_LOGIN = 600; - protected static final int PASSWORD_LENGTH_LOGIN = 4; - protected static final int PASSWORD_LENGTH_MINIMUM = 8; + private static final Pattern PATTERN_NEWLINES_RETURNS_TABS = Pattern.compile("[\n\r\t]"); + private static final String EXTRA_AUTOMATE_LOGIN = "EXTRA_AUTOMATE_LOGIN"; + private static final String EXTRA_PASSWORD = "EXTRA_PASSWORD"; + private static final String STATE_EMAIL = "STATE_EMAIL"; + private static final String STATE_PASSWORD = "STATE_PASSWORD"; + private static final int DELAY_AUTOMATE_LOGIN = 600; + private static final int PASSWORD_LENGTH_LOGIN = 4; + private static final int PASSWORD_LENGTH_MINIMUM = 8; protected ProgressDialogFragment mProgressDialogFragment; - protected AppCompatButton mButton; - protected Simperium mSimperium; - protected TextInputLayout mInputEmail; - protected TextInputLayout mInputPassword; - protected boolean mIsLogin; + private AppCompatButton mButton; + private Simperium mSimperium; + private TextInputLayout mInputEmail; + private TextInputLayout mInputPassword; + private boolean mIsLogin; - protected AuthResponseListener mAuthListener = new AuthResponseListener() { + private AuthResponseListener mAuthListener = new AuthResponseListener() { @Override public void onFailure(final User user, final AuthException error) { runOnUiThread( @@ -326,13 +326,13 @@ protected void onSaveInstanceState(@NonNull Bundle outState) { outState.putString(STATE_PASSWORD, getEditTextString(mInputPassword)); } - protected void clearPassword() { + private void clearPassword() { if (mInputPassword.getEditText() != null) { mInputPassword.getEditText().getText().clear(); } } - protected void copyToClipboard(String url) { + private void copyToClipboard(String url) { Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); try { @@ -350,23 +350,23 @@ protected void copyToClipboard(String url) { } } - protected String getEditTextString(@NonNull TextInputLayout inputLayout) { + private String getEditTextString(@NonNull TextInputLayout inputLayout) { return inputLayout.getEditText() != null ? inputLayout.getEditText().getText().toString() : ""; } - protected void hideDialogProgress() { + private void hideDialogProgress() { if (mProgressDialogFragment != null && !mProgressDialogFragment.isHidden()) { mProgressDialogFragment.dismiss(); mProgressDialogFragment = null; } } - protected boolean isBrowserInstalled() { + private boolean isBrowserInstalled() { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.simperium_url))); return (intent.resolveActivity(getPackageManager()) != null); } - protected boolean isValidEmail(String text) { + private boolean isValidEmail(String text) { return Patterns.EMAIL_ADDRESS.matcher(text).matches(); } @@ -374,11 +374,11 @@ protected boolean isValidEmail(String text) { // - Meets minimum length requirement based on login (PASSWORD_LENGTH_LOGIN) and signup (PASSWORD_LENGTH_MINIMUM) // - Does not have new lines, returns, or tabs (PATTERN_NEWLINES_RETURNS_TABS) // - Does not match email address - protected boolean isValidPassword(String email, String password) { + private boolean isValidPassword(String email, String password) { return isValidPasswordLength(mIsLogin) && !PATTERN_NEWLINES_RETURNS_TABS.matcher(password).find() && !email.contentEquals(password); } - protected boolean isValidPasswordLength(boolean isLogin) { + private boolean isValidPasswordLength(boolean isLogin) { return mInputPassword.getEditText() != null && (isLogin ? getEditTextString(mInputPassword).length() >= PASSWORD_LENGTH_LOGIN : @@ -388,11 +388,11 @@ protected boolean isValidPasswordLength(boolean isLogin) { // Use old password requirements for login validation: // - Meets minimum length requirement (PASSWORD_LENGTH_LOGIN) - protected boolean isValidPasswordLogin() { + private boolean isValidPasswordLogin() { return isValidPasswordLength(mIsLogin); } - protected void setButtonState() { + private void setButtonState() { mButton.setEnabled( mInputEmail.getEditText() != null && mInputPassword.getEditText() != null && @@ -401,13 +401,13 @@ protected void setButtonState() { ); } - protected void setEditTextString(@NonNull TextInputLayout inputLayout, String text) { + private void setEditTextString(@NonNull TextInputLayout inputLayout, String text) { if (inputLayout.getEditText() != null ) { inputLayout.getEditText().setText(text); } } - protected void showDialogError(String message) { + private void showDialogError(String message) { hideDialogProgress(); Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); new AlertDialog.Builder(context) @@ -417,7 +417,7 @@ protected void showDialogError(String message) { .show(); } - protected void showDialogErrorExistingAccount() { + private void showDialogErrorExistingAccount() { hideDialogProgress(); Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); new AlertDialog.Builder(context) @@ -441,7 +441,7 @@ public void onClick(DialogInterface dialog, int which) { .show(); } - protected void showDialogErrorLoginReset() { + private void showDialogErrorLoginReset() { hideDialogProgress(); final Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); new AlertDialog.Builder(context) @@ -470,7 +470,7 @@ public void onClick(DialogInterface dialog, int which) { .show(); } - protected void showDialogErrorBrowser(final String url) { + private void showDialogErrorBrowser(final String url) { final Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); new AlertDialog.Builder(context) .setTitle(R.string.simperium_dialog_title_error_browser) @@ -487,7 +487,7 @@ public void onClick(DialogInterface dialog, int which) { .show(); } - protected void showCompromisedPasswordDialog() { + private void showCompromisedPasswordDialog() { hideDialogProgress(); final Context context = new ContextThemeWrapper(CredentialsActivity.this, getTheme()); new AlertDialog.Builder(context) @@ -516,7 +516,7 @@ public void onClick(DialogInterface dialog, int which) { .show(); } - protected void startLogin() { + private void startLogin() { final String email = getEditTextString(mInputEmail); final String password = getEditTextString(mInputPassword); @@ -530,7 +530,7 @@ protected void startLogin() { } } - protected void startSignup() { + private void startSignup() { final String email = getEditTextString(mInputEmail); final String password = getEditTextString(mInputPassword); From bc04236acd5391b47a8ca0f3e495abd5a4c3ab5f Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Tue, 10 Aug 2021 16:14:02 -0500 Subject: [PATCH 11/13] Increase minor version instead of major version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 910f4994..2f645678 100644 --- a/build.gradle +++ b/build.gradle @@ -35,5 +35,5 @@ def gitDescribe() { } def static gitVersion() { - '0.11.0' + '0.10.1' } From 186b0361c239c8265826f4c1e0e9143aef3efef8 Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Wed, 11 Aug 2021 10:36:53 -0500 Subject: [PATCH 12/13] Use compromised password body so default case is invalid account Instead of checking for something different to "invalid account", check directly for "compromised password" body string so if we get something different it goes to the default case. --- .../src/main/java/com/simperium/client/AuthException.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Simperium/src/main/java/com/simperium/client/AuthException.java b/Simperium/src/main/java/com/simperium/client/AuthException.java index e870f76a..f8c76ffe 100644 --- a/Simperium/src/main/java/com/simperium/client/AuthException.java +++ b/Simperium/src/main/java/com/simperium/client/AuthException.java @@ -9,7 +9,7 @@ public class AuthException extends SimperiumException { static public final String GENERIC_FAILURE_MESSAGE = "Invalid username or password"; static public final String EXISTING_USER_FAILURE_MESSAGE = "Account already exists"; static public final String COMPROMISED_PASSWORD_MESSAGE = "Password has been compromised"; - static public final String INVALID_LOGIN_BODY = "invalid login"; + static public final String COMPROMISED_PASSWORD_BODY = "compromised password"; static public final int ERROR_STATUS_CODE = -1; @@ -44,7 +44,7 @@ public static AuthException exceptionForStatusCode(int statusCode, Throwable cau case 401: // Code 401 can be obtain because credentials are wrong or the user's password has been compromised // To differentiate both responses, we check the response's body - if (cause != null && !Objects.equals(cause.getMessage(), INVALID_LOGIN_BODY)) { + if (cause != null && Objects.equals(cause.getMessage(), COMPROMISED_PASSWORD_BODY)) { return new AuthException(FailureType.COMPROMISED_PASSWORD, COMPROMISED_PASSWORD_MESSAGE, cause); } default: From db7366a3067085aeab7d121ed87ee420622c7ba3 Mon Sep 17 00:00:00 2001 From: danilodominguezperez Date: Wed, 11 Aug 2021 10:40:47 -0500 Subject: [PATCH 13/13] Change to lower case the message from the response --- .../src/main/java/com/simperium/client/AuthException.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Simperium/src/main/java/com/simperium/client/AuthException.java b/Simperium/src/main/java/com/simperium/client/AuthException.java index f8c76ffe..0a0835e3 100644 --- a/Simperium/src/main/java/com/simperium/client/AuthException.java +++ b/Simperium/src/main/java/com/simperium/client/AuthException.java @@ -44,7 +44,8 @@ public static AuthException exceptionForStatusCode(int statusCode, Throwable cau case 401: // Code 401 can be obtain because credentials are wrong or the user's password has been compromised // To differentiate both responses, we check the response's body - if (cause != null && Objects.equals(cause.getMessage(), COMPROMISED_PASSWORD_BODY)) { + String message = cause != null && cause.getMessage() != null ? cause.getMessage().toLowerCase() : ""; + if (Objects.equals(message, COMPROMISED_PASSWORD_BODY)) { return new AuthException(FailureType.COMPROMISED_PASSWORD, COMPROMISED_PASSWORD_MESSAGE, cause); } default: