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 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))); } }); } diff --git a/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java b/Simperium/src/main/java/com/simperium/android/CredentialsActivity.java index 13d4640c..05008683 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(); } + private 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(); + } + private void startLogin() { final String email = getEditTextString(mInputEmail); final String password = getEditTextString(mInputPassword); diff --git a/Simperium/src/main/java/com/simperium/client/AuthException.java b/Simperium/src/main/java/com/simperium/client/AuthException.java index 49fba542..0a0835e3 100644 --- a/Simperium/src/main/java/com/simperium/client/AuthException.java +++ b/Simperium/src/main/java/com/simperium/client/AuthException.java @@ -2,19 +2,21 @@ 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 COMPROMISED_PASSWORD_BODY = "compromised password"; 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; public enum FailureType { - INVALID_ACCOUNT, EXISTING_ACCOUNT + INVALID_ACCOUNT, EXISTING_ACCOUNT, COMPROMISED_PASSWORD } public AuthException(FailureType code, String message){ @@ -39,6 +41,13 @@ 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: + // 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 + 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: return new AuthException(FailureType.INVALID_ACCOUNT, GENERIC_FAILURE_MESSAGE, cause); } 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/ - diff --git a/build.gradle b/build.gradle index d829219b..2f645678 100644 --- a/build.gradle +++ b/build.gradle @@ -35,5 +35,5 @@ def gitDescribe() { } def static gitVersion() { - '0.10.0' + '0.10.1' }