Smack 4.2.0 Account Manager enhancement request to support inBand catpcha registration with Bob XEP-0231 implementation source attached

(2017/1/8)

** Updated iqregisterx.zip and bob.zip to store the bob data in Base64 to be consistent with smack library and XEP document.**

and pass DataForm to onCaptchaRequested instead of Form for better backend processing

Below is an extraction of the implementation for aTalk to support server inBand registration with catpcha protection. The implementation requires access to method AccountManager#getRegistrationInfo(); however it is currently implemented as “private” not accessible to outside. Because of this, I have to duplicate the getRegistrationInfo() function in AccountManager in my source.as shown below.

Appreciate the Smack development team would consider to expose the method public and make info as returned object.

I have implemented the “XEP-0231: Bits of Binary” and attached the source with this message. The smack development team is welcome to include them in the standard smack library release.

======================================================

/**

  • Perform the InBand Registration for the accountId on the defined XMPP connection by pps.

  • Registration can either be:

    • simple username and password or
    • With catpcha protection using form with embedded catpcha image if available, else the
  • image is retrieved from the given url in the form.

  • @param pps

  • The protocolServiceProvider.

  • @param accountId

  • The username accountID for registration.

  • @throws XMPPException,

  • SmackException
    */
    public boolean registerAccount(ProtocolProviderServiceJabberImpl pps, AccountID accountId)
    throws XMPPException, SmackException
    {
    XMPPTCPConnection connection = pps.getConnection();
    try {
    if (!connection.isConnected())
    connection.connect();

    AccountManager accountManager = AccountManager.getInstance(connection);
    if (accountManager.isSupported()) {
    Registration info = getRegistrationInfo(connection);
    if (info != null) {
    Form form = null;
    DataForm dataFrom = info.getExtension(DataForm.ELEMENT, DataForm.NAMESPACE);

    Bitmap captcha = null;
    Bob bob = info.getExtension(Bob.ELEMENT, Bob.NAMESPACE);
    if (bob != null) {
    byte[] bytData = Base64.decode(bob.getData());
    InputStream stream = new ByteArrayInputStream(bytData);
    captcha = BitmapFactory.decodeStream(stream);
    }
    else {
    FormField urlField = (FormField) dataFrom.getField(“url”);
    if (urlField != null) {
    String urlString = urlField.getValues().get(0);
    URL uri = new URL(urlString);
    captcha = BitmapFactory.decodeStream(uri.openConnection().getInputStream());
    }
    }
    if ((captcha != null) && (dataFrom != null)) {
    pps.mOnCaptchaRequested.onCaptchaRequested(connection, accountId, dataFrom , captcha);
    }
    else
    return false;
    }
    else {
    String userName = accountId.getUserID();
    Localpart username = JidCreate.bareFrom(userName).getLocalpartOrNull();
    accountManager.sensitiveOperationOverInsecureConnection(false);
    accountManager.createAccount(username, password);
    }
    }
    else
    return false;
    }
    catch (IOException | InterruptedException ex) {
    // No response received within reply timeout. Timeout was 5000ms (~5s).
    // Rethrow XMPPException will trigger a re-login / registration dialog
    String exMsg = ex.getMessage();
    XMPPError.Builder xmppErrorBuilder
    = XMPPError.from(XMPPError.Condition.not_authorized, exMsg);
    throw new XMPPException.XMPPErrorException(null, xmppErrorBuilder.build());
    }
    return true;
    }

private synchronized Registration getRegistrationInfo(XMPPTCPConnection connection)
throws SmackException.NoResponseException, XMPPException.XMPPErrorException,
SmackException.NotConnectedException, InterruptedException
{
Registration reg = new Registration();
reg.setTo(connection.getXMPPServiceDomain());
return createPacketCollectorAndSend(connection, reg).nextResultOrThrow();
}

private PacketCollector createPacketCollectorAndSend(XMPPTCPConnection connection, IQ req)
throws SmackException.NotConnectedException, InterruptedException
{
PacketCollector collector = connection.createPacketCollectorAndSend(new StanzaIdFilter(req.getStanzaId()), req);
return collector;
}
bob.zip (4094 Bytes)

Thanks for your contribution, but there is already an development effort for BOB underway (SMACK-737). Also please note that due to time constraints and the higher effort to review contributions attached in archives, I’m not able to consider your contribution in the near future. An uncompressed diff, ideally in a single file, would be much better.

Below is an extraction of the implementation for aTalk to support server inBand registration with catpcha protection. The implementation requires access to method AccountManager#getRegistrationInfo(); however it is currently implemented as “private” not accessible to outside. Because of this, I have to duplicate the getRegistrationInfo() function in AccountManager in my source.as shown below.
No, you could have simply used reflection. I’m undecided whether or not this method should be part of Smack’s public API.

