aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmin Novak <armin.novak@gmail.com>2015-10-11 13:11:50 +0200
committerArmin Novak <armin.novak@gmail.com>2015-10-11 13:11:50 +0200
commit6a6cb43b17df1557033c217d199706bd4e09673e (patch)
treeed798114dd8efba872156e61914e3771282c0d0c
parentca1fe61f09107d19d0fee07c9234739cfd8e4344 (diff)
Captcha support.
-rw-r--r--src/main/java/eu/siacs/conversations/generator/IqGenerator.java10
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java57
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java74
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java6
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java112
-rw-r--r--src/main/res/values/strings.xml3
6 files changed, 238 insertions, 24 deletions
diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
index 42c57b24..ba852606 100644
--- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
+++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java
@@ -266,4 +266,14 @@ public class IqGenerator extends AbstractGenerator {
}
return packet;
}
+
+ public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) {
+ final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
+
+ register.setTo(account.getServer());
+ register.setId(id);
+ register.query("jabber:iq:register").addChild(data);
+
+ return register;
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 4d0228e9..9938c18f 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -29,6 +29,7 @@ import android.security.KeyChain;
import android.security.KeyChainException;
import android.util.Log;
import android.util.LruCache;
+import android.util.DisplayMetrics;
import net.java.otr4j.OtrException;
import net.java.otr4j.session.Session;
@@ -257,6 +258,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
private int showErrorToastListenerCount = 0;
private int unreadCount = -1;
private OnAccountUpdate mOnAccountUpdate = null;
+ private OnCaptchaRequested mOnCaptchaRequested = null;
private OnStatusChanged statusListener = new OnStatusChanged() {
@Override
@@ -315,6 +317,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
};
private int accountChangedListenerCount = 0;
+ private int captchaRequestedListenerCount = 0;
private OnRosterUpdate mOnRosterUpdate = null;
private OnUpdateBlocklist mOnUpdateBlocklist = null;
private int updateBlocklistListenerCount = 0;
@@ -1459,6 +1462,31 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void setOnCaptchaRequestedListener(OnCaptchaRequested listener) {
+ synchronized (this) {
+ if (checkListeners()) {
+ switchToForeground();
+ }
+ this.mOnCaptchaRequested = listener;
+ if (this.captchaRequestedListenerCount < 2) {
+ this.captchaRequestedListenerCount++;
+ }
+ }
+ }
+
+ public void removeOnCaptchaRequestedListener() {
+ synchronized (this) {
+ this.captchaRequestedListenerCount--;
+ if (this.captchaRequestedListenerCount <= 0) {
+ this.mOnCaptchaRequested = null;
+ this.captchaRequestedListenerCount = 0;
+ if (checkListeners()) {
+ switchToBackground();
+ }
+ }
+ }
+ }
+
public void setOnRosterUpdateListener(final OnRosterUpdate listener) {
synchronized (this) {
if (checkListeners()) {
@@ -1563,6 +1591,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return (this.mOnAccountUpdate == null
&& this.mOnConversationUpdate == null
&& this.mOnRosterUpdate == null
+ && this.mOnCaptchaRequested == null
&& this.mOnUpdateBlocklist == null
&& this.mOnShowErrorToast == null
&& this.mOnKeyStatusUpdated == null);
@@ -2464,6 +2493,20 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public boolean displayCaptchaRequest(Account account, String id, Data data, Bitmap captcha) {
+ boolean rc = false;
+ if (mOnCaptchaRequested != null) {
+ DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics();
+ Bitmap scaled = Bitmap.createScaledBitmap(captcha, (int)(captcha.getWidth() * metrics.scaledDensity),
+ (int)(captcha.getHeight() * metrics.scaledDensity), false);
+
+ mOnCaptchaRequested.onCaptchaRequested(account, id, data, scaled);
+ rc = true;
+ }
+
+ return rc;
+ }
+
public void updateBlocklistUi(final OnUpdateBlocklist.Status status) {
if (mOnUpdateBlocklist != null) {
mOnUpdateBlocklist.OnUpdateBlocklist(status);
@@ -2620,6 +2663,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
+ public void sendCreateAccountWithCaptchaPacket(Account account, String id, Data data) {
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.sendCaptchaRegistryRequest(id, data);
+ }
+ }
+
public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) {
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
@@ -2786,6 +2836,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
void onAccountUpdate();
}
+ public interface OnCaptchaRequested {
+ void onCaptchaRequested(Account account,
+ String id,
+ Data data,
+ Bitmap captcha);
+ }
+
public interface OnRosterUpdate {
void onRosterUpdate();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index dbd5f117..cba9275f 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -1,9 +1,11 @@
package eu.siacs.conversations.ui;
+import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
+import android.graphics.Bitmap;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@@ -31,17 +33,20 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.XmppConnection.Features;
+import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.pep.Avatar;
-public class EditAccountActivity extends XmppActivity implements OnAccountUpdate, OnKeyStatusUpdated {
+public class EditAccountActivity extends XmppActivity implements OnAccountUpdate,
+ OnKeyStatusUpdated, OnCaptchaRequested {
private AutoCompleteTextView mAccountJid;
private EditText mPassword;
@@ -72,6 +77,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
private ImageButton mRegenerateAxolotlKeyButton;
private LinearLayout keys;
private LinearLayout keysCard;
+ private AlertDialog mCaptchaDialog = null;
private Jid jidToEdit;
private boolean mInitMode = false;
@@ -681,4 +687,70 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
public void onKeyStatusUpdated() {
refreshUi();
}
+
+ @Override
+ public void onCaptchaRequested(final Account account, final String id, final Data data,
+ final Bitmap captcha) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ final ImageView view = new ImageView(this);
+ final LinearLayout layout = new LinearLayout(this);
+ final EditText input = new EditText(this);
+
+ view.setImageBitmap(captcha);
+ view.setScaleType(ImageView.ScaleType.FIT_CENTER);
+
+ input.setHint(getString(R.string.captcha_hint));
+
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.addView(view);
+ layout.addView(input);
+
+ builder.setTitle(getString(R.string.captcha_required));
+ builder.setView(layout);
+
+ builder.setPositiveButton(getString(R.string.ok),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String rc = input.getText().toString();
+ data.put("username", account.getUsername());
+ data.put("password", account.getPassword());
+ data.put("ocr", rc);
+ data.submit();
+
+ if (xmppConnectionServiceBound) {
+ xmppConnectionService.sendCreateAccountWithCaptchaPacket(
+ account, id, data);
+ }
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
+ }
+ }
+ });
+
+ builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
+ }
+ }
+ });
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) {
+ mCaptchaDialog.dismiss();
+ }
+ mCaptchaDialog = builder.create();
+ mCaptchaDialog.show();
+ }
+ });
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index e3848fe2..5e6c188d 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -281,6 +281,9 @@ public abstract class XmppActivity extends Activity {
if (this instanceof XmppConnectionService.OnAccountUpdate) {
this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
}
+ if (this instanceof XmppConnectionService.OnCaptchaRequested) {
+ this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
+ }
if (this instanceof XmppConnectionService.OnRosterUpdate) {
this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
}
@@ -305,6 +308,9 @@ public abstract class XmppActivity extends Activity {
if (this instanceof XmppConnectionService.OnAccountUpdate) {
this.xmppConnectionService.removeOnAccountListChangedListener();
}
+ if (this instanceof XmppConnectionService.OnCaptchaRequested) {
+ this.xmppConnectionService.removeOnCaptchaRequestedListener();
+ }
if (this instanceof XmppConnectionService.OnRosterUpdate) {
this.xmppConnectionService.removeOnRosterUpdateListener();
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index c41b6974..b62c4e6b 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -1,10 +1,15 @@
package eu.siacs.conversations.xmpp;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.wifi.WifiConfiguration;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.SystemClock;
+import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
@@ -14,6 +19,7 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -24,6 +30,8 @@ import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@@ -59,6 +67,8 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Tag;
import eu.siacs.conversations.xml.TagWriter;
import eu.siacs.conversations.xml.XmlReader;
+import eu.siacs.conversations.xmpp.forms.Data;
+import eu.siacs.conversations.xmpp.forms.Field;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.jingle.OnJinglePacketReceived;
@@ -116,6 +126,29 @@ public class XmppConnection implements Runnable {
private SaslMechanism saslMechanism;
+ private OnIqPacketReceived createPacketReceiveHandler() {
+ OnIqPacketReceived receiver = new OnIqPacketReceived() {
+ @Override
+ public void onIqPacketReceived(Account account, IqPacket packet) {
+ if (packet.getType() == IqPacket.TYPE.RESULT) {
+ account.setOption(Account.OPTION_REGISTER,
+ false);
+ changeStatus(Account.State.REGISTRATION_SUCCESSFUL);
+ } else if (packet.hasChild("error")
+ && (packet.findChild("error")
+ .hasChild("conflict"))) {
+ changeStatus(Account.State.REGISTRATION_CONFLICT);
+ } else {
+ changeStatus(Account.State.REGISTRATION_FAILED);
+ Log.d(Config.LOGTAG, packet.toString());
+ }
+ disconnect(true);
+ }
+ };
+
+ return receiver;
+ }
+
public XmppConnection(final Account account, final XmppConnectionService service) {
this.account = account;
this.wakeLock = service.getPowerManager().newWakeLock(
@@ -643,6 +676,15 @@ public class XmppConnection implements Runnable {
return mechanisms;
}
+ public void sendCaptchaRegistryRequest(String id, Data data) {
+ if (data == null) {
+ setAccountCreationFailed("");
+ } else {
+ IqPacket request = getIqGenerator().generateCreateAccountWithCaptcha(account, id, data);
+ sendIqPacket(request, createPacketReceiveHandler());
+ }
+ }
+
private void sendRegistryRequest() {
final IqPacket register = new IqPacket(IqPacket.TYPE.GET);
register.query("jabber:iq:register");
@@ -651,6 +693,7 @@ public class XmppConnection implements Runnable {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
+ boolean failed = false;
if (packet.getType() == IqPacket.TYPE.RESULT
&& packet.query().hasChild("username")
&& (packet.query().hasChild("password"))) {
@@ -659,37 +702,60 @@ public class XmppConnection implements Runnable {
final Element password = new Element("password").setContent(account.getPassword());
register.query("jabber:iq:register").addChild(username);
register.query().addChild(password);
- sendIqPacket(register, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(final Account account, final IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.RESULT) {
- account.setOption(Account.OPTION_REGISTER,
- false);
- changeStatus(Account.State.REGISTRATION_SUCCESSFUL);
- } else if (packet.hasChild("error")
- && (packet.findChild("error")
- .hasChild("conflict"))) {
- changeStatus(Account.State.REGISTRATION_CONFLICT);
- } else {
- changeStatus(Account.State.REGISTRATION_FAILED);
- Log.d(Config.LOGTAG, packet.toString());
- }
- disconnect(true);
+ sendIqPacket(register, createPacketReceiveHandler());
+ } else if (packet.getType() == IqPacket.TYPE.RESULT
+ && (packet.query().hasChild("x", "jabber:x:data"))) {
+ final Data data = Data.parse(packet.query().findChild("x", "jabber:x:data"));
+ final Element blob = packet.query().findChild("data", "urn:xmpp:bob");
+ final String id = packet.getId();
+
+ Bitmap captcha = null;
+ if (blob != null) {
+ try {
+ final String base64Blob = blob.getContent();
+ final byte[] strBlob = Base64.decode(base64Blob, Base64.DEFAULT);
+ InputStream stream = new ByteArrayInputStream(strBlob);
+ captcha = BitmapFactory.decodeStream(stream);
+ } catch (Exception e) {
+
+ }
+ } else {
+ try {
+ Field url = data.getFieldByName("url");
+ String urlString = url.findChildContent("value");
+
+ URL uri = new URL(urlString);
+ captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream());
+ } catch(MalformedURLException e) {
+ Log.e(Config.LOGTAG, e.toString());
+ } catch(IOException e) {
+ Log.e(Config.LOGTAG, e.toString());
}
- });
+ }
+
+ if (captcha != null) {
+ failed = !mXmppConnectionService.displayCaptchaRequest(account, id, data, captcha);
+ }
} else {
+ failed = true;
+ }
+
+ if (failed) {
final Element instructions = packet.query().findChild("instructions");
- changeStatus(Account.State.REGISTRATION_FAILED);
- disconnect(true);
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
- + ": could not register. instructions are"
- + (instructions != null ? instructions.getContent() : ""));
+ setAccountCreationFailed((instructions != null) ? instructions.getContent() : "");
}
}
});
}
+ private void setAccountCreationFailed(String instructions) {
+ changeStatus(Account.State.REGISTRATION_FAILED);
+ disconnect(true);
+ Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ + ": could not register. instructions are"
+ + instructions);
+ }
+
private void sendBindRequest() {
while(!mXmppConnectionService.areMessagesInitialized()) {
try {
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 5be80088..3307b973 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -527,4 +527,7 @@
<string name="action_add_account_from_key">Add account from key</string>
<string name="unable_to_parse_certificate">Unable to parse certificate</string>
<string name="authenticate_with_certificate">Leave empty to authenticate w/ certificate</string>
+ <string name="captcha_ocr">Captcha text</string>
+ <string name="captcha_required">Captcha required</string>
+ <string name="captcha_hint">enter the text from the image</string>
</resources>