diff --git a/documentation/developer/integrationtest.md b/documentation/developer/integrationtest.md index 6a82e2481..89d0941b4 100644 --- a/documentation/developer/integrationtest.md +++ b/documentation/developer/integrationtest.md @@ -18,6 +18,19 @@ $ gradle integrationTest -Dsinttest.service=my.xmppservice.org Note that the service needs to have In-Band Registration (IBR) enabled. +A better alternative to IBR is using XEP-0133: Service Administration +to create the throw away accounts used by the integration test +framework. Simply use + +```bash +$ gradle integrationTest -Dsinttest.service=my.xmppservice.org \ + -Dsinttest.adminAccountUsername=admin \ + -Dsinttest.adminAccountPassword=aeR0Wuub +``` + +to run Smack's integration test framework against `my.xmppservice.org` +with an admin account named `admin` and `aeR0Wuub` as password. + Configuration ------------- @@ -51,6 +64,8 @@ debug=true | serviceTlsPin | TLS Pin (used by [java-pinning](https://github.com/Flowdalic/java-pinning)) | | securityMode | Either 'required' or 'disabled' | | replyTimeout | In milliseconds | +| adminAccountUsername | Username of the XEP-0133 Admin account | +| adminAccountPassword | Password of the XEP-0133 Admin account | | accountOneUsername | Username of the first XMPP account | | accountOnePassword | Password of the first XMPP account | | accountTwoUsername | Username of the second XMPP account | diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 50af12a65..dd2ca5dfb 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -57,6 +57,7 @@ Smack Extensions and currently supported XEPs of smack-extensions | [SI File Transfer](filetransfer.md) | [XEP-0096](http://xmpp.org/extensions/xep-0096.html) | Transfer files between two users over XMPP. | | [Entity Capabilities](caps.md) | [XEP-0115](http://xmpp.org/extensions/xep-0115.html) | Broadcasting and dynamic discovery of entity capabilities. | | Data Forms Validation | [XEP-0122](http://xmpp.org/extensions/xep-0122.html) | Enables an application to specify additional validation guidelines . | +| Service Administration | [XEP-0133](http://xmpp.org/extensions/xep-0133.html) | Recommended best practices for service-level administration of servers and components using Ad-Hoc Commands. | | Stream Compression | [XEP-0138](http://xmpp.org/extensions/xep-0138.html) | Support for optional compression of the XMPP stream. | Data Forms Layout | [XEP-0141](http://xmpp.org/extensions/xep-0141.html) | Enables an application to specify form layouts. | | Personal Eventing Protocol | [XEP-0163](http://xmpp.org/extensions/xep-0163.html) | Using the XMPP publish-subscribe protocol to broadcast state change events associated with an XMPP account. | diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index d8d4bc4a0..d0f9267f3 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java @@ -357,6 +357,36 @@ public class StringUtils { return cs == null || isEmpty(cs); } + /** + * Returns true if all given CharSequences are not empty. + * + * @param css the CharSequences to test. + * @return true if all given CharSequences are not empty. + */ + public static boolean isNotEmpty(CharSequence... css) { + for (CharSequence cs : css) { + if (StringUtils.isNullOrEmpty(cs)) { + return false; + } + } + return true; + } + + /** + * Returns true if all given CharSequences are either null or empty. + * + * @param css the CharSequences to test. + * @return true if all given CharSequences are null or empty. + */ + public static boolean isNullOrEmpty(CharSequence... css) { + for (CharSequence cs : css) { + if (StringUtils.isNotEmpty(cs)) { + return false; + } + } + return true; + } + /** * Returns true if the given CharSequence is empty. * diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java new file mode 100644 index 000000000..199906089 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java @@ -0,0 +1,117 @@ +/** + * + * Copyright 2016 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.admin; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.SmackException.NoResponseException; +import org.jivesoftware.smack.SmackException.NotConnectedException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException.XMPPErrorException; +import org.jivesoftware.smackx.commands.AdHocCommandManager; +import org.jivesoftware.smackx.commands.RemoteCommand; +import org.jivesoftware.smackx.xdata.Form; +import org.jivesoftware.smackx.xdata.FormField; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.Jid; +import org.jxmpp.jid.util.JidUtil; + +public class ServiceAdministrationManager extends Manager { + + public static final String COMMAND_NODE = "http://jabber.org/protocol/admin"; + + private static final String COMMAND_NODE_HASHSIGN = COMMAND_NODE + '#'; + + private static final Map INSTANCES = new WeakHashMap<>(); + + public static synchronized ServiceAdministrationManager getInstanceFor(XMPPConnection connection) { + ServiceAdministrationManager serviceAdministrationManager = INSTANCES.get(connection); + if (serviceAdministrationManager == null) { + serviceAdministrationManager = new ServiceAdministrationManager(connection); + INSTANCES.put(connection, serviceAdministrationManager); + } + return serviceAdministrationManager; + } + + private final AdHocCommandManager adHocCommandManager; + + public ServiceAdministrationManager(XMPPConnection connection) { + super(connection); + + adHocCommandManager = AdHocCommandManager.getAddHocCommandsManager(connection); + } + + public RemoteCommand addUser() { + return addUser(connection().getServiceName()); + } + + public RemoteCommand addUser(Jid service) { + return adHocCommandManager.getRemoteCommand(service, COMMAND_NODE_HASHSIGN + "add-user"); + } + + public void addUser(final EntityBareJid userJid, final String password) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + RemoteCommand command = addUser(); + command.execute(); + + Form answerForm = command.getForm().createAnswerForm(); + + FormField accountJidField = answerForm.getField("accountjid"); + accountJidField.addValue(userJid.toString()); + + FormField passwordField = answerForm.getField("password"); + passwordField.addValue(password); + + FormField passwordVerifyField = answerForm.getField("password-verify"); + passwordVerifyField.addValue(password); + + command.next(answerForm); + assert(command.isCompleted()); + } + + public RemoteCommand deleteUser() { + return deleteUser(connection().getServiceName()); + } + + public RemoteCommand deleteUser(Jid service) { + return adHocCommandManager.getRemoteCommand(service, COMMAND_NODE_HASHSIGN + "delete-user"); + } + + public void deleteUser(EntityBareJid userJidToDelete) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + Set userJidsToDelete = Collections.singleton(userJidToDelete); + deleteUser(userJidsToDelete); + } + + public void deleteUser(Set jidsToDelete) + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + RemoteCommand command = deleteUser(); + command.execute(); + + Form answerForm = command.getForm().createAnswerForm(); + + FormField accountJids = answerForm.getField("accountjids"); + accountJids.addValues(JidUtil.toStringList(jidsToDelete)); + + command.next(answerForm); + assert(command.isCompleted()); + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/package-info.java new file mode 100644 index 000000000..1cb93158d --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2015 Florian Schmaus + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Smack's API for XEP-0133: Service Administration. + */ +package org.jivesoftware.smackx.admin; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommand.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommand.java index feaba1328..e3f5331f6 100755 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommand.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommand.java @@ -326,6 +326,16 @@ public abstract class AdHocCommand { return data.getStatus(); } + /** + * Check if this command has been completed successfully. + * + * @return true if this command is completed. + * @since 4.2 + */ + public boolean isCompleted() { + return getStatus() == Status.completed; + } + /** * Sets the data of the current stage. This should not used. * diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/RemoteCommand.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/RemoteCommand.java index e438a1a15..7f99a5584 100755 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/RemoteCommand.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/RemoteCommand.java @@ -143,11 +143,18 @@ public class RemoteCommand extends AdHocCommand { data.setForm(form.getDataFormToSend()); } - AdHocCommandData responseData = (AdHocCommandData) connection.createPacketCollectorAndSend( - data).nextResultOrThrow(); + AdHocCommandData responseData = null; + try { + responseData = connection.createPacketCollectorAndSend(data).nextResultOrThrow(); + } + finally { + // We set the response data in a 'finally' block, so that it also gets set even if an error IQ was returned. + if (responseData != null) { + this.sessionID = responseData.getSessionID(); + super.setData(responseData); + } + } - this.sessionID = responseData.getSessionID(); - super.setData(responseData); } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/iqregister/AccountManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/iqregister/AccountManager.java index 8179a6444..cc4a6d7c2 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/iqregister/AccountManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/iqregister/AccountManager.java @@ -32,8 +32,10 @@ import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.filter.StanzaIdFilter; +import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.iqregister.packet.Registration; import org.jxmpp.jid.parts.Localpart; @@ -116,6 +118,7 @@ public final class AccountManager extends Manager { * * @param accountCreationSupported true if the server supports In-Band Registration. */ + // TODO: Remove this method and the accountCreationSupported boolean. void setSupportsAccountCreation(boolean accountCreationSupported) { this.accountCreationSupported = accountCreationSupported; } @@ -132,6 +135,8 @@ public final class AccountManager extends Manager { * @throws InterruptedException */ public boolean supportsAccountCreation() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + // TODO: Replace this body with isSupported() and possible deprecate this method. + // Check if we already know that the server supports creating new accounts if (accountCreationSupported) { return true; @@ -326,6 +331,18 @@ public final class AccountManager extends Manager { createPacketCollectorAndSend(reg).nextResultOrThrow(); } + public boolean isSupported() + throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { + XMPPConnection connection = connection(); + + ExtensionElement extensionElement = connection.getFeature(Registration.ELEMENT, Registration.NAMESPACE); + if (extensionElement != null) { + return true; + } + + return ServiceDiscoveryManager.getInstanceFor(connection).serverSupportsFeature(Registration.NAMESPACE); + } + /** * Gets the account registration info from the server. * @throws XMPPErrorException diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java index 13e89fa55..f6ffa63c7 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java @@ -60,7 +60,7 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack try { callback.connectionCallback(connection); } finally { - IntTestUtil.disconnectAndMaybeDelete(connection, true); + IntTestUtil.disconnectAndMaybeDelete(connection, configuration); } } diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java index abc9a4fd2..02210e2a0 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015 Florian Schmaus + * Copyright 2015-2016 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,12 @@ import org.jxmpp.stringprep.XmppStringprepException; public final class Configuration { + public enum AccountRegistration { + disabled, + inBandRegistration, + serviceAdministration, + } + public final DomainBareJid service; public final String serviceTlsPin; @@ -43,7 +49,11 @@ public final class Configuration { public final int replyTimeout; - public final boolean registerAccounts; + public final AccountRegistration accountRegistration; + + public final String adminAccountUsername; + + public final String adminAccountPassword; public final String accountOneUsername; @@ -68,21 +78,27 @@ public final class Configuration { private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout, boolean debug, String accountOneUsername, String accountOnePassword, String accountTwoUsername, String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set enabledTests, Set disabledTests, - Set testPackages) { + Set testPackages, String adminAccountUsername, String adminAccountPassword) { this.service = Objects.requireNonNull(service, "'service' must be set. Either via 'properties' files or via system property 'sinttest.service'."); this.serviceTlsPin = serviceTlsPin; this.securityMode = securityMode; this.replyTimeout = replyTimeout; this.debug = debug; - if (StringUtils.isNullOrEmpty(accountOneUsername) || StringUtils.isNullOrEmpty(accountOnePassword) - || StringUtils.isNullOrEmpty(accountTwoUsername) - || StringUtils.isNullOrEmpty(accountTwoPassword)) { - registerAccounts = true; + if (StringUtils.isNotEmpty(adminAccountUsername, adminAccountPassword)) { + accountRegistration = AccountRegistration.serviceAdministration; + } + else if (StringUtils.isNotEmpty(accountOneUsername, accountOnePassword, accountTwoUsername, accountTwoPassword, + accountThreeUsername, accountThreePassword)) { + accountRegistration = AccountRegistration.disabled; } else { - registerAccounts = false; + accountRegistration = AccountRegistration.inBandRegistration; } + + this.adminAccountUsername = adminAccountUsername; + this.adminAccountPassword = adminAccountPassword; + this.accountOneUsername = accountOneUsername; this.accountOnePassword = accountOnePassword; this.accountTwoUsername = accountTwoUsername; @@ -94,6 +110,10 @@ public final class Configuration { this.testPackages = testPackages; } + public boolean isAccountRegistrationPossible() { + return accountRegistration != AccountRegistration.disabled; + } + public static Builder builder() { return new Builder(); } @@ -108,6 +128,10 @@ public final class Configuration { private int replyTimeout; + private String adminAccountUsername; + + private String adminAccountPassword; + private String accountOneUsername; private String accountOnePassword; @@ -162,6 +186,12 @@ public final class Configuration { return this; } + public Builder setAdminAccountUsernameAndPassword(String adminAccountUsername, String adminAccountPassword) { + this.adminAccountUsername = StringUtils.requireNotNullOrEmpty(adminAccountUsername, "adminAccountUsername must not be null or empty"); + this.adminAccountPassword = StringUtils.requireNotNullOrEmpty(adminAccountPassword, "adminAccountPassword must no be null or empty"); + return this; + } + public Builder setUsernamesAndPassword(String accountOneUsername, String accountOnePassword, String accountTwoUsername, String accountTwoPassword, String accountThreeUsername, String accountThreePassword) { this.accountOneUsername = StringUtils.requireNotNullOrEmpty(accountOneUsername, "accountOneUsername must not be null or empty"); @@ -226,7 +256,7 @@ public final class Configuration { public Configuration build() { return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername, accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests, - testPackages); + testPackages, adminAccountUsername, adminAccountPassword); } } @@ -260,6 +290,12 @@ public final class Configuration { builder.setSecurityMode(properties.getProperty("securityMode")); builder.setReplyTimeout(properties.getProperty("replyTimeout", "60000")); + String adminAccountUsername = properties.getProperty("adminAccountUsername"); + String adminAccountPassword = properties.getProperty("adminAccountPassword"); + if (StringUtils.isNotEmpty(adminAccountUsername, adminAccountPassword)) { + builder.setAdminAccountUsernameAndPassword(adminAccountUsername, adminAccountPassword); + } + String accountOneUsername = properties.getProperty("accountOneUsername"); String accountOnePassword = properties.getProperty("accountOnePassword"); String accountTwoUsername = properties.getProperty("accountTwoUsername"); diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/IntTestUtil.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/IntTestUtil.java index 043efbbcc..1ea3e180d 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/IntTestUtil.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/IntTestUtil.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015 Florian Schmaus + * Copyright 2015-2016 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,13 +25,17 @@ import java.util.logging.Logger; import org.jivesoftware.smack.SmackException.NoResponseException; import org.jivesoftware.smack.SmackException.NotConnectedException; +import org.igniterealtime.smack.inttest.Configuration.AccountRegistration; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.admin.ServiceAdministrationManager; import org.jivesoftware.smackx.iqregister.AccountManager; +import org.jxmpp.jid.EntityBareJid; +import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Localpart; import org.jxmpp.stringprep.XmppStringprepException; @@ -39,14 +43,52 @@ public class IntTestUtil { private static final Logger LOGGER = Logger.getLogger(IntTestUtil.class.getName()); - public static UsernameAndPassword registerAccount(XMPPConnection connection) + + public static UsernameAndPassword registerAccount(XMPPTCPConnection connection, Configuration config) throws InterruptedException, XMPPException, SmackException, IOException { + return registerAccount(connection, StringUtils.insecureRandomString(12), StringUtils.insecureRandomString(12), config); + } + + public static UsernameAndPassword registerAccount(XMPPTCPConnection connection, String accountUsername, String accountPassword, + Configuration config) throws InterruptedException, XMPPException, SmackException, IOException { + switch (config.accountRegistration) { + case inBandRegistration: + return registerAccountViaIbr(connection, accountUsername, accountPassword); + case serviceAdministration: + return registerAccountViaAdmin(connection, accountUsername, accountPassword, config.adminAccountUsername, config.adminAccountPassword); + default: + throw new AssertionError(); + } + } + +// public static UsernameAndPassword registerAccountViaAdmin(XMPPTCPConnection connection) throws XmppStringprepException, NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { +// return registerAccountViaAdmin(connection, StringUtils.insecureRandomString(12), +// StringUtils.insecureRandomString(12)); +// } + + public static UsernameAndPassword registerAccountViaAdmin(XMPPTCPConnection connection, String username, + String password, String adminAccountUsername, String adminAccountPassword) throws InterruptedException, XMPPException, SmackException, IOException { + connection.login(adminAccountUsername, adminAccountPassword); + + ServiceAdministrationManager adminManager = ServiceAdministrationManager.getInstanceFor(connection); + + EntityBareJid userJid = JidCreate.entityBareFrom(Localpart.from(username), connection.getServiceName()); + adminManager.addUser(userJid, password); + + connection.disconnect(); + connection.connect(); + + return new UsernameAndPassword(username, password); + + } + + public static UsernameAndPassword registerAccountViaIbr(XMPPConnection connection) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { - return registerAccount(connection, StringUtils.insecureRandomString(12), + return registerAccountViaIbr(connection, StringUtils.insecureRandomString(12), StringUtils.insecureRandomString(12)); } - public static UsernameAndPassword registerAccount(XMPPConnection connection, String username, + public static UsernameAndPassword registerAccountViaIbr(XMPPConnection connection, String username, String password) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { AccountManager accountManager = AccountManager.getInstance(connection); @@ -82,7 +124,73 @@ public class IntTestUtil { } } - public static void disconnectAndMaybeDelete(XMPPTCPConnection connection, boolean delete) + + public static void disconnectAndMaybeDelete(XMPPTCPConnection connection, Configuration config) throws InterruptedException { + try { + if (!config.isAccountRegistrationPossible()) { + return; + } + + Configuration.AccountRegistration accountDeletionMethod = config.accountRegistration; + + AccountManager accountManager = AccountManager.getInstance(connection); + try { + if (accountManager.isSupported()) { + accountDeletionMethod = AccountRegistration.inBandRegistration; + } + } + catch (NoResponseException | XMPPErrorException | NotConnectedException e) { + LOGGER.log(Level.WARNING, "Could not test if XEP-0077 account deletion is possible", e); + } + + switch (accountDeletionMethod) { + case inBandRegistration: + deleteViaIbr(connection); + break; + case serviceAdministration: + deleteViaServiceAdministration(connection, config); + break; + default: + throw new AssertionError(); + } + } + finally { + connection.disconnect(); + } + } + + public static void deleteViaServiceAdministration(XMPPTCPConnection connection, Configuration config) { + EntityBareJid accountToDelete = connection.getUser().asEntityBareJid(); + + final int maxAttempts = 3; + + int attempts; + for (attempts = 0; attempts < maxAttempts; attempts++) { + connection.disconnect(); + + try { + connection.connect().login(config.adminAccountUsername, config.adminAccountPassword); + } + catch (XMPPException | SmackException | IOException | InterruptedException e) { + LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e); + continue; + } + + ServiceAdministrationManager adminManager = ServiceAdministrationManager.getInstanceFor(connection); + try { + adminManager.deleteUser(accountToDelete); + } + catch (NoResponseException | XMPPErrorException | NotConnectedException | InterruptedException e) { + LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e); + continue; + } + } + if (attempts > maxAttempts) { + LOGGER.log(Level.SEVERE, "Could not delete account for connection: " + connection); + } + } + + public static void deleteViaIbr(XMPPTCPConnection connection) throws InterruptedException { // If the connection is disconnected, then re-reconnect and login. This could happen when // (low-level) integration tests disconnect the connection, e.g. to test disconnection @@ -95,39 +203,35 @@ public class IntTestUtil { LOGGER.log(Level.WARNING, "Exception reconnection account for deletion", e); } } - try { - if (delete) { - final int maxAttempts = 3; - AccountManager am = AccountManager.getInstance(connection); - int attempts; - for (attempts = 0; attempts < maxAttempts; attempts++) { - try { - am.deleteAccount(); - } - catch (XMPPErrorException | NoResponseException e) { - LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e); - continue; - } - catch (NotConnectedException e) { - LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e); - try { - connection.connect().login(); - } - catch (XMPPException | SmackException | IOException e2) { - LOGGER.log(Level.WARNING, "Exception while trying to re-connect " + connection, e); - } - continue; - } - LOGGER.info("Successfully deleted account of " + connection); - break; - } - if (attempts > maxAttempts) { - LOGGER.log(Level.SEVERE, "Could not delete account for connection: " + connection); - } + + final int maxAttempts = 3; + AccountManager am = AccountManager.getInstance(connection); + int attempts; + for (attempts = 0; attempts < maxAttempts; attempts++) { + try { + am.deleteAccount(); } + catch (XMPPErrorException | NoResponseException e) { + LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e); + continue; + } + catch (NotConnectedException e) { + LOGGER.log(Level.WARNING, "Exception deleting account for " + connection, e); + try { + connection.connect().login(); + } + catch (XMPPException | SmackException | IOException e2) { + LOGGER.log(Level.WARNING, "Exception while trying to re-connect " + connection, e); + } + continue; + } + LOGGER.info("Successfully deleted account of " + connection); + break; } - finally { - connection.disconnect(); + if (attempts > maxAttempts) { + LOGGER.log(Level.SEVERE, "Could not delete account for connection: " + connection); } + } + } diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java index cc26d751b..5ce4afc95 100644 --- a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java +++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java @@ -436,7 +436,7 @@ public class SmackIntegrationTestFramework { final int numberOfConnections = testMethod.getParameterTypes().length; XMPPTCPConnection[] connections = null; try { - if (numberOfConnections > 0 && !config.registerAccounts) { + if (numberOfConnections > 0 && !config.isAccountRegistrationPossible()) { throw new TestNotPossibleException( "Must create accounts for this test, but it's not enabled"); } @@ -457,13 +457,13 @@ public class SmackIntegrationTestFramework { } finally { for (int i = 0; i < numberOfConnections; ++i) { - IntTestUtil.disconnectAndMaybeDelete(connections[i], true); + IntTestUtil.disconnectAndMaybeDelete(connections[i], config); } } } protected void disconnectAndMaybeDelete(XMPPTCPConnection connection) throws InterruptedException { - IntTestUtil.disconnectAndMaybeDelete(connection, config.registerAccounts); + IntTestUtil.disconnectAndMaybeDelete(connection, config); } protected SmackIntegrationTestEnvironment prepareEnvironment() throws SmackException, @@ -546,8 +546,8 @@ public class SmackIntegrationTestFramework { } XMPPTCPConnection connection = new XMPPTCPConnection(builder.build()); connection.connect(); - if (config.registerAccounts) { - IntTestUtil.registerAccount(connection, accountUsername, accountPassword); + if (config.isAccountRegistrationPossible()) { + IntTestUtil.registerAccount(connection, accountUsername, accountPassword, config); // TODO is this still required? // Some servers, e.g. Openfire, do not support a login right after the account was @@ -573,7 +573,7 @@ public class SmackIntegrationTestFramework { builder.setXmppDomain(config.service); XMPPTCPConnection connection = new XMPPTCPConnection(builder.build()); connection.connect(); - UsernameAndPassword uap = IntTestUtil.registerAccount(connection); + UsernameAndPassword uap = IntTestUtil.registerAccount(connection, config); connection.login(uap.username, uap.password); return connection; }