Thanks for your time in reply to the message and your team continuous effort in making improvement to Smack Library.

For the aTalk xmpp client development, I am using Android Studio, and linked in the Smack library version .4.2.0-beta3 via build.gradle file e,g.

compile ‘org.igniterealtime.smack:smack-android-extensions:4.2.0-beta3’ from the online jcenter() library.

For any latest Smack features newly released and not in the jcenter() release, I have to manually copy the library source into my project directory.

Do you know of any other way that I can just link in the Smack library latest build into the project build.gradle instead of the source copying?

Currently I am mid-way implementing “XEP-0158: CAPTCHA Forms”. Not sure if your team is also currently working on this area. If so what is the status, possibly sharing the source to help testing in aTalk?

I have just copies the bob source from the latest smack release into aTalk project directory. As I am using smack-4.2.0-beta3 jar library, I have to make some minor modifications to the BoBManager source:

// BoBIQ responseBoBIQ = connection.createStanzaCollectorAndSend(requestBoBIQ).nextResultOrThrow();
BoBIQ responseBoBIQ = connection.createPacketCollectorAndSend(requestBoBIQ).nextResultOrThrow();

Also added the the IQProvide support for XEP-0231: Bits of Binary
ProviderManager.addIQProvider(Bob.ELEMENT, Bob.NAMESPACE, new BoBIQProvider());

I see that smack implements BoB is for the full XEP-0231 protocol and treats BoB content as IQ element in the BoBIQProvider.

However BoB content is treated as ExtensionElement in Registration info. I have to include aTalk Bob Implementation and add

ProviderManager.addExtensionProvider(Bob.ELEMENT, Bob.NAMESPACE, new BobProvider());

In order to extract BoB data from Registration info to get to the image Base64 encoded data.

Without the aTalk BobProvider implementation, I am not sure how to extract the ExtensionElement from the Registration info.

Is there something I missed out in Smack to properly extract Bob content from the Registration info without using aTalk implementation?

Or would it be possible to change BobIQProver to BobExtensionProvider to extract the Bob Content as ExtensionElement. May be then the BobExtensionProvider can be use in BoB in Smack XEP-0231 implementation as well as in Registration info for extension implementation in the future.

For any latest Smack features newly released and not in the jcenter() release, I have to manually copy the library source into my project directory.

Do you know of any other way that I can just link in the Smack library latest build into the project build.gradle instead of the source copying?

  1. Do not use jcenter, use Maven Central

  2. For -SNAPSHOT releases use Maven Central’s snapshot repository

  3. For even more bleeding edge nightly build -SNAPSHOT releases use our repository at Index of /repo

Currently I am mid-way implementing “XEP-0158: CAPTCHA Forms”. Not sure if your team is also currently working on this area. If so what is the status, possibly sharing the source to help testing in aTalk?

Search the issue tracker for an related issue (should have something like “Add support for XEP-0158: CAPTCHA Forms” in its title). If there is an issue, then look at the description if there is an Github PR mentioned.

However BoB content is treated as ExtensionElement in Registration info.
Where is that element defined you are talking about? Where is XEP-0231 § 2.5 used as extension element of an registration info?

Is there something I missed out in Smack to properly extract Bob content from the Registration info without using aTalk implementation?

No, right now BoB is only supported via IQs. It appears to make sense to have an BoBExtension class which acts as common ground for BoB in IQs and Messages.

“Where is XEP-0231 § 2.5 used as extension element of an registration info?”

Please refer to the following the section

4. Extended In-Band Registration

of the link: XEP-0158: CAPTCHA Forms

In my initial message the statement Registration info = getRegistrationInfo(connection);

The returned info actually contains two extension Elements as show in the below captured variables when break just after the above statement:

map[1] will show as “standardPacketExtension” if I do not have

ProviderManager.addExtensionProvider(Bob.ELEMENT, Bob.NAMESPACE, new BobProvider());

=========================

info = {Registration@21758} “IQ Stanza (query jabber:iq:register) [from=atalk.org,id=j2b5n-31,type=result,]”

attributes = {HashMap@22166} size = 0

instructions = “You need a client that supports x:data and CAPTCHA to register”

childElementName = “query”

childElementNamespace = “jabber:iq:register”

type = {IQ$Type@22170} “result”

error = null

from = {DomainpartJid@22171} “atalk.org

id = “j2b5n-31”

language = null

packetExtensions = {MultiMap@22173}

** map = {LinkedHashMap@22177} size = 2**

** 0 = {LinkedHashMap$LinkedEntry@22181} “x\tjabber:x:data” -> " size = 1"**

** 1 = {LinkedHashMap$LinkedEntry@22182} “data\turn:xmpp:bob” -> " size = 1"**

