From 6a6cb43b17df1557033c217d199706bd4e09673e Mon Sep 17 00:00:00 2001 From: Armin Novak Date: Sun, 11 Oct 2015 13:11:50 +0200 Subject: Captcha support. --- .../siacs/conversations/generator/IqGenerator.java | 10 ++ .../services/XmppConnectionService.java | 57 +++++++++++ .../conversations/ui/EditAccountActivity.java | 74 +++++++++++++- .../eu/siacs/conversations/ui/XmppActivity.java | 6 ++ .../siacs/conversations/xmpp/XmppConnection.java | 112 ++++++++++++++++----- src/main/res/values/strings.xml | 3 + 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 @@ Add account from key Unable to parse certificate Leave empty to authenticate w/ certificate + Captcha text + Captcha required + enter the text from the image -- cgit v1.2.3