modify XmppConnection to change status to online for unbound cons
This commit is contained in:
parent
a204bf9ec1
commit
35360fde91
|
@ -2,11 +2,11 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "219a451e9a1889222b7549c8b3c0a5b3",
|
"identityHash": "1952101c2c0d439fcd6c9d417f126a54",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "account",
|
"tableName": "account",
|
||||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `address` TEXT NOT NULL, `resource` TEXT, `randomSeed` BLOB, `enabled` INTEGER NOT NULL, `quickStartAvailable` INTEGER NOT NULL, `pendingRegistration` INTEGER NOT NULL, `loggedInSuccessfully` INTEGER NOT NULL, `showErrorNotification` INTEGER NOT NULL, `rosterVersion` TEXT, `hostname` TEXT, `port` INTEGER, `directTls` INTEGER, `proxytype` TEXT, `proxyhostname` TEXT, `proxyport` INTEGER)",
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `address` TEXT NOT NULL, `resource` TEXT, `randomSeed` BLOB, `enabled` INTEGER NOT NULL, `quickStartAvailable` INTEGER NOT NULL, `loginAndBind` INTEGER NOT NULL, `showErrorNotification` INTEGER NOT NULL, `rosterVersion` TEXT, `hostname` TEXT, `port` INTEGER, `directTls` INTEGER, `proxytype` TEXT, `proxyhostname` TEXT, `proxyport` INTEGER)",
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldPath": "id",
|
"fieldPath": "id",
|
||||||
|
@ -45,14 +45,8 @@
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldPath": "pendingRegistration",
|
"fieldPath": "loginAndBind",
|
||||||
"columnName": "pendingRegistration",
|
"columnName": "loginAndBind",
|
||||||
"affinity": "INTEGER",
|
|
||||||
"notNull": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldPath": "loggedInSuccessfully",
|
|
||||||
"columnName": "loggedInSuccessfully",
|
|
||||||
"affinity": "INTEGER",
|
"affinity": "INTEGER",
|
||||||
"notNull": true
|
"notNull": true
|
||||||
},
|
},
|
||||||
|
@ -2266,7 +2260,7 @@
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '219a451e9a1889222b7549c8b3c0a5b3')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1952101c2c0d439fcd6c9d417f126a54')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,6 +16,7 @@ public final class Namespace {
|
||||||
public static final String BOOKMARKS2_COMPAT = BOOKMARKS2 + "#compat";
|
public static final String BOOKMARKS2_COMPAT = BOOKMARKS2 + "#compat";
|
||||||
public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0";
|
public static final String BOOKMARKS_CONVERSION = "urn:xmpp:bookmarks-conversion:0";
|
||||||
public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams";
|
public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams";
|
||||||
|
public static final String CAPTCHA = "urn:xmpp:captcha";
|
||||||
public static final String CARBONS = "urn:xmpp:carbons:2";
|
public static final String CARBONS = "urn:xmpp:carbons:2";
|
||||||
public static final String CHANNEL_BINDING = "urn:xmpp:sasl-cb:0";
|
public static final String CHANNEL_BINDING = "urn:xmpp:sasl-cb:0";
|
||||||
public static final String CHAT_MARKERS = "urn:xmpp:chat-markers:0";
|
public static final String CHAT_MARKERS = "urn:xmpp:chat-markers:0";
|
||||||
|
|
|
@ -2,6 +2,7 @@ package im.conversations.android.database.dao;
|
||||||
|
|
||||||
import androidx.room.Dao;
|
import androidx.room.Dao;
|
||||||
import androidx.room.Insert;
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.OnConflictStrategy;
|
||||||
import androidx.room.Query;
|
import androidx.room.Query;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
@ -13,7 +14,7 @@ import java.util.List;
|
||||||
@Dao
|
@Dao
|
||||||
public interface AccountDao {
|
public interface AccountDao {
|
||||||
|
|
||||||
@Insert
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
long insert(final AccountEntity account);
|
long insert(final AccountEntity account);
|
||||||
|
|
||||||
@Query("SELECT id,address,randomSeed FROM account WHERE enabled = 1")
|
@Query("SELECT id,address,randomSeed FROM account WHERE enabled = 1")
|
||||||
|
@ -37,11 +38,8 @@ public interface AccountDao {
|
||||||
@Query("SELECT quickStartAvailable FROM account where id=:id")
|
@Query("SELECT quickStartAvailable FROM account where id=:id")
|
||||||
boolean quickStartAvailable(long id);
|
boolean quickStartAvailable(long id);
|
||||||
|
|
||||||
@Query("SELECT pendingRegistration FROM account where id=:id")
|
@Query("SELECT loginAndBind FROM account where id=:id")
|
||||||
boolean pendingRegistration(long id);
|
boolean loginAndBind(long id);
|
||||||
|
|
||||||
@Query("SELECT loggedInSuccessfully == 0 FROM account where id=:id")
|
|
||||||
boolean isInitialLogin(long id);
|
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"UPDATE account set quickStartAvailable=:available WHERE id=:id AND"
|
"UPDATE account set quickStartAvailable=:available WHERE id=:id AND"
|
||||||
|
@ -49,14 +47,9 @@ public interface AccountDao {
|
||||||
void setQuickStartAvailable(long id, boolean available);
|
void setQuickStartAvailable(long id, boolean available);
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"UPDATE account set pendingRegistration=:pendingRegistration WHERE id=:id AND"
|
"UPDATE account set loginAndBind=:loginAndBind WHERE id=:id AND"
|
||||||
+ " pendingRegistration != :pendingRegistration")
|
+ " loginAndBind != :loginAndBind")
|
||||||
void setPendingRegistration(long id, boolean pendingRegistration);
|
void setLoginAndBind(long id, boolean loginAndBind);
|
||||||
|
|
||||||
@Query(
|
|
||||||
"UPDATE account set loggedInSuccessfully=:loggedInSuccessfully WHERE id=:id AND"
|
|
||||||
+ " loggedInSuccessfully != :loggedInSuccessfully")
|
|
||||||
int setLoggedInSuccessfully(long id, boolean loggedInSuccessfully);
|
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"UPDATE account set showErrorNotification=:showErrorNotification WHERE id=:id AND"
|
"UPDATE account set showErrorNotification=:showErrorNotification WHERE id=:id AND"
|
||||||
|
|
|
@ -28,11 +28,7 @@ public class AccountEntity {
|
||||||
public boolean enabled;
|
public boolean enabled;
|
||||||
|
|
||||||
public boolean quickStartAvailable = false;
|
public boolean quickStartAvailable = false;
|
||||||
public boolean pendingRegistration = false;
|
public boolean loginAndBind = true;
|
||||||
|
|
||||||
// TODO this is only used during setup; depending on how the setup procedure will look in the
|
|
||||||
// future we might get rid of this property
|
|
||||||
public boolean loggedInSuccessfully = false;
|
|
||||||
|
|
||||||
public boolean showErrorNotification = true;
|
public boolean showErrorNotification = true;
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,15 @@ import androidx.annotation.NonNull;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import im.conversations.android.IDs;
|
import im.conversations.android.IDs;
|
||||||
import im.conversations.android.database.CredentialStore;
|
import im.conversations.android.database.CredentialStore;
|
||||||
import im.conversations.android.database.entity.AccountEntity;
|
import im.conversations.android.database.entity.AccountEntity;
|
||||||
import im.conversations.android.database.model.Account;
|
import im.conversations.android.database.model.Account;
|
||||||
import im.conversations.android.xmpp.ConnectionPool;
|
import im.conversations.android.xmpp.ConnectionPool;
|
||||||
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
|
import im.conversations.android.xmpp.manager.RegistrationManager;
|
||||||
|
|
||||||
public class AccountRepository extends AbstractRepository {
|
public class AccountRepository extends AbstractRepository {
|
||||||
|
|
||||||
|
@ -18,7 +21,8 @@ public class AccountRepository extends AbstractRepository {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Account createAccount(@NonNull final Jid address, final String password) {
|
private Account createAccount(
|
||||||
|
@NonNull final Jid address, final String password, final boolean loginAndBind) {
|
||||||
Preconditions.checkArgument(
|
Preconditions.checkArgument(
|
||||||
address.isBareJid(), "Account should be specified without resource");
|
address.isBareJid(), "Account should be specified without resource");
|
||||||
Preconditions.checkArgument(password != null, "Missing password");
|
Preconditions.checkArgument(password != null, "Missing password");
|
||||||
|
@ -26,6 +30,7 @@ public class AccountRepository extends AbstractRepository {
|
||||||
final var entity = new AccountEntity();
|
final var entity = new AccountEntity();
|
||||||
entity.address = address;
|
entity.address = address;
|
||||||
entity.enabled = true;
|
entity.enabled = true;
|
||||||
|
entity.loginAndBind = loginAndBind;
|
||||||
entity.randomSeed = randomSeed;
|
entity.randomSeed = randomSeed;
|
||||||
final long id = database.accountDao().insert(entity);
|
final long id = database.accountDao().insert(entity);
|
||||||
final var account = new Account(id, address, entity.randomSeed);
|
final var account = new Account(id, address, entity.randomSeed);
|
||||||
|
@ -38,8 +43,23 @@ public class AccountRepository extends AbstractRepository {
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<Account> createAccountAsync(
|
||||||
|
final @NonNull Jid address, final String password, final boolean loginAndBind) {
|
||||||
|
return Futures.submit(() -> createAccount(address, password, loginAndBind), IO_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
public ListenableFuture<Account> createAccountAsync(
|
public ListenableFuture<Account> createAccountAsync(
|
||||||
final @NonNull Jid address, final String password) {
|
final @NonNull Jid address, final String password) {
|
||||||
return Futures.submit(() -> createAccount(address, password), IO_EXECUTOR);
|
return createAccountAsync(address, password, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<RegistrationManager.Registration> getRegistration(
|
||||||
|
final Account account) {
|
||||||
|
final ListenableFuture<XmppConnection> connectedFuture =
|
||||||
|
ConnectionPool.getInstance(context).reconfigure(account).asConnectedFuture();
|
||||||
|
return Futures.transformAsync(
|
||||||
|
connectedFuture,
|
||||||
|
xc -> xc.getManager(RegistrationManager.class).getRegistration(),
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,10 +118,14 @@ import javax.net.ssl.SSLSocketFactory;
|
||||||
import javax.net.ssl.X509KeyManager;
|
import javax.net.ssl.X509KeyManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
public class XmppConnection implements Runnable {
|
public class XmppConnection implements Runnable {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(XmppConnection.class);
|
||||||
|
|
||||||
protected final Account account;
|
protected final Account account;
|
||||||
private final SparseArray<Stanza> mStanzaQueue = new SparseArray<>();
|
private final SparseArray<Stanza> mStanzaQueue = new SparseArray<>();
|
||||||
private final Hashtable<String, Pair<Iq, Consumer<Iq>>> packetCallbacks = new Hashtable<>();
|
private final Hashtable<String, Pair<Iq, Consumer<Iq>>> packetCallbacks = new Hashtable<>();
|
||||||
|
@ -289,9 +293,9 @@ public class XmppConnection implements Runnable {
|
||||||
try {
|
try {
|
||||||
Socket localSocket;
|
Socket localSocket;
|
||||||
shouldAuthenticate =
|
shouldAuthenticate =
|
||||||
!ConversationsDatabase.getInstance(context)
|
ConversationsDatabase.getInstance(context)
|
||||||
.accountDao()
|
.accountDao()
|
||||||
.pendingRegistration(account.id);
|
.loginAndBind(account.id);
|
||||||
this.changeStatus(ConnectionState.CONNECTING);
|
this.changeStatus(ConnectionState.CONNECTING);
|
||||||
// TODO introduce proxy check
|
// TODO introduce proxy check
|
||||||
final boolean useTor = /*fcontext.useTorToConnect() ||*/ account.isOnion();
|
final boolean useTor = /*fcontext.useTorToConnect() ||*/ account.isOnion();
|
||||||
|
@ -1177,7 +1181,7 @@ public class XmppConnection implements Runnable {
|
||||||
final SSLSocket sslSocket = upgradeSocketToTls(socket);
|
final SSLSocket sslSocket = upgradeSocketToTls(socket);
|
||||||
tagReader.setInputStream(sslSocket.getInputStream());
|
tagReader.setInputStream(sslSocket.getInputStream());
|
||||||
tagWriter.setOutputStream(sslSocket.getOutputStream());
|
tagWriter.setOutputStream(sslSocket.getOutputStream());
|
||||||
Log.d(Config.LOGTAG, account.address + ": TLS connection established");
|
LOGGER.info("TLS connection established");
|
||||||
final boolean quickStart;
|
final boolean quickStart;
|
||||||
try {
|
try {
|
||||||
quickStart = establishStream(SSLSockets.version(sslSocket));
|
quickStart = establishStream(SSLSockets.version(sslSocket));
|
||||||
|
@ -1234,13 +1238,10 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processStreamFeatures(final Tag currentTag) throws IOException {
|
private void processStreamFeatures(final Tag currentTag) throws IOException {
|
||||||
final boolean pendingRegistration =
|
final boolean loginAndBind =
|
||||||
ConversationsDatabase.getInstance(context)
|
ConversationsDatabase.getInstance(context).accountDao().loginAndBind(account.id);
|
||||||
.accountDao()
|
|
||||||
.pendingRegistration(account.id);
|
|
||||||
this.streamFeatures = tagReader.readElement(currentTag, Features.class);
|
this.streamFeatures = tagReader.readElement(currentTag, Features.class);
|
||||||
final boolean isSecure = isSecure();
|
final boolean needsBinding = !isBound && loginAndBind;
|
||||||
final boolean needsBinding = !isBound && !pendingRegistration;
|
|
||||||
if (this.quickStartInProgress) {
|
if (this.quickStartInProgress) {
|
||||||
if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) {
|
if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)) {
|
||||||
Log.d(
|
Log.d(
|
||||||
|
@ -1271,29 +1272,23 @@ public class XmppConnection implements Runnable {
|
||||||
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
|
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
|
||||||
}
|
}
|
||||||
if (this.streamFeatures.hasChild("starttls", Namespace.TLS) && !this.encryptionEnabled) {
|
if (this.streamFeatures.hasChild("starttls", Namespace.TLS) && !this.encryptionEnabled) {
|
||||||
|
LOGGER.info("Negotiating TLS (STARTTLS)");
|
||||||
sendStartTLS();
|
sendStartTLS();
|
||||||
} else if (this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE)
|
return;
|
||||||
&& pendingRegistration) {
|
} else if (!isSecure()) {
|
||||||
if (isSecure) {
|
LOGGER.error("Server does not support STARTTLS");
|
||||||
register();
|
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
|
||||||
} else {
|
}
|
||||||
Log.d(
|
|
||||||
Config.LOGTAG,
|
if (Boolean.FALSE.equals(loginAndBind)) {
|
||||||
account.address
|
LOGGER.info("No login and bind required. Connection is considered online");
|
||||||
+ ": unable to find STARTTLS for registration process "
|
this.lastPacketReceived = SystemClock.elapsedRealtime();
|
||||||
+ XmlHelper.printElementNames(this.streamFeatures));
|
this.changeStatus(ConnectionState.ONLINE);
|
||||||
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
|
|
||||||
}
|
|
||||||
} else if (!this.streamFeatures.hasChild("register", Namespace.REGISTER_STREAM_FEATURE)
|
|
||||||
&& pendingRegistration) {
|
|
||||||
throw new StateChangingException(ConnectionState.REGISTRATION_NOT_SUPPORTED);
|
|
||||||
} else if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)
|
} else if (this.streamFeatures.hasChild("authentication", Namespace.SASL_2)
|
||||||
&& shouldAuthenticate
|
&& shouldAuthenticate) {
|
||||||
&& isSecure) {
|
|
||||||
authenticate(SaslMechanism.Version.SASL_2);
|
authenticate(SaslMechanism.Version.SASL_2);
|
||||||
} else if (this.streamFeatures.hasChild("mechanisms", Namespace.SASL)
|
} else if (this.streamFeatures.hasChild("mechanisms", Namespace.SASL)
|
||||||
&& shouldAuthenticate
|
&& shouldAuthenticate) {
|
||||||
&& isSecure) {
|
|
||||||
authenticate(SaslMechanism.Version.SASL);
|
authenticate(SaslMechanism.Version.SASL);
|
||||||
} else if (this.streamFeatures.streamManagement() && streamId != null && !inSmacksSession) {
|
} else if (this.streamFeatures.streamManagement() && streamId != null && !inSmacksSession) {
|
||||||
if (Config.EXTENDED_SM_LOGGING) {
|
if (Config.EXTENDED_SM_LOGGING) {
|
||||||
|
@ -1306,7 +1301,7 @@ public class XmppConnection implements Runnable {
|
||||||
this.mWaitingForSmCatchup.set(true);
|
this.mWaitingForSmCatchup.set(true);
|
||||||
this.tagWriter.writeStanzaAsync(resume);
|
this.tagWriter.writeStanzaAsync(resume);
|
||||||
} else if (needsBinding) {
|
} else if (needsBinding) {
|
||||||
if (this.streamFeatures.hasChild("bind", Namespace.BIND) && isSecure) {
|
if (this.streamFeatures.hasChild("bind", Namespace.BIND)) {
|
||||||
sendBindRequest();
|
sendBindRequest();
|
||||||
} else {
|
} else {
|
||||||
Log.d(
|
Log.d(
|
||||||
|
@ -1516,7 +1511,7 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
final Iq preAuthRequest = new Iq(Iq.Type.SET);
|
final Iq preAuthRequest = new Iq(Iq.Type.SET);
|
||||||
preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth);
|
preAuthRequest.addChild("preauth", Namespace.PARS).setAttribute("token", preAuth);
|
||||||
sendUnmodifiedIqPacket(
|
sendIqPacketUnbound(
|
||||||
preAuthRequest,
|
preAuthRequest,
|
||||||
(response) -> {
|
(response) -> {
|
||||||
if (response.getType() == Iq.Type.RESULT) {
|
if (response.getType() == Iq.Type.RESULT) {
|
||||||
|
@ -1526,15 +1521,14 @@ public class XmppConnection implements Runnable {
|
||||||
Log.d(Config.LOGTAG, account.address + ": failed to pre auth. " + error);
|
Log.d(Config.LOGTAG, account.address + ": failed to pre auth. " + error);
|
||||||
throw new StateChangingError(ConnectionState.REGISTRATION_INVALID_TOKEN);
|
throw new StateChangingError(ConnectionState.REGISTRATION_INVALID_TOKEN);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendRegistryRequest() {
|
private void sendRegistryRequest() {
|
||||||
final Iq retrieveRegistration = new Iq(Iq.Type.GET);
|
final Iq retrieveRegistration = new Iq(Iq.Type.GET);
|
||||||
retrieveRegistration.addExtension(new Register());
|
retrieveRegistration.addExtension(new Register());
|
||||||
retrieveRegistration.setTo(account.address.getDomain());
|
retrieveRegistration.setTo(account.address.getDomain());
|
||||||
sendUnmodifiedIqPacket(
|
sendIqPacketUnbound(
|
||||||
retrieveRegistration,
|
retrieveRegistration,
|
||||||
(packet) -> {
|
(packet) -> {
|
||||||
if (packet.getType() == Iq.Type.TIMEOUT) {
|
if (packet.getType() == Iq.Type.TIMEOUT) {
|
||||||
|
@ -1562,8 +1556,7 @@ public class XmppConnection implements Runnable {
|
||||||
register.addChild(username);
|
register.addChild(username);
|
||||||
register.addChild(password);
|
register.addChild(password);
|
||||||
registrationRequest.setFrom(account.address);
|
registrationRequest.setFrom(account.address);
|
||||||
sendUnmodifiedIqPacket(
|
sendIqPacketUnbound(registrationRequest, this::handleRegistrationResponse);
|
||||||
registrationRequest, this::handleRegistrationResponse, true);
|
|
||||||
} else if (query.hasChild("x", Namespace.DATA)) {
|
} else if (query.hasChild("x", Namespace.DATA)) {
|
||||||
final Data data = Data.parse(query.findChild("x", Namespace.DATA));
|
final Data data = Data.parse(query.findChild("x", Namespace.DATA));
|
||||||
final Element blob = query.findChild("data", "urn:xmpp:bob");
|
final Element blob = query.findChild("data", "urn:xmpp:bob");
|
||||||
|
@ -1621,15 +1614,14 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
throw new StateChangingError(ConnectionState.REGISTRATION_FAILED);
|
throw new StateChangingError(ConnectionState.REGISTRATION_FAILED);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRegistrationResponse(final Iq packet) {
|
private void handleRegistrationResponse(final Iq packet) {
|
||||||
if (packet.getType() == Iq.Type.RESULT) {
|
if (packet.getType() == Iq.Type.RESULT) {
|
||||||
ConversationsDatabase.getInstance(context)
|
ConversationsDatabase.getInstance(context)
|
||||||
.accountDao()
|
.accountDao()
|
||||||
.setPendingRegistration(account.id, false);
|
.setLoginAndBind(account.id, true);
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
account.address + ": successfully registered new account on server");
|
account.address + ": successfully registered new account on server");
|
||||||
|
@ -1693,7 +1685,7 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
final Iq iq = new Iq(Iq.Type.SET);
|
final Iq iq = new Iq(Iq.Type.SET);
|
||||||
iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource);
|
iq.addChild("bind", Namespace.BIND).addChild("resource").setContent(resource);
|
||||||
this.sendUnmodifiedIqPacket(
|
this.sendIqPacketUnbound(
|
||||||
iq,
|
iq,
|
||||||
(packet) -> {
|
(packet) -> {
|
||||||
if (packet.getType() == Iq.Type.TIMEOUT) {
|
if (packet.getType() == Iq.Type.TIMEOUT) {
|
||||||
|
@ -1759,8 +1751,7 @@ public class XmppConnection implements Runnable {
|
||||||
+ ")");
|
+ ")");
|
||||||
}
|
}
|
||||||
throw new StateChangingError(ConnectionState.BIND_FAILURE);
|
throw new StateChangingError(ConnectionState.BIND_FAILURE);
|
||||||
},
|
});
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setConnectionAddress(final Jid jid) {
|
private void setConnectionAddress(final Jid jid) {
|
||||||
|
@ -1813,7 +1804,7 @@ public class XmppConnection implements Runnable {
|
||||||
Log.d(Config.LOGTAG, account.address + ": sending legacy session to outdated server");
|
Log.d(Config.LOGTAG, account.address + ": sending legacy session to outdated server");
|
||||||
final Iq startSession = new Iq(Iq.Type.SET);
|
final Iq startSession = new Iq(Iq.Type.SET);
|
||||||
startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session");
|
startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session");
|
||||||
this.sendUnmodifiedIqPacket(
|
this.sendIqPacketUnbound(
|
||||||
startSession,
|
startSession,
|
||||||
(packet) -> {
|
(packet) -> {
|
||||||
if (packet.getType() == Iq.Type.RESULT) {
|
if (packet.getType() == Iq.Type.RESULT) {
|
||||||
|
@ -1822,8 +1813,7 @@ public class XmppConnection implements Runnable {
|
||||||
} else if (packet.getType() != Iq.Type.TIMEOUT) {
|
} else if (packet.getType() != Iq.Type.TIMEOUT) {
|
||||||
throw new StateChangingError(ConnectionState.SESSION_FAILURE);
|
throw new StateChangingError(ConnectionState.SESSION_FAILURE);
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO the return value is not used any more
|
// TODO the return value is not used any more
|
||||||
|
@ -2009,6 +1999,14 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Iq> sendIqPacket(final Iq packet) {
|
public ListenableFuture<Iq> sendIqPacket(final Iq packet) {
|
||||||
|
return sendIqPacket(packet, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<Iq> sendIqPacketUnbound(final Iq packet) {
|
||||||
|
return sendIqPacket(packet, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<Iq> sendIqPacket(final Iq packet, final boolean sendToUnboundStream) {
|
||||||
final SettableFuture<Iq> future = SettableFuture.create();
|
final SettableFuture<Iq> future = SettableFuture.create();
|
||||||
sendIqPacket(
|
sendIqPacket(
|
||||||
packet,
|
packet,
|
||||||
|
@ -2021,17 +2019,21 @@ public class XmppConnection implements Runnable {
|
||||||
} else {
|
} else {
|
||||||
future.setException(new IqErrorException(result));
|
future.setException(new IqErrorException(result));
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
sendToUnboundStream);
|
||||||
return future;
|
return future;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String sendIqPacket(final Iq packet, final Consumer<Iq> callback) {
|
public void sendIqPacket(final Iq packet, final Consumer<Iq> callback) {
|
||||||
packet.setFrom(account.address);
|
this.sendIqPacket(packet, callback, false);
|
||||||
return this.sendUnmodifiedIqPacket(packet, callback, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized String sendUnmodifiedIqPacket(
|
public void sendIqPacketUnbound(final Iq packet, final Consumer<Iq> callback) {
|
||||||
final Iq packet, final Consumer<Iq> callback, boolean force) {
|
this.sendIqPacket(packet, callback, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void sendIqPacket(
|
||||||
|
final Iq packet, final Consumer<Iq> callback, final boolean sendToUnboundStream) {
|
||||||
if (Strings.isNullOrEmpty(packet.getId())) {
|
if (Strings.isNullOrEmpty(packet.getId())) {
|
||||||
packet.setId(IDs.medium());
|
packet.setId(IDs.medium());
|
||||||
}
|
}
|
||||||
|
@ -2040,8 +2042,7 @@ public class XmppConnection implements Runnable {
|
||||||
packetCallbacks.put(packet.getId(), new Pair<>(packet, callback));
|
packetCallbacks.put(packet.getId(), new Pair<>(packet, callback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.sendPacket(packet, force);
|
this.sendPacket(packet, sendToUnboundStream);
|
||||||
return packet.getId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendResultFor(final Iq request, final Extension... extensions) {
|
public void sendResultFor(final Iq request, final Extension... extensions) {
|
||||||
|
@ -2079,14 +2080,15 @@ public class XmppConnection implements Runnable {
|
||||||
sendPacket(packet, false);
|
sendPacket(packet, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void sendPacket(final StreamElement packet, final boolean force) {
|
private synchronized void sendPacket(
|
||||||
|
final StreamElement packet, final boolean sendToUnboundStream) {
|
||||||
if (stanzasSent == Integer.MAX_VALUE) {
|
if (stanzasSent == Integer.MAX_VALUE) {
|
||||||
resetStreamId();
|
resetStreamId();
|
||||||
disconnect(true);
|
disconnect(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
synchronized (this.mStanzaQueue) {
|
synchronized (this.mStanzaQueue) {
|
||||||
if (force || isBound) {
|
if (sendToUnboundStream || isBound) {
|
||||||
tagWriter.writeStanzaAsync(packet);
|
tagWriter.writeStanzaAsync(packet);
|
||||||
} else {
|
} else {
|
||||||
Log.d(
|
Log.d(
|
||||||
|
@ -2131,11 +2133,17 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendPing() {
|
public void sendPing() {
|
||||||
if (!r()) {
|
if (this.inSmacksSession) {
|
||||||
|
this.tagWriter.writeStanzaAsync(new Request());
|
||||||
|
} else {
|
||||||
final Iq iq = new Iq(Iq.Type.GET);
|
final Iq iq = new Iq(Iq.Type.GET);
|
||||||
iq.setFrom(account.address);
|
iq.setFrom(account.address);
|
||||||
iq.addExtension(new Ping());
|
iq.addExtension(new Ping());
|
||||||
this.sendIqPacket(iq, null);
|
this.sendIqPacket(
|
||||||
|
iq,
|
||||||
|
response -> {
|
||||||
|
LOGGER.info("Server responded to ping");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.lastPingSent = SystemClock.elapsedRealtime();
|
this.lastPingSent = SystemClock.elapsedRealtime();
|
||||||
}
|
}
|
||||||
|
@ -2213,15 +2221,6 @@ public class XmppConnection implements Runnable {
|
||||||
this.streamId = null;
|
this.streamId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean r() {
|
|
||||||
if (this.inSmacksSession) {
|
|
||||||
this.tagWriter.writeStanzaAsync(new Request());
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getTimeToNextAttempt() {
|
public int getTimeToNextAttempt() {
|
||||||
final int additionalTime =
|
final int additionalTime =
|
||||||
recentErrorConnectionState == ConnectionState.POLICY_VIOLATION ? 3 : 0;
|
recentErrorConnectionState == ConnectionState.POLICY_VIOLATION ? 3 : 0;
|
||||||
|
|
|
@ -10,16 +10,23 @@ import eu.siacs.conversations.xml.Namespace;
|
||||||
import im.conversations.android.xmpp.XmppConnection;
|
import im.conversations.android.xmpp.XmppConnection;
|
||||||
import im.conversations.android.xmpp.model.data.Data;
|
import im.conversations.android.xmpp.model.data.Data;
|
||||||
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
import im.conversations.android.xmpp.model.oob.OutOfBandData;
|
||||||
|
import im.conversations.android.xmpp.model.pars.PreAuth;
|
||||||
import im.conversations.android.xmpp.model.register.Instructions;
|
import im.conversations.android.xmpp.model.register.Instructions;
|
||||||
import im.conversations.android.xmpp.model.register.Password;
|
import im.conversations.android.xmpp.model.register.Password;
|
||||||
import im.conversations.android.xmpp.model.register.Register;
|
import im.conversations.android.xmpp.model.register.Register;
|
||||||
import im.conversations.android.xmpp.model.register.Remove;
|
import im.conversations.android.xmpp.model.register.Remove;
|
||||||
import im.conversations.android.xmpp.model.register.Username;
|
import im.conversations.android.xmpp.model.register.Username;
|
||||||
import im.conversations.android.xmpp.model.stanza.Iq;
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public class RegistrationManager extends AbstractManager {
|
public class RegistrationManager extends AbstractManager {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(RegistrationManager.class);
|
||||||
|
|
||||||
public RegistrationManager(Context context, XmppConnection connection) {
|
public RegistrationManager(Context context, XmppConnection connection) {
|
||||||
super(context, connection);
|
super(context, connection);
|
||||||
}
|
}
|
||||||
|
@ -27,6 +34,7 @@ public class RegistrationManager extends AbstractManager {
|
||||||
public ListenableFuture<Void> setPassword(final String password) {
|
public ListenableFuture<Void> setPassword(final String password) {
|
||||||
final var account = getAccount();
|
final var account = getAccount();
|
||||||
final var iq = new Iq(Iq.Type.SET);
|
final var iq = new Iq(Iq.Type.SET);
|
||||||
|
iq.setTo(account.address.getDomain());
|
||||||
final var register = iq.addExtension(new Register());
|
final var register = iq.addExtension(new Register());
|
||||||
register.addUsername(account.address.getEscapedLocal());
|
register.addUsername(account.address.getEscapedLocal());
|
||||||
register.addPassword(password);
|
register.addPassword(password);
|
||||||
|
@ -36,6 +44,7 @@ public class RegistrationManager extends AbstractManager {
|
||||||
|
|
||||||
public ListenableFuture<Void> unregister() {
|
public ListenableFuture<Void> unregister() {
|
||||||
final var iq = new Iq(Iq.Type.SET);
|
final var iq = new Iq(Iq.Type.SET);
|
||||||
|
iq.setTo(getAccount().address.getDomain());
|
||||||
final var register = iq.addExtension(new Register());
|
final var register = iq.addExtension(new Register());
|
||||||
register.addExtension(new Remove());
|
register.addExtension(new Remove());
|
||||||
return Futures.transform(
|
return Futures.transform(
|
||||||
|
@ -43,10 +52,11 @@ public class RegistrationManager extends AbstractManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ListenableFuture<Registration> getRegistration() {
|
public ListenableFuture<Registration> getRegistration() {
|
||||||
final var iq = new Iq(Iq.Type.SET);
|
final var iq = new Iq(Iq.Type.GET);
|
||||||
|
iq.setTo(getAccount().address.getDomain());
|
||||||
iq.addExtension(new Register());
|
iq.addExtension(new Register());
|
||||||
return Futures.transform(
|
return Futures.transform(
|
||||||
connection.sendIqPacket(iq),
|
connection.sendIqPacketUnbound(iq),
|
||||||
result -> {
|
result -> {
|
||||||
final var register = result.getExtension(Register.class);
|
final var register = result.getExtension(Register.class);
|
||||||
if (register == null) {
|
if (register == null) {
|
||||||
|
@ -58,7 +68,11 @@ public class RegistrationManager extends AbstractManager {
|
||||||
return new SimpleRegistration();
|
return new SimpleRegistration();
|
||||||
}
|
}
|
||||||
final var data = register.getExtension(Data.class);
|
final var data = register.getExtension(Data.class);
|
||||||
if (data != null && Namespace.REGISTER.equals(data.getFormType())) {
|
// note that the captcha namespace is incorrect here. That namespace is only
|
||||||
|
// used in message challenges. ejabberd uses the incorrect namespace though
|
||||||
|
if (data != null
|
||||||
|
&& Arrays.asList(Namespace.REGISTER, Namespace.CAPTCHA)
|
||||||
|
.contains(data.getFormType())) {
|
||||||
return new ExtendedRegistration(data);
|
return new ExtendedRegistration(data);
|
||||||
}
|
}
|
||||||
final var oob = register.getExtension(OutOfBandData.class);
|
final var oob = register.getExtension(OutOfBandData.class);
|
||||||
|
@ -67,14 +81,14 @@ public class RegistrationManager extends AbstractManager {
|
||||||
instructions == null ? null : instructions.getContent();
|
instructions == null ? null : instructions.getContent();
|
||||||
final String redirectUrl = oob == null ? null : oob.getURL();
|
final String redirectUrl = oob == null ? null : oob.getURL();
|
||||||
if (redirectUrl != null) {
|
if (redirectUrl != null) {
|
||||||
return new RedirectRegistration(redirectUrl);
|
return RedirectRegistration.ifValid(redirectUrl);
|
||||||
}
|
}
|
||||||
if (instructionsText != null) {
|
if (instructionsText != null) {
|
||||||
final Matcher matcher = Patterns.WEB_URL.matcher(instructionsText);
|
final Matcher matcher = Patterns.WEB_URL.matcher(instructionsText);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
final String instructionsUrl =
|
final String instructionsUrl =
|
||||||
instructionsText.substring(matcher.start(), matcher.end());
|
instructionsText.substring(matcher.start(), matcher.end());
|
||||||
return new RedirectRegistration(instructionsUrl);
|
return RedirectRegistration.ifValid(instructionsUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("No supported registration method found");
|
throw new IllegalStateException("No supported registration method found");
|
||||||
|
@ -82,7 +96,16 @@ public class RegistrationManager extends AbstractManager {
|
||||||
MoreExecutors.directExecutor());
|
MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract static class Registration {}
|
public ListenableFuture<Void> sendPreAuthentication(final String token) {
|
||||||
|
final var iq = new Iq(Iq.Type.GET);
|
||||||
|
iq.setTo(getAccount().address.getDomain());
|
||||||
|
final var preAuthentication = iq.addExtension(new PreAuth());
|
||||||
|
preAuthentication.setToken(token);
|
||||||
|
return Futures.transform(
|
||||||
|
connection.sendIqPacketUnbound(iq), result -> null, MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class Registration {}
|
||||||
|
|
||||||
// only requires Username + Password
|
// only requires Username + Password
|
||||||
public static class SimpleRegistration extends Registration {}
|
public static class SimpleRegistration extends Registration {}
|
||||||
|
@ -102,14 +125,23 @@ public class RegistrationManager extends AbstractManager {
|
||||||
|
|
||||||
// Redirection as show here: https://xmpp.org/extensions/xep-0077.html#redirect
|
// Redirection as show here: https://xmpp.org/extensions/xep-0077.html#redirect
|
||||||
public static class RedirectRegistration extends Registration {
|
public static class RedirectRegistration extends Registration {
|
||||||
private final String url;
|
private final HttpUrl url;
|
||||||
|
|
||||||
public RedirectRegistration(@NonNull final String url) {
|
private RedirectRegistration(@NonNull HttpUrl url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NonNull String getURL() {
|
public @NonNull HttpUrl getURL() {
|
||||||
return this.url;
|
return this.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static RedirectRegistration ifValid(final String url) {
|
||||||
|
final HttpUrl httpUrl = HttpUrl.parse(url);
|
||||||
|
if (httpUrl != null && httpUrl.isHttps()) {
|
||||||
|
return new RedirectRegistration(httpUrl);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"A URL found the registration instructions is not valid");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package im.conversations.android.xmpp.model.pars;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(namespace = Namespace.PARS)
|
||||||
|
public class PreAuth extends Extension {
|
||||||
|
|
||||||
|
public PreAuth() {
|
||||||
|
super(PreAuth.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(final String token) {
|
||||||
|
this.setAttribute("token", token);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,14 +24,6 @@ public class BindProcessor extends XmppConnection.Delegate implements Consumer<J
|
||||||
final var account = getAccount();
|
final var account = getAccount();
|
||||||
final var database = getDatabase();
|
final var database = getDatabase();
|
||||||
|
|
||||||
final boolean firstLogin =
|
|
||||||
database.accountDao().setLoggedInSuccessfully(account.id, true) > 0;
|
|
||||||
|
|
||||||
if (firstLogin) {
|
|
||||||
// TODO publish display name if this is the first attempt
|
|
||||||
// IIRC this is used when the display name is set from a certificate or something
|
|
||||||
}
|
|
||||||
|
|
||||||
database.presenceDao().deletePresences(account.id);
|
database.presenceDao().deletePresences(account.id);
|
||||||
|
|
||||||
getManager(RosterManager.class).fetch();
|
getManager(RosterManager.class).fetch();
|
||||||
|
|
Loading…
Reference in a new issue