==============================================

  1. For even more bleeding edge nightly build -SNAPSHOT releases use our repository at igniterealtime.org/repo

My current build.gradle contains

compile ‘org.igniterealtime.smack:smack-android-extensions:4.2.0-beta3’

How should I include the smack library and change the above statement to use the smack nightly build -SNAPSHOT.

============= Below is an exact copy of the inBand registration (with captcha protection) result send from ejabberd server ===========

You need a client that supports x:data and CAPTCHA to register

urn:xmpp:captcha

Choose a username and password to register with this server

If you don't see the CAPTCHA image here, visit the web page.

workaround-for-psi

http://atalk.sytes.net:5280/captcha/10704742350567531336/image

atalk.org

10704742350567531336

8nt9Q-194

cid:sha1+b8f653d1252beb01a35902b552a29b04d89f80fd@bob.xmpp.org

<data xmlns=‘urn:xmpp:bob’ cid=‘sha1+b8f653d1252beb01a35902b552a29b04d89f80fd@bob.xmpp.org’ max-age=‘0’ type=‘image/png’>iVBORw0KGgoAAAANSUhEUgAAAIwAAAA8CAAAAACRYQ2XAAAABGdBTUEAALGPC/ xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL 8AAAAHdElNRQfhAQEALyMka2VNAAAMd0lEQVRo3u1YZ1RUS

How should I include the smack library and change the above statement to use the smack nightly build -SNAPSHOT.
Read the gradle manual on how to add additional repositories and how to configure unique snapshot versions.

Below is an exact copy of the inBand registration (with captcha protection) result send from ejabberd server

I don’t see where the usage of {urn:xmpp:bob}data is specified in XEP-0158. This is not really a shop stopper, but it would be nice to have an XEP describing how XEP-0231 and -0158 can be combined.

Yes, you are correct. Going through the document again:

There is no mention of the Bob Embedded in the InBand Registration, the OCR requirements only describes adding media url link or access the bob using the cid specified.

Section 1 (last paragraph):

The generic challenge protocol described in this document is designed for incorporation into protocols such as In-Band Registration (XEP-0077) [4], Multi-User Chat (XEP-0045) [5], Privacy Lists (XEP-0016) [6], and SPIM-Blocking Control (XEP-0159) [7].

Section 3.1.2:

  <field var='ocr' label='Enter the text you see'>
    <media xmlns='urn:xmpp:media-element'
           height='80'
           width='290'>
      <uri type='image/jpeg'>
        http://www.victim.com/challenges/ocr.jpeg?F3A6292C
      </uri>
      <uri type='image/jpeg'>
        cid:sha1+f24030b8d91d233bac14777be5ab531ca3b9f102@bob.xmpp.org
      </uri>
    </media>
  </field>

(last paragraph)

The sender then would retrieve the media data via HTTP or (for the cid: URIs) via XMPP as described in Bits of Binary (XEP-0231) [14].

The document only covers to a point only a CID or URL link is provided:

The ejabbard XMPP server has actually taken a step further to embed the actual bob data in addition to the XRP-0158 requirements. (see above attached data captured from a ejabberd server). Not sure if Prosody or others xmpp server are doing the same.

With reference to Conversion xmpp client source, aTalk has similarly implemented the bob extraction from inBand Registration info.

To support the embedded bob, I am keeping the BobProvider in aTalk. Hopefully Smack will consider to include the same support in future release.

“Read the gradle manual on how to add additional repositories and how to configure unique snapshot versions.”

I have include the following in the aTalk build.gradle:

repositories {
mavenCentral()
maven {
url “https://igniterealtime.org/repo
}
}

ext {
smackVersion=‘4.2.0-beta4-SNAPSHOT’
}

dependencies {

compile "org.igniterealtime.smack:smack-android-extensions:$smackVersion"
compile "org.igniterealtime.smack:smack-bosh:$smackVersion"
compile "org.igniterealtime.smack:smack-compression-jzlib:$smackVersion"
compile "org.igniterealtime.smack:smack-experimental:$smackVersion"
compile "org.igniterealtime.smack:smack-im:$smackVersion"
compile "org.igniterealtime.smack:smack-legacy:$smackVersion"
compile "org.igniterealtime.smack:smack-resolver-minidns:$smackVersion"
compile "org.igniterealtime.smack:smack-sasl-provided:$smackVersion"
compile “org.igniterealtime.smack:smack-tcp:$smackVersion”


}

However I am still getting the following durng sync project with gradle file e.g.:

Error:Failed to resolve: org.igniterealtime.smack:smack-android-extensions:4.2.0-beta4-SNAPSHOT

for all the smack libraries included

Any suggestion?

Also I have checked the smack releases on Maven snapshot repository, tt contains only the earlier smack releases.

