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'
}