Add support for XEP-0133: Service Administration

also extend Smack's integration test framework to use XEP-0133 as a
means for of throw away account creation.

Fixes SMACK-742
This commit is contained in:
Florian Schmaus 2016-11-27 21:14:44 +01:00
parent 1f1bc236fd
commit 274e5630c4
12 changed files with 414 additions and 56 deletions

View File

@ -18,6 +18,19 @@ $ gradle integrationTest -Dsinttest.service=my.xmppservice.org
Note that the service needs to have In-Band Registration (IBR) enabled. 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 Configuration
------------- -------------
@ -51,6 +64,8 @@ debug=true
| serviceTlsPin | TLS Pin (used by [java-pinning](https://github.com/Flowdalic/java-pinning)) | | serviceTlsPin | TLS Pin (used by [java-pinning](https://github.com/Flowdalic/java-pinning)) |
| securityMode | Either 'required' or 'disabled' | | securityMode | Either 'required' or 'disabled' |
| replyTimeout | In milliseconds | | 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 | | accountOneUsername | Username of the first XMPP account |
| accountOnePassword | Password of the first XMPP account | | accountOnePassword | Password of the first XMPP account |
| accountTwoUsername | Username of the second XMPP account | | accountTwoUsername | Username of the second XMPP account |

View File

@ -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. | | [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. | | [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 . | | 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. | 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. | | 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. | | 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. |

View File

@ -357,6 +357,36 @@ public class StringUtils {
return cs == null || isEmpty(cs); 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. * Returns true if the given CharSequence is empty.
* *

View File

@ -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<XMPPConnection, ServiceAdministrationManager> 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<EntityBareJid> userJidsToDelete = Collections.singleton(userJidToDelete);
deleteUser(userJidsToDelete);
}
public void deleteUser(Set<EntityBareJid> 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());
}
}

View File

@ -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;

View File

@ -326,6 +326,16 @@ public abstract class AdHocCommand {
return data.getStatus(); return data.getStatus();
} }
/**
* Check if this command has been completed successfully.
*
* @return <code>true</code> 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. * Sets the data of the current stage. This should not used.
* *

View File

@ -143,11 +143,18 @@ public class RemoteCommand extends AdHocCommand {
data.setForm(form.getDataFormToSend()); data.setForm(form.getDataFormToSend());
} }
AdHocCommandData responseData = (AdHocCommandData) connection.createPacketCollectorAndSend( AdHocCommandData responseData = null;
data).nextResultOrThrow(); 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 @Override

View File

@ -32,8 +32,10 @@ import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.filter.StanzaIdFilter; import org.jivesoftware.smack.filter.StanzaIdFilter;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
import org.jivesoftware.smackx.iqregister.packet.Registration; import org.jivesoftware.smackx.iqregister.packet.Registration;
import org.jxmpp.jid.parts.Localpart; 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. * @param accountCreationSupported true if the server supports In-Band Registration.
*/ */
// TODO: Remove this method and the accountCreationSupported boolean.
void setSupportsAccountCreation(boolean accountCreationSupported) { void setSupportsAccountCreation(boolean accountCreationSupported) {
this.accountCreationSupported = accountCreationSupported; this.accountCreationSupported = accountCreationSupported;
} }
@ -132,6 +135,8 @@ public final class AccountManager extends Manager {
* @throws InterruptedException * @throws InterruptedException
*/ */
public boolean supportsAccountCreation() throws NoResponseException, XMPPErrorException, NotConnectedException, 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 // Check if we already know that the server supports creating new accounts
if (accountCreationSupported) { if (accountCreationSupported) {
return true; return true;
@ -326,6 +331,18 @@ public final class AccountManager extends Manager {
createPacketCollectorAndSend(reg).nextResultOrThrow(); 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. * Gets the account registration info from the server.
* @throws XMPPErrorException * @throws XMPPErrorException

View File

@ -60,7 +60,7 @@ public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmack
try { try {
callback.connectionCallback(connection); callback.connectionCallback(connection);
} finally { } finally {
IntTestUtil.disconnectAndMaybeDelete(connection, true); IntTestUtil.disconnectAndMaybeDelete(connection, configuration);
} }
} }

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2016 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 final class Configuration {
public enum AccountRegistration {
disabled,
inBandRegistration,
serviceAdministration,
}
public final DomainBareJid service; public final DomainBareJid service;
public final String serviceTlsPin; public final String serviceTlsPin;
@ -43,7 +49,11 @@ public final class Configuration {
public final int replyTimeout; public final int replyTimeout;
public final boolean registerAccounts; public final AccountRegistration accountRegistration;
public final String adminAccountUsername;
public final String adminAccountPassword;
public final String accountOneUsername; public final String accountOneUsername;
@ -68,21 +78,27 @@ public final class Configuration {
private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout, private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout,
boolean debug, String accountOneUsername, String accountOnePassword, String accountTwoUsername, boolean debug, String accountOneUsername, String accountOnePassword, String accountTwoUsername,
String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests, String accountTwoPassword, String accountThreeUsername, String accountThreePassword, Set<String> enabledTests, Set<String> disabledTests,
Set<String> testPackages) { Set<String> testPackages, String adminAccountUsername, String adminAccountPassword) {
this.service = Objects.requireNonNull(service, this.service = Objects.requireNonNull(service,
"'service' must be set. Either via 'properties' files or via system property 'sinttest.service'."); "'service' must be set. Either via 'properties' files or via system property 'sinttest.service'.");
this.serviceTlsPin = serviceTlsPin; this.serviceTlsPin = serviceTlsPin;
this.securityMode = securityMode; this.securityMode = securityMode;
this.replyTimeout = replyTimeout; this.replyTimeout = replyTimeout;
this.debug = debug; this.debug = debug;
if (StringUtils.isNullOrEmpty(accountOneUsername) || StringUtils.isNullOrEmpty(accountOnePassword) if (StringUtils.isNotEmpty(adminAccountUsername, adminAccountPassword)) {
|| StringUtils.isNullOrEmpty(accountTwoUsername) accountRegistration = AccountRegistration.serviceAdministration;
|| StringUtils.isNullOrEmpty(accountTwoPassword)) { }
registerAccounts = true; else if (StringUtils.isNotEmpty(accountOneUsername, accountOnePassword, accountTwoUsername, accountTwoPassword,
accountThreeUsername, accountThreePassword)) {
accountRegistration = AccountRegistration.disabled;
} }
else { else {
registerAccounts = false; accountRegistration = AccountRegistration.inBandRegistration;
} }
this.adminAccountUsername = adminAccountUsername;
this.adminAccountPassword = adminAccountPassword;
this.accountOneUsername = accountOneUsername; this.accountOneUsername = accountOneUsername;
this.accountOnePassword = accountOnePassword; this.accountOnePassword = accountOnePassword;
this.accountTwoUsername = accountTwoUsername; this.accountTwoUsername = accountTwoUsername;
@ -94,6 +110,10 @@ public final class Configuration {
this.testPackages = testPackages; this.testPackages = testPackages;
} }
public boolean isAccountRegistrationPossible() {
return accountRegistration != AccountRegistration.disabled;
}
public static Builder builder() { public static Builder builder() {
return new Builder(); return new Builder();
} }
@ -108,6 +128,10 @@ public final class Configuration {
private int replyTimeout; private int replyTimeout;
private String adminAccountUsername;
private String adminAccountPassword;
private String accountOneUsername; private String accountOneUsername;
private String accountOnePassword; private String accountOnePassword;
@ -162,6 +186,12 @@ public final class Configuration {
return this; 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, public Builder setUsernamesAndPassword(String accountOneUsername, String accountOnePassword,
String accountTwoUsername, String accountTwoPassword, String accountThreeUsername, String accountThreePassword) { String accountTwoUsername, String accountTwoPassword, String accountThreeUsername, String accountThreePassword) {
this.accountOneUsername = StringUtils.requireNotNullOrEmpty(accountOneUsername, "accountOneUsername must not be null or empty"); this.accountOneUsername = StringUtils.requireNotNullOrEmpty(accountOneUsername, "accountOneUsername must not be null or empty");
@ -226,7 +256,7 @@ public final class Configuration {
public Configuration build() { public Configuration build() {
return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername, return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername,
accountOnePassword, accountTwoUsername, accountTwoPassword, accountThreeUsername, accountThreePassword, enabledTests, disabledTests, 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.setSecurityMode(properties.getProperty("securityMode"));
builder.setReplyTimeout(properties.getProperty("replyTimeout", "60000")); 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 accountOneUsername = properties.getProperty("accountOneUsername");
String accountOnePassword = properties.getProperty("accountOnePassword"); String accountOnePassword = properties.getProperty("accountOnePassword");
String accountTwoUsername = properties.getProperty("accountTwoUsername"); String accountTwoUsername = properties.getProperty("accountTwoUsername");

View File

@ -1,6 +1,6 @@
/** /**
* *
* Copyright 2015 Florian Schmaus * Copyright 2015-2016 Florian Schmaus
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException; import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.igniterealtime.smack.inttest.Configuration.AccountRegistration;
import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.admin.ServiceAdministrationManager;
import org.jivesoftware.smackx.iqregister.AccountManager; 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.jid.parts.Localpart;
import org.jxmpp.stringprep.XmppStringprepException; import org.jxmpp.stringprep.XmppStringprepException;
@ -39,14 +43,52 @@ public class IntTestUtil {
private static final Logger LOGGER = Logger.getLogger(IntTestUtil.class.getName()); 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, throws NoResponseException, XMPPErrorException, NotConnectedException,
InterruptedException { InterruptedException {
return registerAccount(connection, StringUtils.insecureRandomString(12), return registerAccountViaIbr(connection, StringUtils.insecureRandomString(12),
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, String password) throws NoResponseException, XMPPErrorException,
NotConnectedException, InterruptedException { NotConnectedException, InterruptedException {
AccountManager accountManager = AccountManager.getInstance(connection); 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 { throws InterruptedException {
// If the connection is disconnected, then re-reconnect and login. This could happen when // 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 // (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); LOGGER.log(Level.WARNING, "Exception reconnection account for deletion", e);
} }
} }
try {
if (delete) { final int maxAttempts = 3;
final int maxAttempts = 3; AccountManager am = AccountManager.getInstance(connection);
AccountManager am = AccountManager.getInstance(connection); int attempts;
int attempts; for (attempts = 0; attempts < maxAttempts; attempts++) {
for (attempts = 0; attempts < maxAttempts; attempts++) { try {
try { am.deleteAccount();
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);
}
} }
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 { if (attempts > maxAttempts) {
connection.disconnect(); LOGGER.log(Level.SEVERE, "Could not delete account for connection: " + connection);
} }
} }
} }

View File

@ -436,7 +436,7 @@ public class SmackIntegrationTestFramework {
final int numberOfConnections = testMethod.getParameterTypes().length; final int numberOfConnections = testMethod.getParameterTypes().length;
XMPPTCPConnection[] connections = null; XMPPTCPConnection[] connections = null;
try { try {
if (numberOfConnections > 0 && !config.registerAccounts) { if (numberOfConnections > 0 && !config.isAccountRegistrationPossible()) {
throw new TestNotPossibleException( throw new TestNotPossibleException(
"Must create accounts for this test, but it's not enabled"); "Must create accounts for this test, but it's not enabled");
} }
@ -457,13 +457,13 @@ public class SmackIntegrationTestFramework {
} }
finally { finally {
for (int i = 0; i < numberOfConnections; ++i) { for (int i = 0; i < numberOfConnections; ++i) {
IntTestUtil.disconnectAndMaybeDelete(connections[i], true); IntTestUtil.disconnectAndMaybeDelete(connections[i], config);
} }
} }
} }
protected void disconnectAndMaybeDelete(XMPPTCPConnection connection) throws InterruptedException { protected void disconnectAndMaybeDelete(XMPPTCPConnection connection) throws InterruptedException {
IntTestUtil.disconnectAndMaybeDelete(connection, config.registerAccounts); IntTestUtil.disconnectAndMaybeDelete(connection, config);
} }
protected SmackIntegrationTestEnvironment prepareEnvironment() throws SmackException, protected SmackIntegrationTestEnvironment prepareEnvironment() throws SmackException,
@ -546,8 +546,8 @@ public class SmackIntegrationTestFramework {
} }
XMPPTCPConnection connection = new XMPPTCPConnection(builder.build()); XMPPTCPConnection connection = new XMPPTCPConnection(builder.build());
connection.connect(); connection.connect();
if (config.registerAccounts) { if (config.isAccountRegistrationPossible()) {
IntTestUtil.registerAccount(connection, accountUsername, accountPassword); IntTestUtil.registerAccount(connection, accountUsername, accountPassword, config);
// TODO is this still required? // TODO is this still required?
// Some servers, e.g. Openfire, do not support a login right after the account was // 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); builder.setXmppDomain(config.service);
XMPPTCPConnection connection = new XMPPTCPConnection(builder.build()); XMPPTCPConnection connection = new XMPPTCPConnection(builder.build());
connection.connect(); connection.connect();
UsernameAndPassword uap = IntTestUtil.registerAccount(connection); UsernameAndPassword uap = IntTestUtil.registerAccount(connection, config);
connection.login(uap.username, uap.password); connection.login(uap.username, uap.password);
return connection; return connection;
} }