https://oss.sonatype.org/content/repositories/snapshots/org/igniterealtime/smack /

Any suggestion?
The dependency resolution fails because you did specify a changing snapshot version, not a stable unique snapshot. If you look at Index of /repo/org/igniterealtime/smack/smack-core/4.3.0-alpha1-SNAPSHOT you will find that stable Maven unique snapshot versions look like 4.3.0-alpha1-20170104.010403-1.

Please note that we currently do not provide nightly snapshots for non-master branches. But I’ve just uploaded a new 4.2.0-rc2-SNAPSHOT to Maven Central’s snapshot repository, which you may want to try.

Hopefully Smack will consider to include the same support in future release
I’m not keen on supporting unspecified custom extensions. I also personally think that including the bob within the is not a good idea.

“I’m not keen on supporting unspecified custom extensions. I also personally think that including the bob within the is not a good idea.”

Understand your team decision. As aTalk has both the BoB extension and Provider already created; and the implementation is not in conflict with the standard smack library. Therefore aTalk will keep the embedded BoB feature support.

Thanks for the new 4.2.0-rc2-SNAPSHOT smack library release. I have integrated into the aTalk and is now fully working. I have also implemented “XEP-0077: In-Band Registration” for both the registration submission with fields element or DataForm based on rc2 release. The iqregisterx.zip source is attached for your reference and inclusion in Smack standard library is welcome. As the smack-extension is a dependency library, I am unable to overwrite the class namespace, therefore I have created a same level sub-directory as iqresisterx package.

The iqregisterx has been tested working on aTalk (see source below). The creation of the FormField and DataForm is rather tedious if there are many fields required. Not sure if there is any enhancement that can be made in xdata library to ease the DataForm entry. Note: some for the FormField in captcha protected registration do not have the FormField.Type specified.

As per “XEP-0077: In-Band Registration” document section 3 requirement. I have created and expose the getRegistrationInfo() method for easy application implementation.

3. Use Cases

3.1 Entity Registers with a Host

In order to determine which fields are required for registration with a host, an entity SHOULD first send an IQ get to the host. The entity SHOULD NOT attempt to guess at the required fields by first sending an IQ set, since the nature of the required data is subject to service provisioning.

=========== aTalk registration submission with DataForm ======

/**

  • Handles the ActionEvent triggered when one user clicks on the Submit button.
    */
    private boolean onAcceptClicked()
    {
    FormField formField;
    DataForm dataForm = new DataForm(DataForm.Type.submit);

    formField = new FormField(FORM_TYPE);
    formField.addValue(NS_CAPTCHA);
    dataForm.addField(formField);

    formField = mDataForm.getField(CHALLENGE);
    String cl = formField.getValues().get(0);
    formField = new FormField(CHALLENGE);
    formField.addValue(cl);
    dataForm.addField(formField);

    formField = mDataForm.getField(SID);
    String sid = formField.getValues().get(0);
    formField = new FormField(SID);
    formField.addValue(sid);
    dataForm.addField(formField);

    formField = new FormField(ANSWER);
    formField.addValue(“3”);
    dataForm.addField(formField);

    // Only localPart is required
    String userName = XmppStringUtils.parseLocalpart(mAccountId.getUserID());
    formField = new FormField(USER_NAME);
    formField.addValue(userName);
    dataForm.addField(formField);

    Editable pwd = mPasswordField.getText();
    if (pwd != null) {
    formField = new FormField(PASSWORD);
    formField.addValue(pwd.toString());
    dataForm.addField(formField);
    }

    Editable rc = input.getText();
    if (rc != null) {
    formField = new FormField(OCR);
    formField.addValue(rc.toString());
    dataForm.addField(formField);
    }

    if (mConnection != null) {
    try {
    if (!mConnection.isConnected())
    mConnection.connect();
    }
    catch (InterruptedException | IOException | SmackException | XMPPException e) {
    e.printStackTrace();
    }

    AccountManager accountManager = AccountManager.getInstance(mConnection);
    try {
    accountManager.createAccount(dataForm);
    }
    catch (SmackException.NoResponseException | XMPPException.XMPPErrorException
    | SmackException.NotConnectedException | InterruptedException e) {
    e.printStackTrace();
    }
    }
    return true;
    }
    iqregisterx.zip (10809 Bytes)

for anybody who wish to use the enclosed classes (zip). Do remember to add the following into your source

/**

  • Tell Smack what are the additional StreamFeatureProvider and ExtensionProviders that the apps can support
    */

// XEP-0231: Bits of Binary
ProviderManager.addExtensionProvider(BoB.ELEMENT, BoB.NAMESPACE, new BoBProvider())

ProviderManager.addIQProvider(Registration.ELEMENT, Registration.NAMESPACE,
new RegistrationProvider());