Commit bc7938c0 authored by Yusuke Iwaki's avatar Yusuke Iwaki

implement authentication error

parent 03f23709
...@@ -7,7 +7,7 @@ import android.support.annotation.Nullable; ...@@ -7,7 +7,7 @@ import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import chat.rocket.android.LaunchUtil; import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.fragment.login.GitHubOAuthWebViewFragment; import chat.rocket.android.fragment.oauth.GitHubOAuthWebViewFragment;
import chat.rocket.android.fragment.server_config.AuthenticatingFragment; import chat.rocket.android.fragment.server_config.AuthenticatingFragment;
import chat.rocket.android.fragment.server_config.ConnectingToHostFragment; import chat.rocket.android.fragment.server_config.ConnectingToHostFragment;
import chat.rocket.android.fragment.server_config.InputHostnameFragment; import chat.rocket.android.fragment.server_config.InputHostnameFragment;
...@@ -60,7 +60,9 @@ public class ServerConfigActivity extends AbstractFragmentActivity { ...@@ -60,7 +60,9 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
for (ServerConfig config : configList) { for (ServerConfig config : configList) {
ServerConfigCredential credential = config.getCredential(); ServerConfigCredential credential = config.getCredential();
if (credential != null && !TextUtils.isEmpty(credential.getType())) { if (credential != null
&& !TextUtils.isEmpty(credential.getType())
&& TextUtils.isEmpty(credential.getErrorMessage())) {
return launchFor(context, config); return launchFor(context, config);
} }
} }
...@@ -126,16 +128,20 @@ public class ServerConfigActivity extends AbstractFragmentActivity { ...@@ -126,16 +128,20 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
if (config.isTokenVerified()) { if (config.isTokenVerified()) {
finish(); finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
return; return;
} }
final String token = config.getToken(); final String token = config.getToken();
if (!TextUtils.isEmpty(token)) { if (!TextUtils.isEmpty(token)) {
showFragment(new AuthenticatingFragment());
return; return;
} }
final ServerConfigCredential credential = config.getCredential(); final ServerConfigCredential credential = config.getCredential();
if (credential != null && !TextUtils.isEmpty(credential.getType())) { if (credential != null
&& !TextUtils.isEmpty(credential.getType())
&& TextUtils.isEmpty(credential.getErrorMessage())) {
if (ServerConfigCredential.hasSecret(credential)) { if (ServerConfigCredential.hasSecret(credential)) {
showFragment(new AuthenticatingFragment()); showFragment(new AuthenticatingFragment());
} else { } else {
......
package chat.rocket.android.fragment.login; package chat.rocket.android.fragment.oauth;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
...@@ -21,6 +21,7 @@ import timber.log.Timber; ...@@ -21,6 +21,7 @@ import timber.log.Timber;
public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment { public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment {
private String serverConfigId; private String serverConfigId;
private String credentialId;
private String hostname; private String hostname;
private String url; private String url;
private boolean resultOK; private boolean resultOK;
...@@ -58,6 +59,7 @@ public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment { ...@@ -58,6 +59,7 @@ public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Invalid server_config_id given,"); "Invalid server_config_id given,");
} }
credentialId = serverConfig.getCredential().getId();
hostname = serverConfig.getHostname(); hostname = serverConfig.getHostname();
url = generateURL(oauthConfig.getClientId()); url = generateURL(oauthConfig.getClientId());
} }
...@@ -151,6 +153,7 @@ public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment { ...@@ -151,6 +153,7 @@ public class GitHubOAuthWebViewFragment extends AbstractWebViewFragment {
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject() realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", serverConfigId) .put("id", serverConfigId)
.put("credential", new JSONObject() .put("credential", new JSONObject()
.put("id", credentialId)
.put("type", "github") .put("type", "github")
.put("credentialToken", credentialToken) .put("credentialToken", credentialToken)
.put("credentialSecret", credentialSecret)) .put("credentialSecret", credentialSecret))
......
package chat.rocket.android.fragment.server_config; package chat.rocket.android.fragment.server_config;
import android.os.Handler;
import android.os.Message;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import chat.rocket.android.R; import chat.rocket.android.R;
import chat.rocket.android.helper.LogcatIfError; import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.MeteorLoginServiceConfiguration; import chat.rocket.android.model.MeteorLoginServiceConfiguration;
import chat.rocket.android.model.ServerConfig; import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ServerConfigCredential;
import chat.rocket.android.renderer.ServerConfigCredentialRenderer;
import io.realm.Realm; import io.realm.Realm;
import io.realm.RealmResults; import io.realm.RealmResults;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.List; import java.util.List;
import jp.co.crowdworks.realm_java_helpers.RealmHelper;
import jp.co.crowdworks.realm_java_helpers.RealmListObserver; import jp.co.crowdworks.realm_java_helpers.RealmListObserver;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts; import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONObject; import org.json.JSONObject;
...@@ -24,6 +30,12 @@ public class LoginFragment extends AbstractServerConfigFragment { ...@@ -24,6 +30,12 @@ public class LoginFragment extends AbstractServerConfigFragment {
return R.layout.fragment_login; return R.layout.fragment_login;
} }
private Handler errorShowingHandler = new Handler() {
@Override public void handleMessage(Message msg) {
Toast.makeText(rootView.getContext(), (String) msg.obj, Toast.LENGTH_SHORT).show();
}
};
private RealmListObserver<MeteorLoginServiceConfiguration> authProvidersObserver = private RealmListObserver<MeteorLoginServiceConfiguration> authProvidersObserver =
new RealmListObserver<MeteorLoginServiceConfiguration>() { new RealmListObserver<MeteorLoginServiceConfiguration>() {
@Override protected RealmResults<MeteorLoginServiceConfiguration> queryItems(Realm realm) { @Override protected RealmResults<MeteorLoginServiceConfiguration> queryItems(Realm realm) {
...@@ -50,25 +62,55 @@ public class LoginFragment extends AbstractServerConfigFragment { ...@@ -50,25 +62,55 @@ public class LoginFragment extends AbstractServerConfigFragment {
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject() realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", serverConfigId) .put("id", serverConfigId)
.put("credential", new JSONObject() .put("credential", new JSONObject()
.put("type", "email") .put("id", serverConfigId)
.put("type", ServerConfigCredential.TYPE_EMAIL)
.put("errorMessage", JSONObject.NULL)
.put("username", username.toString()) .put("username", username.toString())
.put("hashedPasswd", sha256sum(passwd.toString()))) .put("hashedPasswd", sha256sum(passwd.toString())))
) )
).continueWith(new LogcatIfError()); ).continueWith(new LogcatIfError());
}); });
showErrorIfNeeded();
}
private void showErrorIfNeeded() {
ServerConfig config = RealmHelper.executeTransactionForRead(realm -> realm.where(ServerConfig.class)
.equalTo("id", serverConfigId)
.isNotNull("credential.errorMessage")
.findFirst());
if (config != null) {
ServerConfigCredential credential = config.getCredential();
new ServerConfigCredentialRenderer(getContext(), credential)
.usernameInto((TextView) rootView.findViewById(R.id.editor_username));
String errorMessage = credential.getErrorMessage();
if (!TextUtils.isEmpty(errorMessage)) {
showError(errorMessage);
}
}
}
private void showError(String errString) {
errorShowingHandler.removeMessages(0);
Message msg = Message.obtain(errorShowingHandler, 0, errString);
errorShowingHandler.sendMessageDelayed(msg, 160);
} }
private static String sha256sum(String orig) { private static String sha256sum(String orig) {
MessageDigest d = null; MessageDigest messageDigest = null;
try { try {
d = MessageDigest.getInstance("SHA-256"); messageDigest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException exception) {
return null; return null;
} }
d.update(orig.getBytes()); messageDigest.update(orig.getBytes());
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for(byte b : d.digest()) sb.append(String.format("%02x", b & 0xff)); for (byte b : messageDigest.digest()) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString(); return sb.toString();
} }
...@@ -80,16 +122,18 @@ public class LoginFragment extends AbstractServerConfigFragment { ...@@ -80,16 +122,18 @@ public class LoginFragment extends AbstractServerConfigFragment {
boolean hasTwitter = false; boolean hasTwitter = false;
boolean hasGitHub = false; boolean hasGitHub = false;
for (MeteorLoginServiceConfiguration authProvider : authProviders) { for (MeteorLoginServiceConfiguration authProvider : authProviders) {
if (!hasTwitter && "twitter".equals(authProvider.getService())) { if (!hasTwitter
&& ServerConfigCredential.TYPE_TWITTER.equals(authProvider.getService())) {
hasTwitter = true; hasTwitter = true;
btnTwitter.setOnClickListener(view -> { btnTwitter.setOnClickListener(view -> {
setAuthType("twitter"); setAuthType(authProvider.getId(), ServerConfigCredential.TYPE_TWITTER);
}); });
} }
if (!hasGitHub && "github".equals(authProvider.getService())) { if (!hasGitHub
&& ServerConfigCredential.TYPE_GITHUB.equals(authProvider.getService())) {
hasGitHub = true; hasGitHub = true;
btnGitHub.setOnClickListener(view -> { btnGitHub.setOnClickListener(view -> {
setAuthType("github"); setAuthType(authProvider.getId(), ServerConfigCredential.TYPE_GITHUB);
}); });
} }
} }
...@@ -98,12 +142,14 @@ public class LoginFragment extends AbstractServerConfigFragment { ...@@ -98,12 +142,14 @@ public class LoginFragment extends AbstractServerConfigFragment {
btnGitHub.setVisibility(hasGitHub ? View.VISIBLE : View.GONE); btnGitHub.setVisibility(hasGitHub ? View.VISIBLE : View.GONE);
} }
private void setAuthType(final String authType) { private void setAuthType(final String authProviderId, final String authType) {
RealmHelperBolts.executeTransaction(realm -> RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject() realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", serverConfigId) .put("id", serverConfigId)
.put("credential", new JSONObject() .put("credential", new JSONObject()
.put("type", authType))) .put("id", authProviderId)
.put("type", authType))
.put("errorMessage", JSONObject.NULL))
).continueWith(new LogcatIfError()); ).continueWith(new LogcatIfError());
} }
......
...@@ -38,7 +38,7 @@ public class ServerConfig extends RealmObject { ...@@ -38,7 +38,7 @@ public class ServerConfig extends RealmObject {
return config != null; return config != null;
} }
@DebugLog public static void logError(String id, Exception exception) { @DebugLog public static void logConnectionError(String id, Exception exception) {
RealmHelperBolts.executeTransaction( RealmHelperBolts.executeTransaction(
realm -> realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject() realm -> realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("id", id) .put("id", id)
......
package chat.rocket.android.model; package chat.rocket.android.model;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android_ddp.DDPClientCallback;
import io.realm.RealmObject; import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey; import io.realm.annotations.PrimaryKey;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts; import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
public class ServerConfigCredential extends RealmObject { public class ServerConfigCredential extends RealmObject {
@PrimaryKey private String type; public static final String TYPE_EMAIL = "email";
public static final String TYPE_TWITTER = "twitter";
public static final String TYPE_GITHUB = "github";
@PrimaryKey private String id;
private String type;
private String credentialToken; private String credentialToken;
private String credentialSecret; private String credentialSecret;
private String username; private String username;
private String hashedPasswd; private String hashedPasswd;
private String errorMessage;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() { public String getType() {
return type; return type;
...@@ -53,6 +70,14 @@ public class ServerConfigCredential extends RealmObject { ...@@ -53,6 +70,14 @@ public class ServerConfigCredential extends RealmObject {
this.hashedPasswd = hashedPasswd; this.hashedPasswd = hashedPasswd;
} }
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public static boolean hasSecret(ServerConfigCredential credential) { public static boolean hasSecret(ServerConfigCredential credential) {
if (credential == null) { if (credential == null) {
return false; return false;
...@@ -73,4 +98,25 @@ public class ServerConfigCredential extends RealmObject { ...@@ -73,4 +98,25 @@ public class ServerConfigCredential extends RealmObject {
return false; return false;
} }
public static void logError(final String id, final Exception exception) {
RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfigCredential.class, new JSONObject()
.put("id", id)
.put("errorMessage", getErrorMessageFor(exception)))
).continueWith(new LogcatIfError());
}
private static String getErrorMessageFor(Exception exception) throws JSONException {
if (exception instanceof DDPClientCallback.RPC.Error) {
JSONObject error = ((DDPClientCallback.RPC.Error) exception).error;
if (!error.isNull("message")) {
return error.getString("message");
} else {
return error.toString();
}
} else {
return exception.getMessage();
}
}
} }
package chat.rocket.android.renderer;
import android.content.Context;
import android.view.View;
import chat.rocket.android.renderer.optional.Condition;
import chat.rocket.android.renderer.optional.Optional;
abstract class AbstractRenderer<T> {
protected final Context context;
protected final T object;
protected AbstractRenderer(Context context, T object) {
this.context = context;
this.object = object;
}
protected boolean shouldHandle(View view) {
return object != null && view != null;
}
protected boolean shouldHandle(View target, Condition additionalCondition, Optional optional,
String key) {
if (target == null || object == null) {
if (optional != null) optional.onNoData(key);
return false;
}
if (optional != null) {
if (!additionalCondition.isOK()) {
optional.onNoData(key);
return false;
} else {
optional.onDataExists(key);
}
}
return true;
}
}
package chat.rocket.android.renderer;
import android.content.Context;
import android.widget.TextView;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfigCredential;
public class ServerConfigCredentialRenderer extends AbstractRenderer<ServerConfigCredential> {
public ServerConfigCredentialRenderer(Context context, ServerConfigCredential credential) {
super(context, credential);
}
public ServerConfigCredentialRenderer usernameInto(TextView textView) {
if (!shouldHandle(textView)) {
return this;
}
if ("email".equals(object.getType())
&& !TextUtils.isEmpty(object.getUsername())) {
textView.setText(object.getUsername());
}
return this;
}
}
package chat.rocket.android.renderer.optional;
public interface Condition {
boolean isOK();
}
package chat.rocket.android.renderer.optional;
public interface Optional {
void onDataExists(String key);
void onNoData(String key);
}
package chat.rocket.android.renderer.optional;
import android.view.View;
import java.util.HashMap;
public class ViewVisibilityOptional extends HashMap<String, View> implements Optional {
@Override public void onDataExists(String key) {
if (containsKey(key)) {
get(key).setVisibility(View.VISIBLE);
}
}
@Override public void onNoData(String key) {
if (containsKey(key)) {
get(key).setVisibility(View.GONE);
}
}
}
...@@ -157,7 +157,7 @@ public class RocketChatWebSocketThread extends HandlerThread { ...@@ -157,7 +157,7 @@ public class RocketChatWebSocketThread extends HandlerThread {
} }
}).continueWith(task -> { }).continueWith(task -> {
if (task.isFaulted()) { if (task.isFaulted()) {
ServerConfig.logError(serverConfigId, task.getError()); ServerConfig.logConnectionError(serverConfigId, task.getError());
} }
return null; return null;
}); });
......
...@@ -2,9 +2,9 @@ package chat.rocket.android.service.observer; ...@@ -2,9 +2,9 @@ package chat.rocket.android.service.observer;
import android.content.Context; import android.content.Context;
import bolts.Task; import bolts.Task;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils; import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfig; import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ServerConfigCredential;
import chat.rocket.android.ws.RocketChatWebSocketAPI; import chat.rocket.android.ws.RocketChatWebSocketAPI;
import chat.rocket.android_ddp.DDPClientCallback; import chat.rocket.android_ddp.DDPClientCallback;
import io.realm.Realm; import io.realm.Realm;
...@@ -31,6 +31,7 @@ public class LoginCredentialObserver extends AbstractModelObserver<ServerConfig> ...@@ -31,6 +31,7 @@ public class LoginCredentialObserver extends AbstractModelObserver<ServerConfig>
.isNotNull("credential.credentialToken") .isNotNull("credential.credentialToken")
.isNotNull("credential.credentialSecret") .isNotNull("credential.credentialSecret")
.endGroup() .endGroup()
.isNull("credential.errorMessage")
.findAll(); .findAll();
} }
...@@ -41,6 +42,7 @@ public class LoginCredentialObserver extends AbstractModelObserver<ServerConfig> ...@@ -41,6 +42,7 @@ public class LoginCredentialObserver extends AbstractModelObserver<ServerConfig>
ServerConfig config = list.get(0); ServerConfig config = list.get(0);
final String serverConfigId = config.getId(); final String serverConfigId = config.getId();
final String credentialId = config.getCredential().getId();
login(config).onSuccessTask(task -> { login(config).onSuccessTask(task -> {
final String token = task.getResult().result.getString("token"); final String token = task.getResult().result.getString("token");
...@@ -51,17 +53,7 @@ public class LoginCredentialObserver extends AbstractModelObserver<ServerConfig> ...@@ -51,17 +53,7 @@ public class LoginCredentialObserver extends AbstractModelObserver<ServerConfig>
.put("tokenVerified", true))); .put("tokenVerified", true)));
}).continueWith(task -> { }).continueWith(task -> {
if (task.isFaulted()) { if (task.isFaulted()) {
RealmHelperBolts.executeTransaction(realm -> { ServerConfigCredential.logError(credentialId, task.getError());
ServerConfig _config = realm.where(ServerConfig.class)
.equalTo("id", serverConfigId)
.findFirst();
if (_config != null) {
_config.getCredential().deleteFromRealm();
_config.setToken(null);
}
return null;
}).continueWith(new LogcatIfError());
} }
return null; return null;
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment