Commit 2d5c65c3 authored by wroot's avatar wroot

Merge remote-tracking branch 'igniterealtime/master'

parents 4904a1ac b1fa4367
......@@ -9,7 +9,7 @@ Homepage: http://www.igniterealtime.org
Package: openfire
Section: net
Priority: optional
Pre-Depends: default-jre-headless (>= 1.7)
Pre-Depends: default-jre-headless (>= 1.7) | java7-runtime-headless | java7-runtime | java8-runtime-headless | java8-runtime | java9-runtime-headless | java9-runtime
Architecture: all
Description: A high performance XMPP (Jabber) server.
Openfire is an instant messaging server that implements the XMPP
......
......@@ -120,10 +120,6 @@
<classpathentry kind="lib" path="src/plugins/registration/lib/recaptcha4j.jar"/>
<classpathentry kind="lib" path="src/plugins/sip/lib/JainSipApi1.1.jar"/>
<classpathentry kind="lib" path="src/plugins/sip/lib/nist-sip-1.2.jar"/>
<classpathentry kind="lib" path="src/plugins/userImportExport/lib/isorelax.jar"/>
<classpathentry kind="lib" path="src/plugins/userImportExport/lib/msv.jar"/>
<classpathentry kind="lib" path="src/plugins/userImportExport/lib/relaxngDatatype.jar"/>
<classpathentry kind="lib" path="src/plugins/userImportExport/lib/xsdlib.jar"/>
<classpathentry kind="lib" path="src/test/throttletest/build/lib/smack.jar"/>
<classpathentry kind="lib" path="src/test/throttletest/build/lib/smackx.jar"/>
<classpathentry kind="lib" path="src/web/WEB-INF/lib/commons-fileupload.jar"/>
......@@ -178,5 +174,6 @@
<classpathentry kind="lib" path="src/plugins/jmxweb/lib/quartz-2.2.2.jar"/>
<classpathentry kind="lib" path="src/plugins/jmxweb/lib/genson-1.4.jar"/>
<classpathentry kind="lib" path="src/plugins/jmxweb/lib/itextpdf-5.4.1.jar"/>
<classpathentry kind="lib" path="build/lib/merge/jsmpp.jar"/>
<classpathentry kind="output" path="work/classes"/>
</classpath>
......@@ -467,7 +467,7 @@
</uninstallerStartup>
</installerGui>
<mediaSets>
<win32 name="Windows" id="3" mediaFileName="" installDir="${compiler:WINDOWS_INSTALL_DIR}" overridePrincipalLanguage="true" requires64bit="false" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" includedJRE="windows-x86-1.8.0_102" manualJREEntry="false" bundleType="1" jreURL="" jreFtpURL="" jreShared="false" customInstallBaseDir="" createUninstallIcon="true" contentFilesType="1" downloadURL="" runAsAdmin="false">
<win32 name="Windows" id="3" mediaFileName="" installDir="${compiler:WINDOWS_INSTALL_DIR}" overridePrincipalLanguage="true" requires64bit="false" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" includedJRE="windows-x86-1.8.0_112" manualJREEntry="false" bundleType="1" jreURL="" jreFtpURL="" jreShared="false" customInstallBaseDir="" createUninstallIcon="true" contentFilesType="1" downloadURL="" runAsAdmin="false">
<excludedLaunchers>
<launcher id="22" />
</excludedLaunchers>
......@@ -484,7 +484,7 @@
<excludedInstallerScreens />
<excludedUninstallerScreens />
</win32>
<linuxRPM name="Linux RPM" id="18" mediaFileName="" installDir="/opt/${compiler:UNIX_INSTALL_DIR}" overridePrincipalLanguage="true" requires64bit="false" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" includedJRE="linux-x86-1.8.0_102" manualJREEntry="false" os="linux" arch="i386">
<linuxRPM name="Linux RPM" id="18" mediaFileName="" installDir="/opt/${compiler:UNIX_INSTALL_DIR}" overridePrincipalLanguage="true" requires64bit="false" runPostProcessor="false" postProcessor="" failOnPostProcessorError="false" includedJRE="linux-x86-1.8.0_112" manualJREEntry="false" os="linux" arch="i386">
<excludedLaunchers>
<launcher id="2" />
<launcher id="12" />
......
......@@ -10,6 +10,7 @@ cglib.jar | 2.1.3 (JMock 2.1.0)
commons-lang.jar | 2.3 | Apache 2.0
commons-httpclient.jar | 3.1 | Apache 2.0
commons-codec.jar | 1.9 | Apache 2.0
commons-pool2.jar | 2.4.2 | Apache 2.0
dom4j.jar | 1.6.1 | BSD (http://www.dom4j.org/dom4j-1.6.1/license.html)
concurrentlinkedhashmap-lru | concurrentlinkedhashmap-lru-1.0_jdk5 | Apache 2.0
dbutil.jar | Jive Code, no release version. | GPL
......@@ -54,6 +55,7 @@ jmdns.jar | PRE 1.0, patched
jmock.jar | 2.1.0 |
jmock-junit4.jar | 2.1.0 |
jmock-legacy.jar | 2.1.0 |
jsmpp | 2.2.4 | Apache 2.0
jtds.jar | 1.3.1 | LGPL
junit.jar | 4.11 | EPL 1.0
hamcrest-core.jar | 1.3 (required by junit) | new BSD licence
......
......@@ -7,6 +7,11 @@ Source0: %{OPENFIRE_SOURCE}
%ifnarch noarch
Source1: %{JRE_BUNDLE}
%endif
%ifarch noarch
# Note that epoch is set here to 1, this appears to be consistent with non-Redhat
# jres as well due to an ancient problem with java-1.5.0-ibm jpackage RPM
Requires: java-headless >= 1:1.7.0
%endif
Group: Applications/Communications
Vendor: Igniterealtime Community
Packager: Igniterealtime Community
......
......@@ -150,6 +150,28 @@
&lt;/jive&gt;
</pre>
<p>Another option is to use an AdminProvider. AdminProvider instances are responsible for listing
the administrators users dynamically. The default use the <tt>authorizedUsernames</tt> setting
previously explained. JDBCAdminProvider allows to list the administrators from a SQL query.
For example:</p>
<pre>
&lt;jive&gt;
...
&lt;provider&gt;
...
&lt;admin&gt;
&lt;className&gt;org.jivesoftware.openfire.admin.JDBCAdminProvider&lt;/className&gt;
&lt;/admin&gt;
...
&lt;/provider&gt;
&lt;jdbcAdminProvider&gt;
&lt;getAdminsSQL&gt;SELECT userid FROM user_account WHERE administrator='Y'&lt;/getAdminsSQL&gt;
&lt;/jdbcAdminProvider&gt;
...
&lt;/jive&gt;
</pre>
<h3>User Integration</h3>
<p>Optionally, Openfire can load user data from your custom database. If you enable user integration
......
......@@ -290,7 +290,7 @@ muc.form.conf.anyone=Jeder
muc.form.conf.owner_enablelogging=Raumkonversationen mitschreiben
muc.form.conf.owner_reservednick=Nur Teilnehmer mit registrierten Nicknames Zutritt gew\u00e4hren
muc.form.conf.owner_canchangenick=Teilnehmern erlauben Ihren Nickname zu \u00e4ndern
muc.form.conf.owner_registration=Benuter erlauben sich beim Raum zu registrieren
muc.form.conf.owner_registration=Benutzer erlauben sich beim Raum zu registrieren
muc.form.conf.roomadminsfixed=Administratoren f\u00fcr diesen Raum k\u00f6nnen angegeben werden. \
Bitte eine JID pro Zeile angeben.
muc.form.conf.owner_roomadmins=Raumadministratoren
......
......@@ -496,6 +496,8 @@ tab.server.descr=Click to manage server settings
sidebar.manage-updates.descr=Click to manage server or plugins updates
sidebar.server-email=Email Settings
sidebar.server-email.descr=Click to configure email settings
sidebar.server-sms=SMS Settings
sidebar.server-sms.descr=Click to configure SMS settings
sidebar.security-audit-viewer=Security Audit Viewer
sidebar.security-audit-viewer.descr=Click to view the security audit logs
sidebar.sidebar-server-settings=Server Settings
......@@ -2614,7 +2616,7 @@ system.email.info=Use the form below to set the host and port of your email serv
on your mail server. Note, if you choose to enable mail debugging the debug output will be written \
to your appserver&#39;s standard out log.
system.email.update_success=SMTP settings updated successfully.
system.email.update_failure=An error occured. Please verify that you have filled out all required fields \
system.email.update_failure=An error occurred. Please verify that you have filled out all required fields \
correctly and try again.
system.email.name=SMTP Settings
system.email.mail_host=Mail Host
......@@ -2650,6 +2652,43 @@ system.emailtest.body=Body
system.emailtest.send=Send
system.emailtest.cancel=Cancel/Go Back
# System SMS
system.sms.title=SMS Settings
system.sms.info=Use the form below to configure the connection to your SMS server. The SMPP protocol will be used to \
establish a connection. At a minimum, you should provide the host address of the SMPP server, your systemId \
(which can be thought of as your username) the corresponding password and an optional system type, all of \
which should be supplied to you by your SMPP service provider.
system.sms.update_success=Settings updated successfully.
system.sms.config_failure=Configuration failure. Please verify that you have filled out all required fields \
correctly and try again.
system.sms.name=SMPP Settings
system.sms.host=Host Address
system.sms.valid_host=Please enter a valid host address.
system.sms.port=Port (Optional)
system.sms.valid_port=Please enter a valid port number (between 1 and 65535).
system.sms.systemId=SystemId (username)
system.sms.valid_systemId=Please enter a valid systemId value.
system.sms.password=Password
system.sms.valid_password=Please enter a valid password.
system.sms.systemType=System Type (Optional)
system.sms.valid_systemType=Please enter a valid system type.
system.sms.save=Save Changes
system.sms.send_test=Send Test SMS...
system.smstest.title=SMS Settings
system.smstest.success=SMS was successfully sent. Verify it was sent by checking the SMS inbox of the recipient.
system.smstest.failure=An error occurred while trying to send the SMS message. The error message: <i>{0}</i>.
system.smstest.info=Use the form below to send a test message. Note that SMS does not guarantee speedy delivery, but \
generally, your message should arrive within moments.
system.smstest.invalid-service-config=The SMS service has not been configured properly. Please go back to the {0}SMS \
settings page{1} and address all issues presented there.
system.smstest.recipient=Recipient
system.smstest.valid_recipient=Please enter a recipient (phone number)
system.smstest.message=Message
system.smstest.valid_message=Please provide a short message.
system.smstest.send=Send
system.smstest.cancel=Cancel/Go Back
# File Transfer Proxy
filetransferproxy.settings.title=File Transfer Proxy Settings
......
/**
* $Revision$
* $Date$
*
* Copyright (C) 2005-2008 Jive Software. All rights reserved.
* Copyright 2008-2016 Robert Marcano
*
* 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.openfire.admin;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* The JDBC admin provider allows you to use an external database to define the administrators
* users. It is best used with the JDBCAuthProvider & JDBCGroupProvider to provide integration
* between your external system and Openfire. All data is treated as read-only so any
* set operations will result in an exception.<p/>
*
* To enable this provider, set the following in the system properties:<p/>
*
* <ul>
* <li><tt>provider.admin.className = org.jivesoftware.openfire.admin.JDBCAdminProvider</tt></li>
* </ul>
*
* Then you need to set your driver, connection string and SQL statements:
* <p/>
* <ul>
* <li><tt>jdbcProvider.driver = com.mysql.jdbc.Driver</tt></li>
* <li><tt>jdbcProvider.connectionString = jdbc:mysql://localhost/dbname?user=username&amp;password=secret</tt></li>
* <li><tt>jdbcAdminProvider.getAdminsSQL = SELECT user FROM myAdmins</tt></li>
* </ul>
*
* In order to use the configured JDBC connection provider do not use a JDBC
* connection string, set the following property
*
* <ul>
* <li><tt>jdbcAdminProvider.useConnectionProvider = true</tt></li>
* </ul>
*
*
* @author Robert Marcano
*/
public class JDBCAdminProvider implements AdminProvider {
private static final Logger Log = LoggerFactory.getLogger(JDBCAdminProvider.class);
private final String getAdminsSQL;
private final String xmppDomain;
private final boolean useConnectionProvider;
private String connectionString;
/**
* Constructs a new JDBC admin provider.
*/
public JDBCAdminProvider() {
// Convert XML based provider setup to Database based
JiveGlobals.migrateProperty("jdbcProvider.driver");
JiveGlobals.migrateProperty("jdbcProvider.connectionString");
JiveGlobals.migrateProperty("jdbcAdminProvider.getAdminsSQL");
xmppDomain = JiveGlobals.getProperty("xmpp.domain");
useConnectionProvider = JiveGlobals.getBooleanProperty("jdbcAdminProvider.useConnectionProvider");
// Load database statement for reading admin list
getAdminsSQL = JiveGlobals.getProperty("jdbcAdminProvider.getAdminsSQL");
// Load the JDBC driver and connection string
if (!useConnectionProvider) {
String jdbcDriver = JiveGlobals.getProperty("jdbcProvider.driver");
try {
Class.forName(jdbcDriver).newInstance();
}
catch (Exception e) {
Log.error("Unable to load JDBC driver: " + jdbcDriver, e);
return;
}
connectionString = JiveGlobals.getProperty("jdbcProvider.connectionString");
}
}
@Override
public List<JID> getAdmins() {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<JID> jids = new ArrayList<>();
try {
con = getConnection();
pstmt = con.prepareStatement(getAdminsSQL);
rs = pstmt.executeQuery();
while (rs.next()) {
String name = rs.getString(1);
jids.add(new JID(name + "@" + xmppDomain));
}
return jids;
}
catch (SQLException e) {
throw new RuntimeException(e);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
@Override
public void setAdmins(List<JID> admins) {
// Reject the operation since the provider is read-only
throw new UnsupportedOperationException();
}
@Override
public boolean isReadOnly() {
return true;
}
private Connection getConnection() throws SQLException {
if (useConnectionProvider) {
return DbConnectionManager.getConnection();
}
return DriverManager.getConnection(connectionString);
}
}
......@@ -154,7 +154,7 @@ public class PluginMonitor
if ( Files.exists( dir ) && Files.getLastModifiedTime( jarFile ).toMillis() > Files.getLastModifiedTime( dir ).toMillis() )
{
// If this is the first time that the monitor process is running, then plugins won't be loaded yet. Therefore, just delete the directory.
if ( pluginManager.isExecuted() )
if ( !pluginManager.isExecuted() )
{
int count = 0;
// Attempt to delete the folder for up to 5 seconds.
......
......@@ -51,7 +51,7 @@ public class IQPrivacyHandler extends IQHandler
private IQHandlerInfo info;
private PrivacyListManager manager = PrivacyListManager.getInstance();
private PrivacyListProvider provider = new PrivacyListProvider();
private PrivacyListProvider provider = PrivacyListProvider.getInstance();
public IQPrivacyHandler() {
super("Blocking Communication Handler");
......
......@@ -542,6 +542,7 @@ public final class HttpBindManager {
initializers.add(new ContainerInitializer(new JasperInitializer(), null));
context.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
context.setAllowNullPathInfo(true);
context.addServlet(new ServletHolder(new HttpBindServlet()),"/*");
if (isHttpCompressionEnabled()) {
Filter gzipFilter = new AsyncGzipFilter() {
......@@ -573,6 +574,7 @@ public final class HttpBindManager {
initializers.add(new ContainerInitializer(new JasperInitializer(), null));
context.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
context.setAllowNullPathInfo(true);
context.addServlet(new ServletHolder(new FlashCrossDomainServlet()),"");
}
......
......@@ -249,7 +249,7 @@ public class LockOutManager {
// If group wan't found in cache, load it up and put it there.
if (flag == null) {
flag = provider.getDisabledStatus(username);
lockOutCache.put(username, flag);
if (flag != null) lockOutCache.put(username, flag);
}
}
}
......
......@@ -162,16 +162,7 @@ public class LocalMUCRole implements MUCRole {
}
this.presence = newPresence;
this.presence.setFrom(getRoleAddress());
if (extendedInformation != null) {
// Remove any previous extendedInformation, then re-add it.
Element mucUser = presence.getElement().element(QName.get("x", "http://jabber.org/protocol/muc#user"));
if (mucUser != null) {
// Remove any previous extendedInformation, then re-add it.
presence.getElement().remove(mucUser);
}
Element exi = extendedInformation.createCopy();
presence.getElement().add(exi);
}
updatePresence();
}
@Override
......@@ -297,8 +288,21 @@ public class LocalMUCRole implements MUCRole {
ElementUtil.setProperty(extendedInformation, "x.item:jid", user.getAddress().toString());
ElementUtil.setProperty(extendedInformation, "x.item:affiliation", affiliation.toString());
ElementUtil.setProperty(extendedInformation, "x.item:role", role.toString());
updatePresence();
}
private void updatePresence() {
if (extendedInformation != null && presence != null) {
// Remove any previous extendedInformation, then re-add it.
Element mucUser = presence.getElement().element(QName.get("x", "http://jabber.org/protocol/muc#user"));
if (mucUser != null) {
// Remove any previous extendedInformation, then re-add it.
presence.getElement().remove(mucUser);
}
Element exi = extendedInformation.createCopy();
presence.getElement().add(exi);
}
}
@Override
public int hashCode() {
......
......@@ -1393,8 +1393,6 @@ public class LocalMUCRoom implements MUCRoom, GroupEventListener {
if (role.isLocal()) {
role.setAffiliation(newAffiliation);
role.setRole(newRole);
// Set the new presence, so that the updated affiliation and role is reflected in the presence stanza.
role.setPresence(role.getPresence());
// Notify the other cluster nodes to update the occupant
CacheFactory.doClusterTask(new UpdateOccupant(this, role));
// Prepare a new presence to be sent to all the room occupants
......
......@@ -20,43 +20,20 @@
package org.jivesoftware.openfire.muc.spi;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.openfire.PacketException;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.muc.CannotBeInvitedException;
import org.jivesoftware.openfire.muc.ConflictException;
import org.jivesoftware.openfire.muc.ForbiddenException;
import org.jivesoftware.openfire.muc.HistoryRequest;
import org.jivesoftware.openfire.muc.MUCRole;
import org.jivesoftware.openfire.muc.MUCRoom;
import org.jivesoftware.openfire.muc.MUCUser;
import org.jivesoftware.openfire.muc.MultiUserChatService;
import org.jivesoftware.openfire.muc.NotAcceptableException;
import org.jivesoftware.openfire.muc.NotAllowedException;
import org.jivesoftware.openfire.muc.RegistrationRequiredException;
import org.jivesoftware.openfire.muc.RoomLockedException;
import org.jivesoftware.openfire.muc.ServiceUnavailableException;
import org.jivesoftware.openfire.muc.*;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import org.xmpp.packet.Packet;
import org.xmpp.packet.PacketError;
import org.xmpp.packet.Presence;
import org.xmpp.packet.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Representation of users interacting with the chat service. A user
......@@ -384,10 +361,14 @@ public class LocalMUCUser implements MUCUser {
// Packets to a specific node/group/room
MUCRole role = roles.get(group);
if (role == null) {
Log.debug( "Ignoring stanza received from a non-occupant of '{}': {}", group, packet.toXML() );
if ( packet.isRequest() )
{
// If a non-occupant sends a disco to an address of the form <room@service/nick>,
// a MUC service MUST return a <bad-request/> error.
// http://xmpp.org/extensions/xep-0045.html#disco-occupant
sendErrorPacket(packet, PacketError.Condition.bad_request);
sendErrorPacket( packet, PacketError.Condition.bad_request );
}
}
else if (IQ.Type.result == packet.getType()
|| IQ.Type.error == packet.getType()) {
......
......@@ -34,7 +34,7 @@ public class PrivacyListManager {
private static final PrivacyListManager instance = new PrivacyListManager();
private static Cache<String, PrivacyList> listsCache;
private PrivacyListProvider provider = new PrivacyListProvider();
private PrivacyListProvider provider = PrivacyListProvider.getInstance();
private List<PrivacyListEventListener> listeners = new CopyOnWriteArrayList<>();
......
......@@ -29,7 +29,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicBoolean;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
......@@ -45,6 +45,8 @@ import org.slf4j.LoggerFactory;
*/
public class PrivacyListProvider {
private static final PrivacyListProvider instance = new PrivacyListProvider();
private static final Logger Log = LoggerFactory.getLogger(PrivacyListProvider.class);
private static final String PRIVACY_LIST_COUNT =
......@@ -71,11 +73,20 @@ public class PrivacyListProvider {
private BlockingQueue<SAXReader> xmlReaders = new LinkedBlockingQueue<>(POOL_SIZE);
/**
* Stores the total number of privacy lists.
* Boolean used to optimize getters when the database is empty
*/
private AtomicBoolean databaseContainsPrivacyLists;
/**
* Returns the unique instance of this class.
*
* @return the unique instance of this class.
*/
private AtomicInteger privacyListCount;
public static PrivacyListProvider getInstance() {
return instance;
}
public PrivacyListProvider() {
private PrivacyListProvider() {
super();
// Initialize the pool of sax readers
for (int i=0; i<POOL_SIZE; i++) {
......@@ -84,13 +95,10 @@ public class PrivacyListProvider {
xmlReaders.add(xmlReader);
}
// Load the total number of privacy lists in the database. We're looking
// for the (very common) special case that there are no privacy lists stored.
// In that case, we can optimize away many database calls. In the future, a
// better general-case solution may be to cache all privacy lists defined
// if there are less than, say, 500.
privacyListCount = new AtomicInteger(0);
loadPrivacyListCount();
// Checks if the PrivacyLists database is empty.
// In that case, we can optimize away many database calls.
databaseContainsPrivacyLists = new AtomicBoolean(false);
loadDatabaseContainsPrivacyLists();
}
/**
......@@ -102,7 +110,7 @@ public class PrivacyListProvider {
*/
public Map<String, Boolean> getPrivacyLists(String username) {
// If there are no privacy lists stored, this method is a no-op.
if (privacyListCount.get() == 0) {
if (!databaseContainsPrivacyLists.get()) {
return Collections.emptyMap();
}
......@@ -139,7 +147,7 @@ public class PrivacyListProvider {
*/
public PrivacyList loadPrivacyList(String username, String listName) {
// If there are no privacy lists stored, this method is a no-op.
if (privacyListCount.get() == 0) {
if (!databaseContainsPrivacyLists.get()) {
return null;
}
......@@ -203,7 +211,7 @@ public class PrivacyListProvider {
*/
public PrivacyList loadDefaultPrivacyList(String username) {
// If there are no privacy lists stored, this method is a no-op.
if (privacyListCount.get() == 0) {
if (!databaseContainsPrivacyLists.get()) {
return null;
}
......@@ -280,9 +288,7 @@ public class PrivacyListProvider {
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
// Set the privacy list count to -1. We don't know how many privacy lists there
// are, but it's not "0", which is the case we care about.
privacyListCount.set(-1);
databaseContainsPrivacyLists.set(true);
}
/**
......@@ -310,6 +316,7 @@ public class PrivacyListProvider {
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
databaseContainsPrivacyLists.set(true);
}
/**
......@@ -320,7 +327,7 @@ public class PrivacyListProvider {
*/
public void deletePrivacyList(String username, String listName) {
// If there are no privacy lists stored, this method is a no-op.
if (privacyListCount.get() == 0) {
if (!databaseContainsPrivacyLists.get()) {
return;
}
Connection con = null;
......@@ -338,9 +345,7 @@ public class PrivacyListProvider {
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
// Set the privacy list count to -1. We don't know how many privacy lists there
// are, but it's probably not "0", which is the case we care about.
privacyListCount.set(-1);
databaseContainsPrivacyLists.set(true);
}
/**
......@@ -350,7 +355,7 @@ public class PrivacyListProvider {
*/
public void deletePrivacyLists(String username) {
// If there are no privacy lists stored, this method is a no-op.
if (privacyListCount.get() == 0) {
if (!databaseContainsPrivacyLists.get()) {
return;
}
Connection con = null;
......@@ -367,15 +372,13 @@ public class PrivacyListProvider {
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
// Set the privacy list count to -1. We don't know how many privacy lists there
// are, but it's probably not "0", which is the case we care about.
privacyListCount.set(-1);
databaseContainsPrivacyLists.set(true);
}
/**
* Loads the total number of privacy lists stored in the database.
* Loads the total number of privacy lists stored in the database to know if we must use them.
*/
private void loadPrivacyListCount() {
private void loadDatabaseContainsPrivacyLists() {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
......@@ -384,7 +387,7 @@ public class PrivacyListProvider {
pstmt = con.prepareStatement(PRIVACY_LIST_COUNT);
rs = pstmt.executeQuery();
rs.next();
privacyListCount.set(rs.getInt(1));
databaseContainsPrivacyLists.set(rs.getInt(1) != 0);
}
catch (Exception e) {
Log.error(e.getMessage(), e);
......
......@@ -20,28 +20,19 @@
package org.jivesoftware.util;
import org.apache.commons.lang.StringUtils;
import org.jivesoftware.database.DbConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.TimeZone;
import java.util.TimerTask;
import org.apache.commons.lang.StringUtils;
import org.jivesoftware.database.DbConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Controls Jive properties. Jive properties are only meant to be set and retrieved
......@@ -565,6 +556,33 @@ public class JiveGlobals {
}
}
/**
* Returns an enum constant Jive property. If the specified property doesn't exist, or if it's value cannot be parsed
* as an enum constant, the <tt>defaultValue</tt> will be returned.
*
* @param name the name of the property to return.
* @param enumType the {@code Class} object of the enum type from which to return a constant.
* @param defaultValue value returned if the property doesn't exist or it's value could not be parsed.
* @param <E> The enum type whose constant is to be returned.
* @return the property value (as an enum constant) or <tt>defaultValue</tt>.
*/
public static <E extends Enum<E>> E getEnumProperty( String name, Class<E> enumType, E defaultValue )
{
String value = getProperty( name );
if ( value != null )
{
try
{
return E.valueOf( enumType, value );
}
catch ( IllegalArgumentException e )
{
// Ignore
}
}
return defaultValue;
}
/**
* Returns an integer value Jive property. If the specified property doesn't exist, the
* <tt>defaultValue</tt> will be returned.
......
package org.jivesoftware.util;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.jsmpp.bean.*;
import org.jsmpp.extra.NegativeResponseException;
import org.jsmpp.session.BindParameter;
import org.jsmpp.session.SMPPSession;
import org.jsmpp.util.AbsoluteTimeFormatter;
import org.jsmpp.util.TimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* A service to send SMS messages.<p>
*
* This class is configured with a set of Jive properties. Note that each service provider can require a different set
* of properties to be set.
* <ul>
* <li><tt>sms.smpp.connections.maxAmount</tt> -- the maximum amount of connections. The default value is one.
* <li><tt>sms.smpp.connections.idleMillis</tt> -- time (in ms) after which idle connections are allowed to be evicted. Defaults to two minutes.
* <li><tt>sms.smpp.host</tt> -- the host name of your SMPP Server or SMSC, i.e. smsc.example.org. The default value is "localhost".
* <li><tt>sms.smpp.port</tt> -- the port on which the SMSC is listening. Defaults to 2775.
* <li><tt>sms.smpp.systemId</tt> -- the 'user name' to use when connecting to the SMSC.
* <li><tt>sms.smpp.password</tt> -- the password that authenticates the systemId value when connecting to the SMSC.
* <li><tt>sms.smpp.systemType</tt> -- an optional system type, which, if defined, will be used when connecting to the SMSC.
* <li><tt>sms.smpp.receive.ton</tt> -- The type-of-number value for 'receiving' SMS messages. Defaults to 'UNKNOWN'.
* <li><tt>sms.smpp.receive.npi</tt> -- The number-plan-indicator value for 'receiving' SMS messages. Defaults to 'UNKNOWN'.
* <li><tt>sms.smpp.source.ton</tt> -- The type-of-number value for the source of SMS messages. Defaults to 'UNKNOWN'.
* <li><tt>sms.smpp.source.npi</tt> -- The number-plan-indicator value for the source of SMS messages. Defaults to 'UNKNOWN'.
* <li><tt>sms.smpp.source.address</tt> -- The source address of SMS messages.
* <li><tt>sms.smpp.destination.ton</tt> -- The type-of-number value for the destination of SMS messages. Defaults to 'UNKNOWN'.
* <li><tt>sms.smpp.destination.npi</tt> -- The number-plan-indicator value for the destination of SMS messages. Defaults to 'UNKNOWN'.
* </ul>
*
* @author Guus der Kinderen, guus@goodbytes.nl
*/
public class SmsService
{
private static final Logger Log = LoggerFactory.getLogger( SmsService.class );
private static TimeFormatter timeFormatter = new AbsoluteTimeFormatter();
private static SmsService INSTANCE;
public static synchronized SmsService getInstance()
{
if ( INSTANCE == null )
{
INSTANCE = new SmsService();
}
return INSTANCE;
}
/**
* Pool of SMPP sessions that is used to transmit messages to the SMSC.
*/
private final SMPPSessionPool sessionPool;
private SmsService()
{
sessionPool = new SMPPSessionPool();
PropertyEventDispatcher.addListener( sessionPool );
}
/**
* Causes a new SMS message to be sent.
*
* Note that the message is sent asynchronously. This method does not block. A successful invocation does not
* guarantee successful delivery
*
* @param message The body of the message (cannot be null or empty).
* @param recipient The address / phone number to which the message is to be send (cannot be null or empty).
*/
public void send( String message, String recipient )
{
if ( message == null || message.isEmpty() )
{
throw new IllegalArgumentException( "Argument 'message' cannot be null or an empty String." );
}
if ( recipient == null || recipient.isEmpty() )
{
throw new IllegalArgumentException( "Argument 'recipient' cannot be null or an empty String." );
}
TaskEngine.getInstance().submit( new SmsTask( sessionPool, message, recipient ) );
}
/**
* Causes a new SMS message to be sent.
*
* This method differs from {@link #send(String, String)} in that the message is sent before this method returns,
* rather than queueing the messages to be sent later (in an async fashion). As a result, any exceptions that occur
* while sending the message are thrown by this method (which can be useful to test the configuration of this
* service).
*
* @param message The body of the message (cannot be null or empty).
* @param recipient The address / phone number to which the message is to be send (cannot be null or empty).
* @throws Exception On any problem.
*/
public void sendImmediately( String message, String recipient ) throws Exception
{
if ( message == null || message.isEmpty() )
{
throw new IllegalArgumentException( "Argument 'message' cannot be null or an empty String." );
}
if ( recipient == null || recipient.isEmpty() )
{
throw new IllegalArgumentException( "Argument 'recipient' cannot be null or an empty String." );
}
try
{
new SmsTask( sessionPool, message, recipient ).sendMessage();
}
catch ( Exception e )
{
Log.error( "An exception occurred while sending a SMS message (to '{}')", recipient, e );
throw e;
}
}
/**
* Checks if an exception in the chain of the provided throwable contains a 'command status' that can be
* translated in a somewhat more helpful error message.
*
* The list of error messages was taken from http://www.smssolutions.net/tutorials/smpp/smpperrorcodes/
*
* @param ex The exception in which to search for a command status.
* @return a human readable error message.
*/
public static String getDescriptiveMessage( Throwable ex )
{
if ( ex instanceof NegativeResponseException )
{
final Map<Integer, String> errors = new HashMap<>();
errors.put( 0x00000000, "No Error" );
errors.put( 0x00000001, "Message too long" );
errors.put( 0x00000002, "Command length is invalid" );
errors.put( 0x00000003, "Command ID is invalid or not supported" );
errors.put( 0x00000004, "Incorrect bind status for given command" );
errors.put( 0x00000005, "Already bound" );
errors.put( 0x00000006, "Invalid Priority Flag" );
errors.put( 0x00000007, "Invalid registered delivery flag" );
errors.put( 0x00000008, "System error" );
errors.put( 0x0000000A, "Invalid source address" );
errors.put( 0x0000000B, "Invalid destination address" );
errors.put( 0x0000000C, "Message ID is invalid" );
errors.put( 0x0000000D, "Bind failed" );
errors.put( 0x0000000E, "Invalid password" );
errors.put( 0x0000000F, "Invalid System ID" );
errors.put( 0x00000011, "Cancelling message failed" );
errors.put( 0x00000013, "Message recplacement failed" );
errors.put( 0x00000014, "Message queue full" );
errors.put( 0x00000015, "Invalid service type" );
errors.put( 0x00000033, "Invalid number of destinations" );
errors.put( 0x00000034, "Invalid distribution list name" );
errors.put( 0x00000040, "Invalid destination flag" );
errors.put( 0x00000042, "Invalid submit with replace request" );
errors.put( 0x00000043, "Invalid esm class set" );
errors.put( 0x00000044, "Invalid submit to ditribution list" );
errors.put( 0x00000045, "Submitting message has failed" );
errors.put( 0x00000048, "Invalid source address type of number ( TON )" );
errors.put( 0x00000049, "Invalid source address numbering plan ( NPI )" );
errors.put( 0x00000050, "Invalid destination address type of number ( TON )" );
errors.put( 0x00000051, "Invalid destination address numbering plan ( NPI )" );
errors.put( 0x00000053, "Invalid system type" );
errors.put( 0x00000054, "Invalid replace_if_present flag" );
errors.put( 0x00000055, "Invalid number of messages" );
errors.put( 0x00000058, "Throttling error" );
errors.put( 0x00000061, "Invalid scheduled delivery time" );
errors.put( 0x00000062, "Invalid Validty Period value" );
errors.put( 0x00000063, "Predefined message not found" );
errors.put( 0x00000064, "ESME Receiver temporary error" );
errors.put( 0x00000065, "ESME Receiver permanent error" );
errors.put( 0x00000066, "ESME Receiver reject message error" );
errors.put( 0x00000067, "Message query request failed" );
errors.put( 0x000000C0, "Error in the optional part of the PDU body" );
errors.put( 0x000000C1, "TLV not allowed" );
errors.put( 0x000000C2, "Invalid parameter length" );
errors.put( 0x000000C3, "Expected TLV missing" );
errors.put( 0x000000C4, "Invalid TLV value" );
errors.put( 0x000000FE, "Transaction delivery failure" );
errors.put( 0x000000FF, "Unknown error" );
errors.put( 0x00000100, "ESME not authorised to use specified servicetype" );
errors.put( 0x00000101, "ESME prohibited from using specified operation" );
errors.put( 0x00000102, "Specified servicetype is unavailable" );
errors.put( 0x00000103, "Specified servicetype is denied" );
errors.put( 0x00000104, "Invalid data coding scheme" );
errors.put( 0x00000105, "Invalid source address subunit" );
errors.put( 0x00000106, "Invalid destination address subunit" );
errors.put( 0x0000040B, "Insufficient credits to send message" );
errors.put( 0x0000040C, "Destination address blocked by the ActiveXperts SMPP Demo Server" );
String error = errors.get( ( (NegativeResponseException) ex ).getCommandStatus() );
if ( ex.getMessage() != null && !ex.getMessage().isEmpty() )
{
error += " (exception message: '" + ex.getMessage() + "')";
}
return error;
}
else if ( ex.getCause() != null )
{
return getDescriptiveMessage( ex.getCause() );
}
return ex.getMessage();
}
/**
* Runnable that allows an SMS to be sent in a different thread.
*/
private static class SmsTask implements Runnable
{
private final ObjectPool<SMPPSession> sessionPool;
// Settings that apply to source of an SMS message.
private final TypeOfNumber sourceTon = JiveGlobals.getEnumProperty( "sms.smpp.source.ton", TypeOfNumber.class, TypeOfNumber.UNKNOWN );
private final NumberingPlanIndicator sourceNpi = JiveGlobals.getEnumProperty( "sms.smpp.source.npi", NumberingPlanIndicator.class, NumberingPlanIndicator.UNKNOWN );
private final String sourceAddress = JiveGlobals.getProperty( "sms.smpp.source.address" );
// Settings that apply to destination of an SMS message.
private final TypeOfNumber destinationTon = JiveGlobals.getEnumProperty( "sms.smpp.destination.ton", TypeOfNumber.class, TypeOfNumber.UNKNOWN );
private final NumberingPlanIndicator destinationNpi = JiveGlobals.getEnumProperty( "sms.smpp.destination.npi", NumberingPlanIndicator.class, NumberingPlanIndicator.UNKNOWN );
private final String destinationAddress;
private final byte[] message;
// Non-configurable defaults (for now - TODO?)
private final ESMClass esm = new ESMClass();
private final byte protocolId = 0;
private final byte priorityFlag = 1;
private final String serviceType = "CMT";
private final String scheduleDeliveryTime = timeFormatter.format( new Date() );
private final String validityPeriod = null;
private final RegisteredDelivery registeredDelivery = new RegisteredDelivery( SMSCDeliveryReceipt.DEFAULT );
private final byte replaceIfPresentFlag = 0;
private final DataCoding dataCoding = new GeneralDataCoding( Alphabet.ALPHA_DEFAULT, MessageClass.CLASS1, false );
private final byte smDefaultMsgId = 0;
SmsTask( ObjectPool<SMPPSession> sessionPool, String message, String destinationAddress )
{
this.sessionPool = sessionPool;
this.message = message.getBytes();
this.destinationAddress = destinationAddress;
}
@Override
public void run()
{
try
{
sendMessage();
}
catch ( Exception e )
{
Log.error( "An exception occurred while sending a SMS message (to '{}')", destinationAddress, e );
}
}
public void sendMessage() throws Exception
{
final SMPPSession session = sessionPool.borrowObject();
try
{
final String messageId = session.submitShortMessage(
serviceType,
sourceTon, sourceNpi, sourceAddress,
destinationTon, destinationNpi, destinationAddress,
esm, protocolId, priorityFlag,
scheduleDeliveryTime, validityPeriod, registeredDelivery, replaceIfPresentFlag,
dataCoding, smDefaultMsgId, message );
Log.debug( "Message submitted, message_id is '{}'.", messageId );
}
finally
{
sessionPool.returnObject( session );
}
}
}
/**
* A factory of SMPPSession instances that are used in an object pool.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
private static class SMPPSessionFactory extends BasePooledObjectFactory<SMPPSession>
{
private static final Logger Log = LoggerFactory.getLogger( SMPPSessionFactory.class );
@Override
public SMPPSession create() throws Exception
{
// SMSC connection settings
final String host = JiveGlobals.getProperty( "sms.smpp.host", "localhost" );
final int port = JiveGlobals.getIntProperty( "sms.smpp.port", 2775 );
final String systemId = JiveGlobals.getProperty( "sms.smpp.systemId" );
final String password = JiveGlobals.getProperty( "sms.smpp.password" );
final String systemType = JiveGlobals.getProperty( "sms.smpp.systemType" );
// Settings that apply to 'receiving' SMS. Should not apply to this implementation, as we're not receiving anything..
final TypeOfNumber receiveTon = JiveGlobals.getEnumProperty( "sms.smpp.receive.ton", TypeOfNumber.class, TypeOfNumber.UNKNOWN );
final NumberingPlanIndicator receiveNpi = JiveGlobals.getEnumProperty( "sms.smpp.receive.npi", NumberingPlanIndicator.class, NumberingPlanIndicator.UNKNOWN );
Log.debug( "Creating a new sesssion (host: '{}', port: '{}', systemId: '{}'.", host, port, systemId );
final SMPPSession session = new SMPPSession();
session.connectAndBind( host, port, new BindParameter( BindType.BIND_TX, systemId, password, systemType, receiveTon, receiveNpi, null ) );
Log.debug( "Created a new session with ID '{}'.", session.getSessionId() );
return session;
}
@Override
public boolean validateObject( PooledObject<SMPPSession> pooledObject )
{
final SMPPSession session = pooledObject.getObject();
final boolean isValid = session.getSessionState().isTransmittable(); // updated by the SMPPSession internal enquireLink timer.
Log.debug( "Ran a check to see if session with ID '{}' is valid. Outcome: {}", session.getSessionId(), isValid );
return isValid;
}
@Override
public void destroyObject( PooledObject<SMPPSession> pooledObject ) throws Exception
{
final SMPPSession session = pooledObject.getObject();
Log.debug( "Destroying a pooled session with ID '{}'.", session.getSessionId() );
session.unbindAndClose();
}
@Override
public PooledObject<SMPPSession> wrap( SMPPSession smppSession )
{
return new DefaultPooledObject<>( smppSession );
}
}
/**
* Implementation of an Object pool that manages instances of SMPPSession. The intend of this pool is to have a
* single session, that's allowed to be idle for at least two minutes before being closed.
*
* The pool reacts to Openfire property changes, clearing all (inactive) sessions when a property used to create
* a session is modified. Note that sessions that are borrowed from the pool are not affected by such a change. When
* a property change occurs while a session is borrowed, a warning is logged (the property change will be applied
* when that session is eventually rotated out of the pool by the eviction strategy.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
private static class SMPPSessionPool extends GenericObjectPool<SMPPSession> implements PropertyEventListener
{
private static final Logger Log = LoggerFactory.getLogger( SMPPSessionPool.class );
SMPPSessionPool()
{
super( new SMPPSessionFactory() );
setMaxTotal( JiveGlobals.getIntProperty( "sms.smpp.connections.maxAmount", 1 ) );
setNumTestsPerEvictionRun( getMaxTotal() );
setMinEvictableIdleTimeMillis( JiveGlobals.getLongProperty( "sms.smpp.connections.idleMillis", 1000 * 60 * 2 ) );
if ( getMinEvictableIdleTimeMillis() > 0 )
{
setTimeBetweenEvictionRunsMillis( getMinEvictableIdleTimeMillis() / 10 );
}
setTestOnBorrow( true );
setTestWhileIdle( true );
}
void processPropertyChange( String propertyName )
{
final Set<String> ofInterest = new HashSet<>();
ofInterest.add( "sms.smpp.host" );
ofInterest.add( "sms.smpp.port" );
ofInterest.add( "sms.smpp.systemId" );
ofInterest.add( "sms.smpp.password" );
ofInterest.add( "sms.smpp.systemType" );
ofInterest.add( "sms.smpp.receive.ton" );
ofInterest.add( "sms.smpp.receive.npi" );
if ( ofInterest.contains( propertyName ) )
{
Log.debug( "Property change for '{}' detected. Clearing all (inactive) sessions.", propertyName );
if ( getNumActive() > 0 )
{
// This can occur when an SMS is being sent while the property is being updated at the same time.
Log.warn( "Note that property change for '{}' will not affect one or more sessions that are currently actively used (although changes will be applied after the session is rotated out, due to time-based eviction)." );
}
clear();
}
// No need to clear the sessions for these properties:
if ( propertyName.equals( "sms.smpp.connections.maxAmount" ) )
{
setMaxTotal( JiveGlobals.getIntProperty( "sms.smpp.connections.maxAmount", 1 ) );
setNumTestsPerEvictionRun( getMaxTotal() );
}
if ( propertyName.equals( "sms.smpp.connections.idleMillis" ) )
{
setMinEvictableIdleTimeMillis( JiveGlobals.getLongProperty( "sms.smpp.connections.idleMillis", 1000 * 60 * 2 ) );
if ( getMinEvictableIdleTimeMillis() > 0 )
{
setTimeBetweenEvictionRunsMillis( getMinEvictableIdleTimeMillis() / 10 );
}
}
}
@Override
public void propertySet( String property, Map<String, Object> params )
{
processPropertyChange( property );
}
@Override
public void propertyDeleted( String property, Map<String, Object> params )
{
processPropertyChange( property );
}
@Override
public void xmlPropertySet( String property, Map<String, Object> params )
{
processPropertyChange( property );
}
@Override
public void xmlPropertyDeleted( String property, Map<String, Object> params )
{
processPropertyChange( property );
}
}
}
......@@ -373,8 +373,14 @@ public class ClusterExternalizableUtil implements ExternalizableUtilStrategy {
} else if (obj instanceof Date) {
out.writeByte(8);
out.writeLong(((Date) obj).getTime());
} else {
} else if(obj instanceof byte[]){
out.writeByte(9);
// write length
out.writeInt(((byte[]) obj).length);
// write byte array
out.write((byte[]) obj);
} else {
out.writeByte(10);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
......@@ -404,6 +410,10 @@ public class ClusterExternalizableUtil implements ExternalizableUtilStrategy {
} else if (type == 8) {
return new Date(in.readLong());
} else if (type == 9) {
byte[] buf = new byte[in.readInt()];
in.readFully(buf);
return buf;
} else if (type == 10) {
int len = in.readInt();
byte[] buf = new byte[len];
in.readFully(buf);
......
......@@ -148,6 +148,11 @@ hr {
</div>
<div id="pageBody">
<p><b>2.6.0</b> -- September 27, 2016</p>
<ul>
<li>Added option to use XEP-0227 Compliant export and import.</li>
</ul>
<p><b>2.5.0</b> -- October 12, 2015</p>
<ul>
<li>[<a href='http://www.igniterealtime.org/issues/browse/OF-953'>OF-953</a>] - Updated JSP libraries.</li>
......
<?xml version='1.0' encoding='UTF-8'?>
<xs:schema
xmlns:xs='http://www.w3.org/2001/XMLSchema'
xmlns:xml='http://www.w3.org/XML/1998/namespace'
targetNamespace='jabber:client'
xmlns='jabber:client'
elementFormDefault='qualified'>
<xs:import namespace='urn:ietf:params:xml:ns:xmpp-stanzas'
schemaLocation='http://xmpp.org/schemas/stanzaerror.xsd'/>
<xs:import namespace='http://www.w3.org/XML/1998/namespace'
schemaLocation='http://www.w3.org/2001/03/xml.xsd'/>
<xs:element name='message'>
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs='0' maxOccurs='unbounded'>
<xs:element ref='subject'/>
<xs:element ref='body'/>
<xs:element ref='thread'/>
</xs:choice>
<xs:any namespace='##other'
minOccurs='0'
maxOccurs='unbounded'
processContents='lax'/>
<xs:element ref='error'
minOccurs='0'/>
</xs:sequence>
<xs:attribute name='from'
type='xs:string'
use='optional'/>
<xs:attribute name='id'
type='xs:NMTOKEN'
use='optional'/>
<xs:attribute name='to'
type='xs:string'
use='optional'/>
<xs:attribute name='type'
use='optional'
default='normal'>
<xs:simpleType>
<xs:restriction base='xs:NMTOKEN'>
<xs:enumeration value='chat'/>
<xs:enumeration value='error'/>
<xs:enumeration value='groupchat'/>
<xs:enumeration value='headline'/>
<xs:enumeration value='normal'/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute ref='xml:lang' use='optional'/>
</xs:complexType>
</xs:element>
<xs:element name='body'>
<xs:complexType>
<xs:simpleContent>
<xs:extension base='xs:string'>
<xs:attribute ref='xml:lang' use='optional'/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name='subject'>
<xs:complexType>
<xs:simpleContent>
<xs:extension base='xs:string'>
<xs:attribute ref='xml:lang' use='optional'/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name='thread'>
<xs:complexType>
<xs:simpleContent>
<xs:extension base='xs:NMTOKEN'>
<xs:attribute name='parent'
type='xs:NMTOKEN'
use='optional'/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name='presence'>
<xs:complexType>
<xs:sequence>
<xs:choice minOccurs='0' maxOccurs='unbounded'>
<xs:element ref='show'/>
<xs:element ref='status'/>
<xs:element ref='priority'/>
</xs:choice>
<xs:any namespace='##other'
minOccurs='0'
maxOccurs='unbounded'
processContents='lax'/>
<xs:element ref='error'
minOccurs='0'/>
</xs:sequence>
<xs:attribute name='from'
type='xs:string'
use='optional'/>
<xs:attribute name='id'
type='xs:NMTOKEN'
use='optional'/>
<xs:attribute name='to'
type='xs:string'
use='optional'/>
<xs:attribute name='type' use='optional'>
<xs:simpleType>
<xs:restriction base='xs:NMTOKEN'>
<xs:enumeration value='error'/>
<xs:enumeration value='probe'/>
<xs:enumeration value='subscribe'/>
<xs:enumeration value='subscribed'/>
<xs:enumeration value='unavailable'/>
<xs:enumeration value='unsubscribe'/>
<xs:enumeration value='unsubscribed'/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute ref='xml:lang' use='optional'/>
</xs:complexType>
</xs:element>
<xs:element name='show'>
<xs:simpleType>
<xs:restriction base='xs:NMTOKEN'>
<xs:enumeration value='away'/>
<xs:enumeration value='chat'/>
<xs:enumeration value='dnd'/>
<xs:enumeration value='xa'/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name='status'>
<xs:complexType>
<xs:simpleContent>
<xs:extension base='string1024'>
<xs:attribute ref='xml:lang' use='optional'/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:simpleType name='string1024'>
<xs:restriction base='xs:string'>
<xs:minLength value='1'/>
<xs:maxLength value='1024'/>
</xs:restriction>
</xs:simpleType>
<xs:element name='priority' type='xs:byte'/>
<xs:element name='iq'>
<xs:complexType>
<xs:sequence>
<xs:any namespace='##other'
minOccurs='0'
maxOccurs='1'
processContents='lax'/>
<xs:element ref='error'
minOccurs='0'/>
</xs:sequence>
<xs:attribute name='from'
type='xs:string'
use='optional'/>
<xs:attribute name='id'
type='xs:NMTOKEN'
use='required'/>
<xs:attribute name='to'
type='xs:string'
use='optional'/>
<xs:attribute name='type' use='required'>
<xs:simpleType>
<xs:restriction base='xs:NMTOKEN'>
<xs:enumeration value='error'/>
<xs:enumeration value='get'/>
<xs:enumeration value='result'/>
<xs:enumeration value='set'/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute ref='xml:lang' use='optional'/>
</xs:complexType>
</xs:element>
<xs:element name='error'>
<xs:complexType>
<xs:sequence xmlns:err='urn:ietf:params:xml:ns:xmpp-stanzas'>
<xs:group ref='err:stanzaErrorGroup'/>
<xs:element ref='err:text'
minOccurs='0'/>
</xs:sequence>
<xs:attribute name='by'
type='xs:string'
use='optional'/>
<xs:attribute name='type' use='required'>
<xs:simpleType>
<xs:restriction base='xs:NMTOKEN'>
<xs:enumeration value='auth'/>
<xs:enumeration value='cancel'/>
<xs:enumeration value='continue'/>
<xs:enumeration value='modify'/>
<xs:enumeration value='wait'/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
<?xml version='1.0' encoding='UTF-8'?>
<xs:schema
xmlns:xs='http://www.w3.org/2001/XMLSchema'
targetNamespace='jabber:iq:private'
xmlns='jabber:iq:private'
elementFormDefault='qualified'>
<xs:annotation>
<xs:documentation>
The protocol documented by this schema is defined in
XEP-0049: http://www.xmpp.org/extensions/xep-0049.html
</xs:documentation>
</xs:annotation>
<xs:element name='query'>
<xs:complexType>
<xs:sequence minOccurs='0'>
<xs:any namespace='##other'/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'
targetNamespace='jabber:iq:roster'
xmlns='jabber:iq:roster'
elementFormDefault='qualified'>
<xs:element name='query'>
<xs:complexType>
<xs:sequence>
<xs:element ref='item' minOccurs='0' maxOccurs='unbounded' />
</xs:sequence>
<xs:attribute name='ver' type='xs:string' use='optional' />
</xs:complexType>
</xs:element>
<xs:element name='item'>
<xs:complexType>
<xs:sequence>
<xs:element ref='group' minOccurs='0' maxOccurs='unbounded' />
</xs:sequence>
<xs:attribute name='approved' type='xs:boolean' use='optional' />
<xs:attribute name='ask' use='optional'>
<xs:simpleType>
<xs:restriction base='xs:NMTOKEN'>
<xs:enumeration value='subscribe' />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name='jid' type='xs:string' use='required' />
<xs:attribute name='name' type='xs:string' use='optional' />
<xs:attribute name='subscription' use='optional' default='none'>
<xs:simpleType>
<xs:restriction base='xs:NMTOKEN'>
<xs:enumeration value='both' />
<xs:enumeration value='from' />
<xs:enumeration value='none' />
<xs:enumeration value='remove' />
<xs:enumeration value='to' />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name='group' type='xs:string' />
</xs:schema>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<xs:schema
xmlns:xs='http://www.w3.org/2001/XMLSchema'
targetNamespace='urn:xmpp:pie:0'
xmlns='urn:xmpp:pie:0'
elementFormDefault='qualified'>
<xs:annotation>
<xs:documentation>
The protocol documented by this schema is defined in
XEP-0227: http://xmpp.org/extensions/xep-0227.html
</xs:documentation>
</xs:annotation>
<xs:element name='server-data'>
<xs:complexType>
<xs:sequence>
<xs:element ref='host' maxOccurs='unbounded'/>
<xs:any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name='host'>
<xs:complexType>
<xs:sequence>
<xs:element ref='user' maxOccurs='unbounded'/>
<xs:any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>
</xs:sequence>
<xs:attribute name='jid' type='xs:string' use='required'/>
</xs:complexType>
</xs:element>
<xs:element name='user'>
<xs:complexType>
<xs:sequence>
<xs:element ref='offline-messages' minOccurs='0' maxOccurs='1'/>
<xs:any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>
</xs:sequence>
<xs:attribute name='name' type='xs:string' use='required'/>
<xs:attribute name='password' type='xs:string' use='optional'/>
</xs:complexType>
</xs:element>
<xs:element name='offline-messages'>
<xs:complexType>
<xs:sequence>
<xs:any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
<?xml version='1.0' encoding='UTF-8'?>
<xs:schema
xmlns:xs='http://www.w3.org/2001/XMLSchema'
xmlns:xml='http://www.w3.org/XML/1998/namespace'
targetNamespace='urn:ietf:params:xml:ns:xmpp-stanzas'
xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'
elementFormDefault='qualified'>
<xs:import namespace='http://www.w3.org/XML/1998/namespace'
schemaLocation='http://www.w3.org/2001/03/xml.xsd'/>
<xs:element name='bad-request' type='empty'/>
<xs:element name='conflict' type='empty'/>
<xs:element name='feature-not-implemented' type='empty'/>
<xs:element name='forbidden' type='empty'/>
<xs:element name='gone' type='xs:string'/>
<xs:element name='internal-server-error' type='empty'/>
<xs:element name='item-not-found' type='empty'/>
<xs:element name='jid-malformed' type='empty'/>
<xs:element name='not-acceptable' type='empty'/>
<xs:element name='not-allowed' type='empty'/>
<xs:element name='not-authorized' type='empty'/>
<xs:element name='payment-required' type='empty'/>
<xs:element name='policy-violation' type='empty'/>
<xs:element name='recipient-unavailable' type='empty'/>
<xs:element name='redirect' type='xs:string'/>
<xs:element name='registration-required' type='empty'/>
<xs:element name='remote-server-not-found' type='empty'/>
<xs:element name='remote-server-timeout' type='empty'/>
<xs:element name='resource-constraint' type='empty'/>
<xs:element name='service-unavailable' type='empty'/>
<xs:element name='subscription-required' type='empty'/>
<xs:element name='undefined-condition' type='empty'/>
<xs:element name='unexpected-request' type='empty'/>
<xs:group name='stanzaErrorGroup'>
<xs:choice>
<xs:element ref='bad-request'/>
<xs:element ref='conflict'/>
<xs:element ref='feature-not-implemented'/>
<xs:element ref='forbidden'/>
<xs:element ref='gone'/>
<xs:element ref='internal-server-error'/>
<xs:element ref='item-not-found'/>
<xs:element ref='jid-malformed'/>
<xs:element ref='not-acceptable'/>
<xs:element ref='not-authorized'/>
<xs:element ref='not-allowed'/>
<xs:element ref='payment-required'/>
<xs:element ref='policy-violation'/>
<xs:element ref='recipient-unavailable'/>
<xs:element ref='redirect'/>
<xs:element ref='registration-required'/>
<xs:element ref='remote-server-not-found'/>
<xs:element ref='remote-server-timeout'/>
<xs:element ref='resource-constraint'/>
<xs:element ref='service-unavailable'/>
<xs:element ref='subscription-required'/>
<xs:element ref='undefined-condition'/>
<xs:element ref='unexpected-request'/>
</xs:choice>
</xs:group>
<xs:element name='text'>
<xs:complexType>
<xs:simpleContent>
<xs:extension base='xs:string'>
<xs:attribute ref='xml:lang' use='optional'/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:simpleType name='empty'>
<xs:restriction base='xs:string'>
<xs:enumeration value=''/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) The Internet Society (2000). All Rights Reserved.
This document and translations of it may be copied and
furnished to others, and derivative works that comment
on or otherwise explain it or assist in its implmentation
may be prepared, copied, published and distributed, in whole
or in part, without restriction of any kind, provided that
the above copyright notice and this paragraph are included
on all such copies and derivative works.
However, this document itself may not be modified in any
way, such as by removing the copyright notice or references
to the Internet Society or other Internet organizations,
except as needed for the purpose of developing Internet
standards in which case the procedures for copyrights
defined in the Internet Standards process MUST be followed,
or as required to translate it into languages other than English.
The limited permissions granted above are perpetual and will
not be revoked by the Internet Society or its successors or
assigns.
This document and the information contained herein is provided
on an "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET
ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE
USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR
ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A
PARTICULAR PURPOSE.
-->
<!-- ==== -->
<!-- NOTE: the following root element is not used in the
modified vcard-temp DTD published by the Jabber
project (now XMPP Standards Foundation) and is
included here only for historical purposes;
implementations that comply with vcard-temp must
specify the root element as vCard, not xCard. -->
<!-- Root element and container for one
or more vCard objects -->
<!ELEMENT xCard (vCard)+>
<!-- Individual vCard container -->
<!ELEMENT vCard (
(VERSION, FN, N),
(NICKNAME?,
PHOTO?,
BDAY?,
ADR?,
LABEL?,
TEL?,
EMAIL?,
JABBERID?,
MAILER?,
TZ?,
GEO?,
TITLE?,
ROLE?,
LOGO?,
AGENT?,
ORG?,
CATEGORIES?,
NOTE?,
PRODID?,
REV?,
SORT-STRING?,
SOUND?,
UID?,
URL?,
CLASS?,
KEY?,
DESC?
)*)>
<!-- vCard specification version property.
This MUST be 2.0, if the document conforms to RFC 2426. -->
<!ELEMENT VERSION (#PCDATA)>
<!-- Formatted or display name property. -->
<!ELEMENT FN (#PCDATA)>
<!-- Structured name property. Name components with multiple
values must be specified as a comma separated
list of values. -->
<!ELEMENT N ( FAMILY?, GIVEN?, MIDDLE?, PREFIX?, SUFFIX?)>
<!ELEMENT FAMILY (#PCDATA)>
<!ELEMENT GIVEN (#PCDATA)>
<!ELEMENT MIDDLE (#PCDATA)>
<!ELEMENT PREFIX (#PCDATA)>
<!ELEMENT SUFFIX (#PCDATA)>
<!-- Nickname property. Multiple nicknames must be
specified as a comma separated list value. -->
<!ELEMENT NICKNAME (#PCDATA)>
<!-- Photograph property. Value is either a BASE64 encoded
binary value or a URI to the external content. -->
<!ELEMENT PHOTO ((TYPE, BINVAL) | EXTVAL)>
<!-- Birthday property. Value must be an ISO 8601 formatted
date or date/time value. -->
<!ELEMENT BDAY (#PCDATA)>
<!-- Structured address property. Address components with
multiple values must be specified as a comma separated list
of values. -->
<!ELEMENT ADR (
HOME?,
WORK?,
POSTAL?,
PARCEL?,
(DOM | INTL)?,
PREF?,
POBOX?,
EXTADD?,
STREET?,
LOCALITY?,
REGION?,
PCODE?,
CTRY?
)>
<!ELEMENT POBOX (#PCDATA)>
<!ELEMENT EXTADD (#PCDATA)>
<!ELEMENT STREET (#PCDATA)>
<!ELEMENT LOCALITY (#PCDATA)>
<!ELEMENT REGION (#PCDATA)>
<!ELEMENT PCODE (#PCDATA)>
<!ELEMENT CTRY (#PCDATA)>
<!-- Address label property. -->
<!ELEMENT LABEL (
HOME?,
WORK?,
POSTAL?,
PARCEL?,
(DOM | INTL)?,
PREF?,
LINE+
)>
<!-- Individual label lines. -->
<!ELEMENT LINE (#PCDATA)>
<!-- Telephone number property. -->
<!ELEMENT TEL (
HOME?,
WORK?,
VOICE?,
FAX?,
PAGER?,
MSG?,
CELL?,
VIDEO?,
BBS?,
MODEM?,
ISDN?,
PCS?,
PREF?,
NUMBER
)>
<!-- Phone number value. -->
<!ELEMENT NUMBER (#PCDATA)>
<!-- Email address property. Default type is INTERNET. -->
<!ELEMENT EMAIL (
HOME?,
WORK?,
INTERNET?,
PREF?,
X400?,
USERID
)>
<!ELEMENT USERID (#PCDATA)>
<!-- NOTE: the following element was added by the Jabber
project (now XMPP Standards Foundation) to
handle Jabber IDs; the value must be in the
form of user@host -->
<!ELEMENT JABBERID (#PCDATA)>
<!-- Mailer (e.g., Mail User Agent Type) property. -->
<!ELEMENT MAILER (#PCDATA)>
<!-- Time zone's Standard Time UTC offset. Value must be an
ISO 8601 formatted UTC offset. -->
<!ELEMENT TZ (#PCDATA)>
<!-- Geographical position. Values are the decimal degress of
LATitude and LONgitude. The value should be specified to
six decimal places.-->
<!ELEMENT GEO (LAT, LON)>
<!-- Latitude value. -->
<!ELEMENT LAT (#PCDATA)>
<!-- Longitude value. -->
<!ELEMENT LON (#PCDATA)>
<!-- Title property. -->
<!ELEMENT TITLE (#PCDATA)>
<!-- Role property. -->
<!ELEMENT ROLE (#PCDATA)>
<!-- Organization logo property. -->
<!ELEMENT LOGO ((TYPE, BINVAL) | EXTVAL)>
<!-- Administrative agent property. -->
<!ELEMENT AGENT (vCard | EXTVAL)>
<!-- Organizational name and units property. -->
<!ELEMENT ORG (ORGNAME, ORGUNIT*)>
<!ELEMENT ORGNAME (#PCDATA)>
<!ELEMENT ORGUNIT (#PCDATA)>
<!-- Application specific categories property. -->
<!ELEMENT CATEGORIES (KEYWORD+)>
<!ELEMENT KEYWORD (#PCDATA)>
<!-- Commentary note property. -->
<!ELEMENT NOTE (#PCDATA)>
<!-- Identifier of product that generated the vCard property. -->
<!ELEMENT PRODID (#PCDATA)>
<!-- Last revised property. The value must be an
ISO 8601 formatted UTC date/time. -->
<!ELEMENT REV (#PCDATA)>
<!-- Sort string property. -->
<!ELEMENT SORT-STRING (#PCDATA)>
<!-- Formatted name pronunciation property. The value is
either a textual phonetic pronunciation, a BASE64
encoded binary digital audio pronunciation or a URI to
an external binary digital audio pronunciation.-->
<!ELEMENT SOUND (PHONETIC | BINVAL | EXTVAL)>
<!-- Textual phonetic pronunciation. -->
<!ELEMENT PHONETIC (#PCDATA)>
<!-- Unique identifier property. -->
<!ELEMENT UID (#PCDATA)>
<!-- Directory URL property. -->
<!ELEMENT URL (#PCDATA)>
<!-- NOTE: the following element was added by the Jabber
project (now XMPP Standards Foundation) to
handle free-form descriptive text. -->
<!ELEMENT DESC (#PCDATA)>
<!-- Privacy classification property. -->
<!ELEMENT CLASS (PUBLIC | PRIVATE | CONFIDENTIAL)>
<!ELEMENT PUBLIC EMPTY>
<!ELEMENT PRIVATE EMPTY>
<!ELEMENT CONFIDENTIAL EMPTY>
<!-- Authentication credential or encryption key property. -->
<!ELEMENT KEY (TYPE?, CRED)>
<!ELEMENT CRED (#PCDATA)>
<!-- ==== -->
<!-- Common elements. -->
<!-- Addressing type indicators. -->
<!ELEMENT HOME EMPTY>
<!ELEMENT WORK EMPTY>
<!ELEMENT POSTAL EMPTY>
<!ELEMENT PARCEL EMPTY>
<!ELEMENT DOM EMPTY>
<!ELEMENT INTL EMPTY>
<!ELEMENT PREF EMPTY>
<!ELEMENT VOICE EMPTY>
<!ELEMENT FAX EMPTY>
<!ELEMENT PAGER EMPTY>
<!ELEMENT MSG EMPTY>
<!ELEMENT CELL EMPTY>
<!ELEMENT VIDEO EMPTY>
<!ELEMENT BBS EMPTY>
<!ELEMENT MODEM EMPTY>
<!ELEMENT ISDN EMPTY>
<!ELEMENT PCS EMPTY>
<!ELEMENT INTERNET EMPTY>
<!ELEMENT X400 EMPTY>
<!-- Format type parameter. -->
<!ELEMENT TYPE (#PCDATA)>
<!-- Base64 encoded binary value. -->
<!ELEMENT BINVAL (#PCDATA)>
<!-- URI to external binary value -->
<!ELEMENT EXTVAL (#PCDATA)>
<!-- ==== -->
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3c.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="Openfire">
<xs:complexType>
<xs:sequence>
......@@ -11,8 +11,8 @@
<xs:element name="User">
<xs:complexType>
<xs:all>
<xs:element ref="Username" use="required" />
<xs:element ref="Password" use="required" />
<xs:element ref="Username" />
<xs:element ref="Password" />
<xs:element ref="Email" />
<xs:element ref="Name" />
<xs:element ref="CreationDate" />
......
<?xml version='1.0'?>
<!-- <!DOCTYPE xs:schema PUBLIC "-//W3C//DTD XMLSCHEMA 200102//EN" "XMLSchema.dtd" > -->
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
<xs:annotation>
<xs:documentation>
See http://www.w3.org/XML/1998/namespace.html and
http://www.w3.org/TR/REC-xml for information about this namespace.
This schema document describes the XML namespace, in a form
suitable for import by other schema documents.
Note that local names in this namespace are intended to be defined
only by the World Wide Web Consortium or its subgroups. The
following names are currently defined in this namespace and should
not be used with conflicting semantics by any Working Group,
specification, or document instance:
base (as an attribute name): denotes an attribute whose value
provides a URI to be used as the base for interpreting any
relative URIs in the scope of the element on which it
appears; its value is inherited. This name is reserved
by virtue of its definition in the XML Base specification.
lang (as an attribute name): denotes an attribute whose value
is a language code for the natural language of the content of
any element; its value is inherited. This name is reserved
by virtue of its definition in the XML specification.
space (as an attribute name): denotes an attribute whose
value is a keyword indicating what whitespace processing
discipline is intended for the content of the element; its
value is inherited. This name is reserved by virtue of its
definition in the XML specification.
Father (in any context at all): denotes Jon Bosak, the chair of
the original XML Working Group. This name is reserved by
the following decision of the W3C XML Plenary and
XML Coordination groups:
In appreciation for his vision, leadership and dedication
the W3C XML Plenary on this 10th day of February, 2000
reserves for Jon Bosak in perpetuity the XML name
xml:Father
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>This schema defines attributes and an attribute group
suitable for use by
schemas wishing to allow xml:base, xml:lang or xml:space attributes
on elements they define.
To enable this, such a schema must import this schema
for the XML namespace, e.g. as follows:
&lt;schema . . .>
. . .
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/03/xml.xsd"/>
Subsequently, qualified reference to any of the attributes
or the group defined below will have the desired effect, e.g.
&lt;type . . .>
. . .
&lt;attributeGroup ref="xml:specialAttrs"/>
will define a type which will schema-validate an instance
element with any of those attributes</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>In keeping with the XML Schema WG's standard versioning
policy, this schema document will persist at
http://www.w3.org/2001/03/xml.xsd.
At the date of issue it can also be found at
http://www.w3.org/2001/xml.xsd.
The schema document at that URI may however change in the future,
in order to remain compatible with the latest version of XML Schema
itself. In other words, if the XML Schema namespace changes, the version
of this document at
http://www.w3.org/2001/xml.xsd will change
accordingly; the version at
http://www.w3.org/2001/03/xml.xsd will not change.
</xs:documentation>
</xs:annotation>
<xs:attribute name="lang" type="xs:language">
<xs:annotation>
<xs:documentation>In due course, we should install the relevant ISO 2- and 3-letter
codes as the enumerated possible values . . .</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="space" default="preserve">
<xs:simpleType>
<xs:restriction base="xs:NCName">
<xs:enumeration value="default"/>
<xs:enumeration value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="base" type="xs:anyURI">
<xs:annotation>
<xs:documentation>See http://www.w3.org/TR/xmlbase/ for
information about this attribute.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attributeGroup name="specialAttrs">
<xs:attribute ref="xml:base"/>
<xs:attribute ref="xml:lang"/>
<xs:attribute ref="xml:space"/>
</xs:attributeGroup>
</xs:schema>
......@@ -6,8 +6,8 @@
<name>User Import Export</name>
<description>Enables import and export of user data</description>
<author>Ryan Graham</author>
<version>2.5.0</version>
<date>10/12/2015</date>
<version>2.6.0</version>
<date>09/26/2016</date>
<minServerVersion>4.0.0</minServerVersion>
<adminconsole>
......
......@@ -183,13 +183,14 @@ userImportExport.jar file over the existing file.</p>
<p>Presently, there is nothing that can be configured for the user import/export plugin.</p>
<h2>Using the Plugin</h2>
<p>The plugin is accessed via the "User Import & Export" sidebar item located under the
<p>The plugin is accessed via the "User Import &amp; Export" sidebar item located under the
"Users/Groups" tab in the Admin Console. Note: if you are using a read-only user store such as LDAP
or POP3 this plugin will still work with two caveats:
<ol>
<li>When exporting, the username will be placed in the password element.
<li>When importing, no new users will be created but if the user exists in the user store thier roster will be loaded.
<li>When importing, no new users will be created but if the user exists in the user store the roster will be loaded.
</ol>
<li><strong>Importing</strong> - Select the "Import User Data" option from the user import/export selection
page. On the import page, use the "Browse" button to locate the file that contains the user
information you want to locate and then click on the "Import" button. If the plugin is successful
......@@ -198,7 +199,7 @@ If the plugin was not successful in importing all user data you, will receive a
what might have gone wrong. If during the import process, the plugin detects that you are trying to
import a user that already exists in the system, it will not import that user or any roster
information, except in the case of using a read-only user store.</li>
<br>
<br/>
<li><strong>Exporting</strong> - Select the "Export User Data" option from the user import/export selection
page. User data can be exported either to a file or directly to the screen. To export to a file,
select the "To File" radio button, enter the name you want your export file to be called in the
......@@ -206,9 +207,9 @@ select the "To File" radio button, enter the name you want your export file to b
an ".xml" extension to the file name if it is not already present. To export to the screen, select
the "To Screen" radio button and then click on the "Export" button. The user data will be placed in
the provided text area.</li>
<br>
<br/>
<li><strong>Migration</strong> - To import user data from another instant messaging system using the plugin,
the import file must conform to the openfire-user-schema.xsd.xml schema file (located in the classes
the import file must conform to the wildfire-user-schema.xsd.xml schema file (located in the classes
directory of the userImportExport.jar). When importing a user data file the plugin will first validate
the file against the schema file. If the plugin cannot validate the import file the user data will
not be imported. During the import process the plugin gives you the ability to update user roster
......@@ -406,6 +407,15 @@ Below is a list of the different status types and what their associated numbers
</table>
</div>
<p>
<li><strong>XEP-0227 Compliance</strong> - Both Import and Export have an option to
enable to use the format as defined in <a href="http://www.xmpp.org/extensions/xep-0227.html"
target="_blank">XEP-0227</a>. This format is intended as a Portable Import/Export Format for
XMPP-IM Servers. When importing, the xml input file will be checked against a set of xsd's
which are compliant with XEPP-0227. These are located in the classes directory of the userImportExport.jar.
</li>
</p>
</div>
</div>
......
/**
*
* Copyright 2016 Ryan Graham
*
* 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.openfire.plugin;
import gnu.inet.encoding.Stringprep;
import gnu.inet.encoding.StringprepException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang.StringEscapeUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterItemProvider;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.user.UserProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* The user import/export plugin provides a way to import and export Openfire
......@@ -49,23 +48,17 @@ public class ImportExportPlugin implements Plugin {
private static final Logger Log = LoggerFactory.getLogger(ImportExportPlugin.class);
private UserManager userManager;
private UserProvider provider;
private String serverName;
public ImportExportPlugin() {
userManager = XMPPServer.getInstance().getUserManager();
provider = UserManager.getUserProvider();
serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
}
public void initializePlugin(PluginManager manager, File pluginDirectory) {
}
public void destroyPlugin() {
userManager = null;
provider = null;
serverName = null;
}
/**
......@@ -84,11 +77,11 @@ public class ImportExportPlugin implements Plugin {
* @return a byte[] of the user data.
* @throws IOException if there's a problem writing to the XMLWriter.
*/
public byte[] exportUsersToByteArray() throws IOException {
public byte[] exportUsersToByteArray(boolean xep227Support) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
writer.write(exportUsers());
writer.write(exportUsers(xep227Support));
return out.toByteArray();
}
......@@ -100,12 +93,12 @@ public class ImportExportPlugin implements Plugin {
* @return a formatted String representation of the user data.
* @throws IOException if there's a problem writing to the XMLWriter.
*/
public String exportUsersToString() throws IOException {
public String exportUsersToString(boolean xep227Support) throws IOException {
StringWriter stringWriter = new StringWriter();
XMLWriter writer = null;
try {
writer = new XMLWriter(stringWriter, OutputFormat.createPrettyPrint());
writer.write(exportUsers());
writer.write(exportUsers(xep227Support));
} catch (IOException ioe) {
Log.error(ioe.getMessage(), ioe);
throw ioe;
......@@ -115,7 +108,7 @@ public class ImportExportPlugin implements Plugin {
}
}
return stringWriter.toString();
return StringEscapeUtils.escapeHtml(stringWriter.toString());
}
/**
......@@ -132,10 +125,11 @@ public class ImportExportPlugin implements Plugin {
* @throws IOException if there is a problem reading the FileItem.
* @throws DocumentException if an error occurs during parsing.
*/
public List<String> importUserData(FileItem file, String previousDomain) throws DocumentException, IOException {
SAXReader reader = new SAXReader();
Document document = reader.read(file.getInputStream());
return importUsers(document, previousDomain);
public List<String> importUserData(FileItem file, String previousDomain, boolean xep227Support) throws DocumentException, IOException {
InExporter exporter = XMLImportExportFactory.getExportInstance(xep227Support);
return exporter.importUsers(file.getInputStream(), previousDomain,isUserProviderReadOnly());
}
/**
......@@ -144,9 +138,11 @@ public class ImportExportPlugin implements Plugin {
* @param file a FileItem to be validated.
* @return True if FileItem matches the openfire user schema.
*/
public boolean validateImportFile(FileItem file) {
public boolean validateImportFile(FileItem usersFile, boolean xep227Support) {
try {
return new UserSchemaValidator(file, "wildfire-user-schema.xsd.xml").validate();
InExporter exporter = XMLImportExportFactory.getExportInstance(xep227Support);
return exporter.validate(usersFile.getInputStream());
}
catch (Exception e) {
Log.error(e.getMessage(), e);
......@@ -154,160 +150,11 @@ public class ImportExportPlugin implements Plugin {
}
}
private Document exportUsers() {
Document document = DocumentHelper.createDocument();
Element root = document.addElement("Openfire");
Collection<User> users = userManager.getUsers();
for (User user : users) {
Element userElement = root.addElement("User");
String userName = user.getUsername();
userElement.addElement("Username").addText(userName);
try {
userElement.addElement("Password").addText(AuthFactory.getPassword(user.getUsername()));
}
catch (UserNotFoundException e) {
Log.info("User " + userName + " not found, setting their password to their username");
userElement.addElement("Password").addText(userName);
}
catch (UnsupportedOperationException e) {
Log.info("Unable to retrieve " + userName + " password, setting their password to their username");
userElement.addElement("Password").addText(userName);
}
userElement.addElement("Email").addText(user.getEmail() == null ? "" : user.getEmail());
String name = user.getName();
userElement.addElement("Name").addText(name == null ? "" : name);
//creation and modified datte are not used as part of the import process but are exported
//for historical purposes, should they be formatted differently?
userElement.addElement("CreationDate").addText(String.valueOf(user.getCreationDate().getTime()));
userElement.addElement("ModifiedDate").addText(String.valueOf(user.getModificationDate().getTime()));
Element rosterElement = userElement.addElement("Roster");
Collection<RosterItem> roster = user.getRoster().getRosterItems();
for (RosterItem ri : roster) {
Element itemElement = rosterElement.addElement("Item");
itemElement.addAttribute("jid", ri.getJid().toBareJID());
itemElement.addAttribute("askstatus", String.valueOf(ri.getAskStatus().getValue()));
itemElement.addAttribute("recvstatus", String.valueOf(ri.getRecvStatus().getValue()));
itemElement.addAttribute("substatus", String.valueOf(ri.getSubStatus().getValue()));
itemElement.addAttribute("name", ri.getNickname());
List<String> groups = ri.getGroups();
for (String group : groups) {
if (group != null && group.trim().length() > 0) {
itemElement.addElement("Group").addText(group);
}
}
}
}
return document;
}
private List<String> importUsers(Document document, String previousDomain) {
List<String> invalidUsers = new ArrayList<String>();
UserManager userManager = UserManager.getInstance();
RosterItemProvider rosterItemProvider = RosterManager.getRosterItemProvider();
Element users = document.getRootElement();
Iterator<Element> usersIter = users.elementIterator("User");
while (usersIter.hasNext()) {
Element user = usersIter.next();
String userName = null;
String password = null;
String email = null;
String name = null;
List<RosterItem> rosterItems = new ArrayList<RosterItem>();
Iterator<Element> userElements = user.elementIterator();
while (userElements.hasNext()) {
Element userElement = userElements.next();
String nameElement = userElement.getName();
if ("Username".equals(nameElement)) {
userName = userElement.getText();
}
else if ("Password".equals(nameElement)) {
password = userElement.getText();
}
else if ("Name".equals(nameElement)) {
name = userElement.getText();
}
else if ("Email".equals(nameElement)) {
email = userElement.getText();
}
else if ("Roster".equals(nameElement)) {
Iterator<Element> rosterIter = userElement.elementIterator("Item");
while (rosterIter.hasNext()) {
Element rosterElement = rosterIter.next();
String jid = rosterElement.attributeValue("jid");
String askstatus = rosterElement.attributeValue("askstatus");
String recvstatus = rosterElement.attributeValue("recvstatus");
String substatus = rosterElement.attributeValue("substatus");
String nickname = rosterElement.attributeValue("name");
private Document exportUsers(boolean xep227Support) {
List<String> groups = new ArrayList<String>();
Iterator<Element> groupIter = rosterElement.elementIterator("Group");
while (groupIter.hasNext()) {
Element group = groupIter.next();
String groupName = group.getText();
if (groupName != null && groupName.trim().length() > 0) {
groups.add(groupName);
}
}
InExporter exporter = XMLImportExportFactory.getExportInstance(xep227Support);
//used for migration
if (previousDomain != null) {
jid = jid.replace(previousDomain, serverName);
return exporter.exportUsers();
}
rosterItems.add(new RosterItem(new JID(jid),
RosterItem.SubType.getTypeFromInt(Integer.parseInt(substatus)),
RosterItem.AskType.getTypeFromInt(Integer.parseInt(askstatus)),
RosterItem.RecvType.getTypeFromInt(Integer.parseInt(recvstatus)),
nickname,
groups));
}
}
}
if ((userName != null) && (password != null)) {
try {
userName = Stringprep.nodeprep(userName);
if (!isUserProviderReadOnly()) {
userManager.createUser(userName, password, name, email);
}
//Check to see user exists before adding their roster, this is for read-only user providers.
userManager.getUser(userName);
for (RosterItem ri : rosterItems) {
rosterItemProvider.createItem(userName, ri);
}
}
catch (StringprepException se) {
Log.info("Invalid username " + userName);
invalidUsers.add(userName);
}
catch (UserAlreadyExistsException e) {
Log.info("User already exists " + userName);
invalidUsers.add(userName);
}
catch (UserNotFoundException e) {
Log.info("User not found " + userName);
invalidUsers.add(userName);
}
}
}
return invalidUsers;
}
}
/**
*
* Copyright 2016 Anno van Vliet
*
* 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.openfire.plugin;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
/**
* An interface to an In and Exporter.
*
* @author Anno van Vliet
*
*/
public interface InExporter {
/**
* Export the users in a Document
* @param userManager
*
* @return
*/
Document exportUsers();
/**
* Validate the xml to the correct XSD.
* @param doc
*
* @return is Valid xml
* @throws IOException
* @throws DocumentException
*/
boolean validate(InputStream doc);
/**
* Import users
*
* @param inputStream
* @param previousDomain
* @param isUserProviderReadOnly
* @return
*/
List<String> importUsers(InputStream inputStream, String previousDomain, boolean isUserProviderReadOnly);
}
/**
*
* Copyright 2016 Anno van Vliet
*
* 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.openfire.plugin;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.DOMReader;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterItemProvider;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import gnu.inet.encoding.Stringprep;
import gnu.inet.encoding.StringprepException;
/**
* Export and import Users in the Openfire XML format.
*
* @author Anno van Vliet
*
*/
public class OpenfireExporter implements InExporter {
private static final Logger Log = LoggerFactory.getLogger(OpenfireExporter.class);
private final String serverName;
private final UserManager userManager;
private final RosterItemProvider rosterItemProvider;
/**
*
*/
public OpenfireExporter() {
serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
userManager = UserManager.getInstance();
rosterItemProvider = RosterManager.getRosterItemProvider();
}
/**
* @param serverName
* @param userManager
* @param rosterItemProvider
*/
public OpenfireExporter(String serverName, UserManager userManager, RosterItemProvider rosterItemProvider) {
super();
this.serverName = serverName;
this.userManager = userManager;
this.rosterItemProvider = rosterItemProvider;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.plugin.Exporter#exportUsers()
*/
@Override
public Document exportUsers() {
Log.debug("exportUsers");
Document document = DocumentHelper.createDocument();
Element root = document.addElement("Openfire");
Collection<User> users = userManager.getUsers();
for (User user : users) {
Element userElement = root.addElement("User");
String userName = user.getUsername();
userElement.addElement("Username").addText(userName);
try {
String pw = AuthFactory.getPassword(user.getUsername());
userElement.addElement("Password").addText(pw);
}
catch (UserNotFoundException e) {
Log.info("User " + userName + " not found, setting their password to their username");
userElement.addElement("Password").addText(userName);
}
catch (UnsupportedOperationException e) {
Log.info("Unable to retrieve " + userName + " password, setting their password to their username");
userElement.addElement("Password").addText(userName);
}
userElement.addElement("Email").addText(user.getEmail() == null ? "" : user.getEmail());
String name = user.getName();
userElement.addElement("Name").addText(name == null ? "" : name);
//creation and modified date are not used as part of the import process but are exported
//for historical purposes, should they be formatted differently?
userElement.addElement("CreationDate").addText(String.valueOf(user.getCreationDate().getTime()));
userElement.addElement("ModifiedDate").addText(String.valueOf(user.getModificationDate().getTime()));
Element rosterElement = userElement.addElement("Roster");
Collection<RosterItem> roster = user.getRoster().getRosterItems();
for (RosterItem ri : roster) {
Element itemElement = rosterElement.addElement("Item");
itemElement.addAttribute("jid", ri.getJid().toBareJID());
itemElement.addAttribute("askstatus", String.valueOf(ri.getAskStatus().getValue()));
itemElement.addAttribute("recvstatus", String.valueOf(ri.getRecvStatus().getValue()));
itemElement.addAttribute("substatus", String.valueOf(ri.getSubStatus().getValue()));
itemElement.addAttribute("name", ri.getNickname());
List<String> groups = ri.getGroups();
for (String group : groups) {
if (group != null && group.trim().length() > 0) {
itemElement.addElement("Group").addText(group);
}
}
}
}
return document;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.plugin.InExporter#validate()
*/
@Override
public boolean validate(InputStream file) {
Log.debug("validate");
org.w3c.dom.Document doc = new UserSchemaValidator(file, "wildfire-user-schema.xsd.xml").validateAndParse();
return ( doc != null);
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.plugin.InExporter#importUsers(java.io.InputStream, java.lang.String, boolean)
*/
@Override
public List<String> importUsers(InputStream inputStream, String previousDomain, boolean isUserProviderReadOnly) {
Log.debug("importUsers");
DOMReader xmlReader = new DOMReader();
Document doc = xmlReader.read(new UserSchemaValidator(inputStream).validateAndParse());
return importUsers(doc, previousDomain, isUserProviderReadOnly);
}
/*
* (non-Javadoc)
*
* @see
* org.jivesoftware.openfire.plugin.InExporter#importUsers(org.dom4j.Document,
* java.lang.String)
*/
@SuppressWarnings("unchecked")
private List<String> importUsers(Document document, String previousDomain, boolean isUserProviderReadOnly) {
Log.debug("importUsers");
List<String> invalidUsers = new ArrayList<String>();
Element users = document.getRootElement();
Iterator<Element> usersIter = users.elementIterator("User");
while (usersIter.hasNext()) {
Element user = usersIter.next();
String userName = null;
String password = null;
String email = null;
String name = null;
List<RosterItem> rosterItems = new ArrayList<RosterItem>();
Iterator<Element> userElements = user.elementIterator();
while (userElements.hasNext()) {
Element userElement = userElements.next();
String nameElement = userElement.getName();
if ("Username".equals(nameElement)) {
userName = userElement.getText();
} else if ("Password".equals(nameElement)) {
password = userElement.getText();
} else if ("Name".equals(nameElement)) {
name = userElement.getText();
} else if ("Email".equals(nameElement)) {
email = userElement.getText();
} else if ("Roster".equals(nameElement)) {
Iterator<Element> rosterIter = userElement.elementIterator("Item");
while (rosterIter.hasNext()) {
Element rosterElement = rosterIter.next();
String jid = rosterElement.attributeValue("jid");
String askstatus = rosterElement.attributeValue("askstatus");
String recvstatus = rosterElement.attributeValue("recvstatus");
String substatus = rosterElement.attributeValue("substatus");
String nickname = rosterElement.attributeValue("name");
List<String> groups = new ArrayList<String>();
Iterator<Element> groupIter = rosterElement.elementIterator("Group");
while (groupIter.hasNext()) {
Element group = groupIter.next();
String groupName = group.getText();
if (groupName != null && groupName.trim().length() > 0) {
groups.add(groupName);
}
}
// used for migration
if (previousDomain != null) {
jid = jid.replace(previousDomain, serverName);
}
rosterItems.add(new RosterItem(new JID(jid), RosterItem.SubType.getTypeFromInt(Integer.parseInt(substatus)),
RosterItem.AskType.getTypeFromInt(Integer.parseInt(askstatus)), RosterItem.RecvType.getTypeFromInt(Integer.parseInt(recvstatus)),
nickname, groups));
}
}
}
if ((userName != null) && (password != null)) {
try {
userName = Stringprep.nodeprep(userName);
if (isUserProviderReadOnly) {
userManager.createUser(userName, password, name, email);
}
// Check to see user exists before adding their roster, this is for
// read-only user providers.
userManager.getUser(userName);
for (RosterItem ri : rosterItems) {
rosterItemProvider.createItem(userName, ri);
}
} catch (StringprepException se) {
Log.info("Invalid username " + userName);
invalidUsers.add(userName);
} catch (UserAlreadyExistsException e) {
Log.info("User already exists " + userName);
invalidUsers.add(userName);
} catch (UserNotFoundException e) {
Log.info("User not found " + userName);
invalidUsers.add(userName);
}
}
}
return invalidUsers;
}
}
/**
*
* Copyright 2016 Anno van Vliet
*
* 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.openfire.plugin;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import org.apache.commons.fileupload.FileItem;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;
import org.dom4j.io.SAXWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ContentHandler;
import org.w3c.dom.Document;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import com.sun.msv.reader.util.GrammarLoader;
import com.sun.msv.reader.util.IgnoreController;
import com.sun.msv.verifier.DocumentDeclaration;
import com.sun.msv.verifier.Verifier;
/**
* An utility to convert a file to XML, and validate the XML against a set of XML Schema files.
*
* @author Anno van Vliet
*
*/
public class UserSchemaValidator {
private static final Logger Log = LoggerFactory.getLogger(UserSchemaValidator.class);
private Document doc;
private String schema;
private static final CharSequence STRICT_DECLARATION_MSG = "The matching wildcard is strict, but no declaration can be found for element";
UserSchemaValidator(FileItem usersFile, String schemaFile) throws DocumentException, IOException {
SAXReader reader = new SAXReader();
doc = reader.read(usersFile.getInputStream());
private final InputStream source;
private final Source[] schemaSources;
URL schemaURL = this.getClass().getClassLoader().getResource(schemaFile);
schema = schemaURL.toExternalForm();
}
/**
* Construct a Validator object which parses and validates a input source.
*
* @param source XML input document
* @param schemaFile zero or more schema files.
*/
UserSchemaValidator(InputStream source, String... schemaFile) {
this.source = source;
List<Source> sourceList = new ArrayList<Source>();
boolean validate() {
for (String schema : schemaFile) {
try {
SAXParserFactory saxFactory = SAXParserFactory.newInstance();
saxFactory.setNamespaceAware(true);
DocumentDeclaration docDeclaration = GrammarLoader.loadVGM(schema, new IgnoreController() {
@Override
public void error(Locator[] locations,
String message,
Exception exception) {
Log.error("ERROR: " + message);
URL schemaURL = this.getClass().getClassLoader().getResource(schema);
if (schemaURL != null) {
sourceList.add(new StreamSource(schemaURL.openStream()));
} else {
Log.warn("Cannot find schema definition " + schema);
}
} catch (IOException e) {
Log.warn("Cannot open schema definition " + schema + " : " + e.getMessage());
Log.debug("", e);
}
}
schemaSources = new Source[sourceList.size()];
sourceList.toArray(schemaSources);
public void error(Locator[] locations, String message) {
Log.error("WARNING: " + message);
}
}, saxFactory);
ValidatorErrorHandler validatorErrorHandler = new ValidatorErrorHandler();
Verifier verifier = new Verifier(docDeclaration, validatorErrorHandler);
/**
* Perform a validate and a parse of the specified source document.
* Validating is done with the specified schemafiles.
*
* @return A Document when the validation s successful.
*/
Document validateAndParse() {
ValidatorErrorHandler handler = new ValidatorErrorHandler();
try {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setXIncludeAware(true);
documentBuilderFactory.setValidating(false);
// We don't want xml:base and xml:lang attributes in the output.
documentBuilderFactory.setFeature(
"http://apache.org/xml/features/xinclude/fixup-base-uris", false);
documentBuilderFactory.setFeature(
"http://apache.org/xml/features/xinclude/fixup-language", false);
SAXWriter writer = new SAXWriter((ContentHandler) verifier);
writer.setErrorHandler(validatorErrorHandler);
if ( schemaSources.length > 0 ) {
Log.info("Checking Schema's");
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaFactory.setErrorHandler(handler);
Schema schema = schemaFactory.newSchema(schemaSources);
documentBuilderFactory.setSchema(schema);
Log.info("Start validating document");
writer.write(doc);
if (verifier.isValid()) {
return true;
} else {
Log.error(doc.getName() + " is invalid.");
return false;
Log.info("Loading document");
}
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
handler.reset();
// Communicate some info about the Xincludes in the imported file. These imports should be resolvable by the server.
documentBuilder.setEntityResolver( new EntityResolver() {
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
Log.info(String.format("resolved Entity:%s %s", (publicId != null ? publicId : "") , systemId));
return null;
}
} );
documentBuilder.setErrorHandler(handler);
Document result = documentBuilder.parse(source);
if (result != null && handler.isValid()) {
return result;
} else {
Log.warn(String.format("document is invalid. %1$d errors found.", handler.getNrOfErrors()));
return null;
}
} catch (Exception e) {
Log.error(e.getMessage(), e);
return false;
Log.warn(String.format("document validation failed. %1$d errors found.", handler.getNrOfErrors()));
Log.debug("", e);
return null;
}
}
private class ValidatorErrorHandler implements ErrorHandler {
private int nrOfErrors = 0;
public void error(SAXParseException e) {
Log.error("ERROR:" + e);
if (e.getMessage().contains(STRICT_DECLARATION_MSG)) {
Log.warn("This error indicates there is no XML Schema to validate the refered element: " + e.getLocalizedMessage());
} else {
Log.warn("ERROR:" + e.getLocalizedMessage());
nrOfErrors++;
}
}
public void fatalError(SAXParseException e) {
Log.error("Fatal:" + e);
Log.error("Fatal:" + e.getLocalizedMessage());
nrOfErrors++;
}
public void warning(SAXParseException e) {
Log.error("Warning:" + e);
Log.error("Warning:" + e.getLocalizedMessage());
}
public void reset() {
nrOfErrors = 0;
}
public boolean isValid() {
return nrOfErrors == 0;
}
/**
* @return the nrOfErrors
*/
public int getNrOfErrors() {
return nrOfErrors;
}
}
}
/**
*
* Copyright 2016 Anno van Vliet
*
* 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.openfire.plugin;
/**
* Factory to instantiate the correct InExporter.
*
* @author Anno van Vliet
*
*/
public class XMLImportExportFactory {
/**
* @param xep227Support
* @return
*/
public static InExporter getExportInstance(boolean xep227Support) {
if ( xep227Support ) {
return new Xep227Exporter();
} else {
return new OpenfireExporter();
}
}
}
/**
*
* Copyright 2016 Anno van Vliet
*
* 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.openfire.plugin;
import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.io.DOMReader;
import org.jivesoftware.openfire.OfflineMessage;
import org.jivesoftware.openfire.OfflineMessageStore;
import org.jivesoftware.openfire.PrivateStorage;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterItem.AskType;
import org.jivesoftware.openfire.roster.RosterItem.RecvType;
import org.jivesoftware.openfire.roster.RosterItem.SubType;
import org.jivesoftware.openfire.roster.RosterItemProvider;
import org.jivesoftware.openfire.roster.RosterManager;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.util.XMPPDateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import gnu.inet.encoding.Stringprep;
import gnu.inet.encoding.StringprepException;
/**
* A In and Exporter which conforms to XEP-0227.
*
* @author Anno van Vliet
*
*/
public class Xep227Exporter implements InExporter {
/**
* constants defining field and attribute names
*/
private static final String V_CARD_NAME = "vCard";
private static final String ASK_SUBSCRIBE_ENUM = "subscribe";
private static final String STAMP_NAME = "stamp";
private static final String DELAY_ELEMENT_NAME = "delay";
private static final String FROM_NAME = "from";
private static final String MESSAGE_ELEMENT_NAME = "message";
private static final String OFFLINE_MESSAGES_ELEMENT_NAME = "offline-messages";
private static final String GROUP_ELEMENT_NAME = "group";
private static final String SUBSCRIPTION_NAME = "subscription";
private static final String ASK_NAME = "ask";
private static final String ITEM_ELEMENT_NAME = "item";
private static final String QUERY_ELEMENT_NAME = "query";
private static final String PASSWORD_NAME = "password";
private static final String NAME_NAME = "name";
private static final String USER_ELEMENT_NAME = "user";
private static final String JID_NAME = "jid";
private static final String HOST_ELEMENT_NAME = "host";
private static final String SERVER_DATA_ELEMENT_NAME = "server-data";
/**
* the relevant namespaces
*/
private static final String JABBER_CLIENT_NS = "jabber:client";
private static final String VCARD_TEMP_NS = "vcard-temp";
private static final String JABBER_IQ_ROSTER_NS = "jabber:iq:roster";
private static final String URN_XMPP_PIE_0_NS = "urn:xmpp:pie:0";
private static final Namespace JABBER_MSG_NS = new Namespace("", JABBER_CLIENT_NS);
private static final Logger Log = LoggerFactory.getLogger(Xep227Exporter.class);
private final String serverName;
private final OfflineMessageStore offlineMessagesStore;
private final VCardManager vCardManager;
//private final PrivateStorage privateStorage;
private final UserManager userManager;
private final RosterItemProvider rosterItemProvider;
private final DateFormat dateformater = new SimpleDateFormat(XMPPDateTimeFormat.XMPP_DATETIME_FORMAT, Locale.US);
/**
*
*/
public Xep227Exporter() {
offlineMessagesStore = XMPPServer.getInstance()
.getOfflineMessageStore();
serverName = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
//TODO not yet implemented
//privateStorage = XMPPServer.getInstance().getPrivateStorage();
vCardManager = VCardManager.getInstance();
userManager = UserManager.getInstance();
rosterItemProvider = RosterManager.getRosterItemProvider();
}
/**
* Constructor used for testing purposes.
*
* @param serverName
* @param offlineMessagesStore
* @param vCardManager
* @param privateStorage
* @param userManager
* @param rosterItemProvider
*/
public Xep227Exporter(String serverName, OfflineMessageStore offlineMessagesStore, VCardManager vCardManager, PrivateStorage privateStorage,
UserManager userManager, RosterItemProvider rosterItemProvider) {
super();
this.serverName = serverName;
this.offlineMessagesStore = offlineMessagesStore;
this.vCardManager = vCardManager;
//this.privateStorage = privateStorage;
this.userManager = userManager;
this.rosterItemProvider = rosterItemProvider;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.plugin.Exporter#exportUsers(org.jivesoftware.openfire.user.UserManager)
*/
@Override
public Document exportUsers() {
Log.debug("exportUsers");
Document document = DocumentHelper.createDocument();
Element root = document.addElement(SERVER_DATA_ELEMENT_NAME, URN_XMPP_PIE_0_NS);
Element host = root.addElement(HOST_ELEMENT_NAME);
host.addAttribute(JID_NAME, serverName);
Collection<User> users = userManager.getUsers();
for (User user : users) {
String userName = user.getUsername();
Element userElement = host.addElement(USER_ELEMENT_NAME);
exportUser(userElement, user);
exportOfflineMessages(serverName, userElement, userName);
exportRoster(userElement, user);
exportVCard(userElement, userName);
exportPrivateStorage(userName, userElement);
}
return document;
}
/**
* Adding heading of an user and its parameters
*
* @param userElement
* DOM element
* @param user
* User object
*/
private void exportUser(Element userElement, User user) {
String userName = user.getUsername();
userElement.addAttribute(NAME_NAME, userName);
try {
String pw = AuthFactory.getPassword(userName);
userElement.addAttribute(PASSWORD_NAME, pw );
} catch (UserNotFoundException e) {
Log.info("User " + userName
+ " not found, setting their password to their username");
userElement.addAttribute(PASSWORD_NAME, userName);
} catch (UnsupportedOperationException e) {
Log.info("Unable to retrieve " + userName
+ " password, setting their password to their username");
userElement.addAttribute(PASSWORD_NAME, userName);
}
}
/**
* Add roster and its groups to a DOM element
*
* @param userElement
* DOM element
* @param user
* User
*/
private void exportRoster(Element userElement, User user) {
Element rosterElement = userElement.addElement(QUERY_ELEMENT_NAME,
JABBER_IQ_ROSTER_NS);
Collection<RosterItem> roster = user.getRoster().getRosterItems();
for (RosterItem ri : roster) {
Element itemElement = rosterElement.addElement(ITEM_ELEMENT_NAME);
itemElement.addAttribute(JID_NAME, ri.getJid().toBareJID());
itemElement.addAttribute(NAME_NAME, ri.getNickname());
itemElement.addAttribute(SUBSCRIPTION_NAME, ri.getSubStatus()
.getName());
if ( ri.getAskStatus() == AskType.SUBSCRIBE ) {
itemElement.addAttribute(ASK_NAME, ASK_SUBSCRIBE_ENUM);
}
/**
* Adding groups
*/
Element groupElement = itemElement.addElement(GROUP_ELEMENT_NAME);
List<String> groups = ri.getGroups();
for (String group : groups) {
groupElement.addText(group);
}
}
}
/**
* Adding offline messages, if there are some.
*
* @param hostname
* host name
* @param userElement
* DOM element
* @param userName
* user name
*/
@SuppressWarnings("unchecked")
private void exportOfflineMessages(String hostname, Element userElement,
String userName) {
Collection<OfflineMessage> offlineMessages = offlineMessagesStore
.getMessages(userName, false);
if (!offlineMessages.isEmpty()) {
Element offlineElement = userElement.addElement(OFFLINE_MESSAGES_ELEMENT_NAME);
for (OfflineMessage offMessage : offlineMessages) {
Element messageElement = offlineElement.addElement(new QName(MESSAGE_ELEMENT_NAME, JABBER_MSG_NS));
for ( Object att : offMessage.getElement().attributes() ) {
Attribute attribute = (Attribute) att;
messageElement.addAttribute(attribute.getQName(),attribute.getValue());
}
for (Iterator<Element> iterator = offMessage.getElement().elementIterator(); iterator
.hasNext();) {
Element element = iterator.next();
messageElement.add(element.createCopy(new QName(element.getName(), (element.getNamespace() == Namespace.NO_NAMESPACE ? JABBER_MSG_NS : element.getNamespace()))));
}
/**
* Adding delay element
*/
Element delayElement = messageElement.addElement("delay", "urn:xmpp:delay");
delayElement.addAttribute(FROM_NAME, hostname);
delayElement.addAttribute("stamp", XMPPDateTimeFormat.format(offMessage.getCreationDate()));
delayElement.addText("Offline Storage");
}
}
}
/**
* Adding vcard element
*
* @param userElement
* DOM element
* @param userName
* user name
*/
@SuppressWarnings("unchecked")
private void exportVCard(Element userElement, String userName) {
Element vCard = vCardManager.getVCard(userName);
if (vCard != null) {
Element vCardElement = userElement
.addElement(V_CARD_NAME, VCARD_TEMP_NS);
for (Iterator<Element> iterator = vCard.elementIterator(); iterator
.hasNext();) {
Element element = iterator.next();
vCardElement.add(element.createCopy());
}
}
}
/**
* Add all the private stored information (XEP-0049)
* <b>Note: this method is not supported in the available openfire releases.
*
* </b>
* @param userName User name
* @param userElement User element
*/
private void exportPrivateStorage(String userName, Element userElement) {
// Element result = privateStorage.getAll(userName);
// if (result.elements().size() > 0) {
// userElement.add(result.createCopy());
// }
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.plugin.InExporter#validate()
*/
@Override
public boolean validate(InputStream file) {
Log.debug("validate");
org.w3c.dom.Document doc = new UserSchemaValidator(file, "pie.xsd", "jabber-iq-roster.xsd", "jabber-iq-private.xsd","xml.xsd","stanzaerror.xsd","jabber-client.xsd").validateAndParse();
return ( doc != null );
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.plugin.InExporter#importUsers(java.io.InputStream, java.lang.String, boolean)
*/
@Override
public List<String> importUsers(InputStream inputStream, String previousDomain, boolean isUserProviderReadOnly) {
Log.debug("importUsers");
DOMReader xmlReader = new DOMReader();
Document doc = xmlReader.read(new UserSchemaValidator(inputStream).validateAndParse());
return importUsers(doc, previousDomain, isUserProviderReadOnly);
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.plugin.InExporter#importUsers(org.dom4j.Document, java.lang.String, boolean)
*/
@SuppressWarnings("unchecked")
private List<String> importUsers(Document document, String previousDomain, boolean isUserProviderReadOnly) {
List<String> invalidUsers = new ArrayList<String>();
Element hosts = document.getRootElement();
Iterator<Element> hostsIter = hosts.elementIterator(HOST_ELEMENT_NAME);
while (hostsIter.hasNext()) {
Element host = hostsIter.next();
Iterator<Element> usersIter = host.elementIterator(USER_ELEMENT_NAME);
while (usersIter.hasNext()) {
Element user = usersIter.next();
importUser(user,previousDomain,isUserProviderReadOnly,invalidUsers);
}
}
return invalidUsers;
}
/**
* @param user
* @param previousDomain
* @param isUserProviderReadOnly
* @param invalidUsers
*/
@SuppressWarnings("unchecked")
private void importUser(Element user, String previousDomain, boolean isUserProviderReadOnly, List<String> invalidUsers) {
Log.debug("importUser");
List<RosterItem> rosterItems = new ArrayList<RosterItem>();
List<OfflineMessage> offlineMessages = new ArrayList<OfflineMessage>();
Element vCardElement = null;
String userName = user.attributeValue(NAME_NAME);
String password = user.attributeValue(PASSWORD_NAME);
Iterator<Element> userElements = user.elementIterator();
while (userElements.hasNext()) {
Element userElement = userElements.next();
String nameElement = userElement.getName();
if (OFFLINE_MESSAGES_ELEMENT_NAME.equals(nameElement)) {
importOffLineMessages(userElement, offlineMessages);
} else if (QUERY_ELEMENT_NAME.equals(nameElement) && JABBER_IQ_ROSTER_NS.equals(userElement.getNamespaceURI())) {
importUserRoster(userElement, rosterItems, previousDomain);
} else if (V_CARD_NAME.equals(nameElement) && VCARD_TEMP_NS.equals(userElement.getNamespaceURI())) {
vCardElement = userElement;
}
}
if ( userName != null ) {
try {
userName = Stringprep.nodeprep(userName);
if (!isUserProviderReadOnly && (password != null) ) {
userManager.createUser(userName, password, userName, null);
}
if ( !isUserProviderReadOnly && vCardElement != null ) {
try {
vCardManager.setVCard(userName, vCardElement);
} catch (Exception e) {
Log.warn("Error updating VCard:" + userName + ":" + e.getMessage());
Log.debug("", e);
}
}
// Check to see user exists before adding their roster, this is for
// read-only user providers.
userManager.getUser(userName);
for (RosterItem ri : rosterItems) {
rosterItemProvider.createItem(userName, ri);
}
for (OfflineMessage offlineMessage : offlineMessages) {
offlineMessagesStore.addMessage(offlineMessage);
}
} catch (StringprepException se) {
Log.info("Invalid username " + userName);
invalidUsers.add(userName);
} catch (UserAlreadyExistsException e) {
Log.info("User already exists " + userName);
invalidUsers.add(userName);
} catch (UserNotFoundException e) {
Log.info("User not found " + userName);
invalidUsers.add(userName);
} catch (Exception e) {
Log.warn("Error updating User:" + userName + ":" + e.getLocalizedMessage());
invalidUsers.add(userName);
}
}
}
/**
* @param userElement
* @param rosterItems
* @param previousDomain
*/
@SuppressWarnings("unchecked")
private void importUserRoster(Element userElement, List<RosterItem> rosterItems, String previousDomain) {
Log.debug("importUserRoster");
Iterator<Element> rosterIter = userElement.elementIterator(ITEM_ELEMENT_NAME);
while (rosterIter.hasNext()) {
Element rosterElement = rosterIter.next();
String jid = rosterElement.attributeValue(JID_NAME);
String nickname = rosterElement.attributeValue(NAME_NAME);
String substatus = rosterElement.attributeValue(SUBSCRIPTION_NAME);
String askstatus = rosterElement.attributeValue(ASK_NAME);
List<String> groups = new ArrayList<String>();
Iterator<Element> groupIter = rosterElement.elementIterator(GROUP_ELEMENT_NAME);
while (groupIter.hasNext()) {
Element group = groupIter.next();
String groupName = group.getText();
if (groupName != null && groupName.trim().length() > 0) {
groups.add(groupName);
}
}
// used for migration
if (previousDomain != null && jid != null ) {
jid = jid.replace(previousDomain, serverName);
}
try {
rosterItems.add(new RosterItem(new JID(jid),
( substatus != null ? SubType.valueOf(substatus.toUpperCase()) : SubType.BOTH ),
( ASK_SUBSCRIBE_ENUM.equals(askstatus) ? AskType.SUBSCRIBE : AskType.NONE),
RecvType.NONE,
nickname, groups));
} catch (Exception e) {
Log.warn("Adding User Roster failed:" + e.getLocalizedMessage());
Log.debug("", e);
}
}
}
/**
* @param userElement
* @param offlineMessages
*/
@SuppressWarnings("unchecked")
private void importOffLineMessages(Element userElement, List<OfflineMessage> offlineMessages) {
Log.debug("importOffLineMessages");
// TODO Auto-generated method stub
Iterator<Element> messageIter = userElement.elementIterator(MESSAGE_ELEMENT_NAME);
while (messageIter.hasNext()) {
Element msgElement = messageIter.next();
String creationDateStr = null;
if (msgElement.element(DELAY_ELEMENT_NAME) != null) {
creationDateStr = msgElement.element(DELAY_ELEMENT_NAME).attributeValue(STAMP_NAME);
}
Date creationDate = null;
try {
if (creationDateStr != null) {
creationDate = dateformater.parse(creationDateStr);
}
} catch (ParseException e) {
Log.warn("Date not parsable:" + e.getLocalizedMessage());
}
offlineMessages.add(new OfflineMessage(creationDate, msgElement));
}
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="console" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{yyyy.MM.dd HH:mm:ss} %-5p [%t]: %c - %m%n" />
</layout>
<filter class="org.apache.log4j.varia.LevelRangeFilter">
<param name="LevelMin" value="debug" />
<param name="AcceptOnMatch" value="true" />
</filter>
</appender>
<!-- OF-1095: Uniform output of loading/unloading of plugins to std-out. -->
<logger name="org.jivesoftware.openfire.container.PluginManager">
<appender-ref ref="console"/>
</logger>
<logger name="org.jivesoftware.openfire.container.PluginMonitor">
<appender-ref ref="console"/>
</logger>
<!-- OF-506: Jetty INFO messages are generally not useful. Ignore them by default. -->
<logger name="org.eclipse.jetty">
<level value="warn" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
/**
*
* Copyright 2016 Anno van Vliet
*
* 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.openfire.plugin;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.logging.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.jivesoftware.openfire.roster.DefaultRosterItemProvider;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterItemProvider;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.util.JiveGlobals;
import org.junit.Before;
import org.junit.Test;
/**
* Test Openfire flavour of Import and export
*
* @author Anno van Vliet
*
*/
public class OpenfireExporterTest {
public class TestRosterItemProvider extends DefaultRosterItemProvider implements RosterItemProvider {
/* (non-Javadoc)
* @see org.jivesoftware.openfire.roster.DefaultRosterItemProvider#createItem(java.lang.String, org.jivesoftware.openfire.roster.RosterItem)
*/
@Override
public RosterItem createItem(String username, RosterItem item) throws UserAlreadyExistsException {
logger.finest("createItem:" + username + " - " + item);
return item;
}
}
private static Logger logger = Logger.getLogger(OpenfireExporterTest.class.getName());
private UserManager userManager;
private RosterItemProvider rosterItemProvider;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
URL url = this.getClass().getResource("test-openfire.xml");
File f = new File(url.getFile());
JiveGlobals.setConfigName(f.getName());
JiveGlobals.setHomeDirectory(f.getParent());
JiveGlobals.setProperty("provider.user.className",
"org.jivesoftware.openfire.plugin.TestUserProvider");
userManager = UserManager.getInstance();
rosterItemProvider = new TestRosterItemProvider();
}
/**
* Test method for {@link org.jivesoftware.openfire.plugin.OpenfireExporter#exportUsers(org.jivesoftware.openfire.user.UserManager)}.
* @throws UserAlreadyExistsException
* @throws IOException
*/
@Test
public void testExportUsers() throws UserAlreadyExistsException, IOException {
InExporter testobject = new OpenfireExporter("serverName",userManager,rosterItemProvider);
for (int i = 0; i < 10; i++) {
userManager.createUser("username" + i,"pw" , "name" + i, "email" + i);
}
Document result = testobject.exportUsers();
assertNotNull(result);
assertEquals(1, result.nodeCount());
assertNotNull(result.node(0));
Element elem = ((Element)result.node(0));
assertEquals(10, elem.nodeCount());
assertNotNull(elem.node(0));
assertEquals(7, ((Element)elem.node(0)).nodeCount());
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
writer.write(result);
logger.fine(out.toString() );
assertNotNull(testobject.validate(new ByteArrayInputStream(out.toByteArray())));
}
/**
* @throws IOException
* @throws DocumentException
*
*/
@Test
public void testImportUser() throws DocumentException, IOException {
logger.finest("testImportUser");
InExporter testobject = new OpenfireExporter("serverName",userManager,rosterItemProvider);
String TEST_IMPORT_FILE = "test-openfire-import.xml";
InputStream stream = this.getClass().getResourceAsStream(TEST_IMPORT_FILE);
assertTrue("Invalid input", testobject.validate(stream));
stream.close();
String previousDomain = null;
boolean isUserProviderReadOnly = false;
stream = this.getClass().getResourceAsStream(TEST_IMPORT_FILE);
List<String> res = testobject.importUsers(stream, previousDomain, isUserProviderReadOnly);
assertNotNull(res);
assertEquals(0, res.size());
stream.close();
}
}
/**
*
* Copyright 2016 Anno van Vliet
*
* 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.openfire.plugin;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;
import org.jivesoftware.openfire.roster.Roster;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterItem.AskType;
import org.jivesoftware.openfire.roster.RosterItem.RecvType;
import org.jivesoftware.openfire.roster.RosterItem.SubType;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.openfire.user.UserProvider;
import org.xmpp.packet.JID;
/**
* A Dummy implementation for the UserProvider for testing purposes.
*
* @author Anno van Vliet
*
*/
public class TestUserProvider implements UserProvider {
public class TestRoster extends Roster {
private final Collection<RosterItem> rosteritems;
/**
*
*/
public TestRoster() {
rosteritems = new ArrayList<RosterItem>();
JID jid = new JID("roster@jid.test");
SubType subStatus = SubType.BOTH;
AskType askStatus = AskType.NONE;
RecvType recvStatus = RecvType.SUBSCRIBE;
String nickname = "nick";
List<String> groups = null;
rosteritems.add(new RosterItem(1, jid, subStatus, askStatus, recvStatus, nickname, groups));
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.roster.Roster#getRosterItems()
*/
@Override
public Collection<RosterItem> getRosterItems() {
logger.finest("getRosterItems");
return rosteritems;
}
}
public class TestUser extends User {
private final Roster roster;
/**
* @param username
* @param name
* @param email
* @param creationDate
* @param modificationDate
*/
public TestUser(String username, String name, String email, Date creationDate, Date modificationDate) {
super(username, name, email, creationDate, modificationDate);
roster = new TestRoster();
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.User#getRoster()
*/
@Override
public Roster getRoster() {
logger.finest("getRoster");
return roster;
}
}
private static Logger logger = Logger.getLogger(TestUserProvider.class.getName());
private final Map<String,User> userList;
/**
*
*/
public TestUserProvider() {
userList = new TreeMap<String,User>();
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#loadUser(java.lang.String)
*/
@Override
public User loadUser(String username) throws UserNotFoundException {
logger.finest("loadUser");
return userList.get(username);
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#createUser(java.lang.String, java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public User createUser(String username, String password, String name, String email) throws UserAlreadyExistsException {
logger.finest("createUser");
Date creationDate = new Date();
Date modificationDate = new Date();
User u = new TestUser(username, name, email, creationDate, modificationDate);
userList.put(username, u);
return u;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#deleteUser(java.lang.String)
*/
@Override
public void deleteUser(String username) {
logger.finest("deleteUser");
userList.remove(username);
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#getUserCount()
*/
@Override
public int getUserCount() {
logger.finest("getUserCount");
return userList.size();
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#getUsers()
*/
@Override
public Collection<User> getUsers() {
logger.finest("getUsers");
return userList.values();
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#getUsernames()
*/
@Override
public Collection<String> getUsernames() {
logger.finest("getUsernames");
return userList.keySet();
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#getUsers(int, int)
*/
@Override
public Collection<User> getUsers(int startIndex, int numResults) {
logger.finest("getUsers");
return null;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#setName(java.lang.String, java.lang.String)
*/
@Override
public void setName(String username, String name) throws UserNotFoundException {
logger.finest("setName");
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#setEmail(java.lang.String, java.lang.String)
*/
@Override
public void setEmail(String username, String email) throws UserNotFoundException {
logger.finest("setEmail");
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#setCreationDate(java.lang.String, java.util.Date)
*/
@Override
public void setCreationDate(String username, Date creationDate) throws UserNotFoundException {
logger.finest("setCreationDate");
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#setModificationDate(java.lang.String, java.util.Date)
*/
@Override
public void setModificationDate(String username, Date modificationDate) throws UserNotFoundException {
logger.finest("setModificationDate");
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#getSearchFields()
*/
@Override
public Set<String> getSearchFields() throws UnsupportedOperationException {
logger.finest("getSearchFields");
return null;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#findUsers(java.util.Set, java.lang.String)
*/
@Override
public Collection<User> findUsers(Set<String> fields, String query) throws UnsupportedOperationException {
logger.finest("findUsers");
return null;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#findUsers(java.util.Set, java.lang.String, int, int)
*/
@Override
public Collection<User> findUsers(Set<String> fields, String query, int startIndex, int numResults) throws UnsupportedOperationException {
logger.finest("findUsers");
return null;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#isReadOnly()
*/
@Override
public boolean isReadOnly() {
logger.finest("isReadOnly");
return false;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#isNameRequired()
*/
@Override
public boolean isNameRequired() {
logger.finest("isNameRequired");
return false;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.user.UserProvider#isEmailRequired()
*/
@Override
public boolean isEmailRequired() {
logger.finest("isEmailRequired");
return false;
}
}
/**
*
* Copyright 2016 Anno van Vliet
*
* 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.openfire.plugin;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.logging.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.dom.DOMElement;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.jivesoftware.openfire.OfflineMessage;
import org.jivesoftware.openfire.OfflineMessageStore;
import org.jivesoftware.openfire.PrivateStorage;
import org.jivesoftware.openfire.roster.DefaultRosterItemProvider;
import org.jivesoftware.openfire.roster.RosterItem;
import org.jivesoftware.openfire.roster.RosterItemProvider;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.UserManager;
import org.jivesoftware.openfire.vcard.VCardManager;
import org.jivesoftware.util.JiveGlobals;
import org.junit.Before;
import org.junit.Test;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
/**
* XEPP-0227 compliant import and export.
*
* @author Anno van Vliet
*
*/
public class Xep227ExporterTest {
public class TestVCardManager extends VCardManager {
/* (non-Javadoc)
* @see org.jivesoftware.openfire.vcard.VCardManager#getVCard(java.lang.String)
*/
@Override
public Element getVCard(String username) {
logger.finest("getVCard");
return new DOMElement("VCARD");
}
}
public class TestOfflineMessageStore extends OfflineMessageStore {
//private Collection<OfflineMessage> offMsgs = Collections.emptyList();
/* (non-Javadoc)
* @see org.jivesoftware.openfire.OfflineMessageStore#getMessages(java.lang.String, boolean)
*/
@Override
public Collection<OfflineMessage> getMessages(String username, boolean delete) {
logger.finest("getMessages");
Collection<OfflineMessage> offMsgs = new ArrayList<OfflineMessage>();
Element element = new DOMElement("message");
Date creationDate = new Date();
OfflineMessage offmsg = new OfflineMessage(creationDate, element);
offmsg.setFrom(new JID("of@server.id"));
offmsg.setTo(new JID("of@server.id"));
offmsg.setBody("text");
offMsgs.add(offmsg);
return offMsgs;
}
/* (non-Javadoc)
* @see org.jivesoftware.openfire.OfflineMessageStore#addMessage(org.xmpp.packet.Message)
*/
@Override
public void addMessage(Message message) {
logger.finest("addMessage:" + message);
assertNotNull(message);
}
}
public class TestRosterItemProvider extends DefaultRosterItemProvider implements RosterItemProvider {
/* (non-Javadoc)
* @see org.jivesoftware.openfire.roster.DefaultRosterItemProvider#createItem(java.lang.String, org.jivesoftware.openfire.roster.RosterItem)
*/
@Override
public RosterItem createItem(String username, RosterItem item) throws UserAlreadyExistsException {
logger.finest("createItem:" + username + " - " + item);
return item;
}
}
private static Logger logger = Logger.getLogger(Xep227ExporterTest.class.getName());
private OfflineMessageStore offlineMessagesStore;
private VCardManager vCardManager;
private PrivateStorage privateStorage;
private UserManager userManager;
private RosterItemProvider rosterItemProvider;
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception {
URL url = this.getClass().getResource("test-openfire.xml");
File f = new File(url.getFile());
JiveGlobals.setConfigName(f.getName());
JiveGlobals.setHomeDirectory(f.getParent());
JiveGlobals.setProperty("provider.user.className",
"org.jivesoftware.openfire.plugin.TestUserProvider");
offlineMessagesStore = new TestOfflineMessageStore();
vCardManager = new TestVCardManager();
privateStorage = null;
userManager = UserManager.getInstance();
rosterItemProvider = new TestRosterItemProvider();
//Empty users
List<User> l = new ArrayList<>(userManager.getUsers());
for (User user : l ) {
userManager.deleteUser(user);
}
}
/**
* Test method for {@link org.jivesoftware.openfire.plugin.OpenfireExporter#exportUsers(org.jivesoftware.openfire.user.UserManager)}.
* @throws UserAlreadyExistsException
* @throws IOException
*/
@Test
public void testExportUsers() throws UserAlreadyExistsException, IOException {
InExporter testobject = new Xep227Exporter("serverName", offlineMessagesStore, vCardManager, privateStorage, userManager, null);
for (int i = 0; i < 10; i++) {
userManager.createUser("username" + i,"pw" , "name" + i, "email" + i);
}
Document result = testobject.exportUsers();
assertNotNull(result);
assertEquals(1, result.nodeCount());
assertNotNull(result.node(0));
Element elem = ((Element)result.node(0));
assertEquals(1, elem.nodeCount());
assertNotNull(elem.node(0));
elem = ((Element)elem.node(0));
assertEquals(10, elem.nodeCount());
assertNotNull(elem.node(0));
elem = ((Element)elem.node(0));
assertEquals(3, elem.nodeCount());
assertEquals(2, elem.attributeCount());
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
writer.write(result);
logger.fine(out.toString() );
assertTrue("Invalid input", testobject.validate(new ByteArrayInputStream(out.toByteArray())));
}
/**
* @throws IOException
* @throws DocumentException
*
*/
@Test
public void testValidateUser() throws DocumentException, IOException {
logger.finest("testImportUser");
InExporter testobject = new Xep227Exporter("serverName", offlineMessagesStore, vCardManager, privateStorage, userManager, rosterItemProvider);
InputStream stream = this.getClass().getResourceAsStream("test-xepp227-import.xml");
assertTrue("Invalid input", testobject.validate(stream));
stream.close();
}
/**
* @throws IOException
* @throws DocumentException
*
*/
@Test
public void testImportUser() throws DocumentException, IOException {
logger.finest("testImportUser");
InExporter testobject = new Xep227Exporter("serverName", offlineMessagesStore, vCardManager, privateStorage, userManager, rosterItemProvider);
InputStream stream = this.getClass().getResourceAsStream("test-xepp227-import.xml");
String previousDomain = null;
boolean isUserProviderReadOnly = false;
List<String> res = testobject.importUsers(stream, previousDomain, isUserProviderReadOnly);
assertNotNull(res);
assertEquals(0, res.size());
stream.close();
Collection<User> users = userManager.getUsers();
assertEquals(4, users.size());
}
/**
* Test if XInclude is working
*
* @throws IOException
* @throws DocumentException
*
*/
@Test
public void testImportXInclude() throws DocumentException, IOException {
logger.finest("testImportIncludeUser");
InExporter testobject = new Xep227Exporter("serverName", offlineMessagesStore, vCardManager, privateStorage, userManager, rosterItemProvider);
String IMPORT_FILE_NAME = "/test-export-xinclude/xep227.xml";
URL streamurl = this.getClass().getResource(IMPORT_FILE_NAME);
assertNotNull(streamurl);
logger.fine("testImportIncludeUser:"+ streamurl.getFile());
InputStream stream = new FileInputStream(streamurl.getFile());
assertTrue("Invalid input", testobject.validate(stream));
stream.close();
String previousDomain = null;
boolean isUserProviderReadOnly = false;
stream = this.getClass().getResourceAsStream(IMPORT_FILE_NAME);
List<String> res = testobject.importUsers(stream, previousDomain, isUserProviderReadOnly);
assertNotNull(res);
assertEquals(0, res.size());
stream.close();
Collection<User> users = userManager.getUsers();
assertEquals(2, users.size());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<Openfire>
<User>
<Username>admin</Username>
<Password>admin</Password>
<Email>admin@example.com</Email>
<Name>Administrator</Name>
<CreationDate>1450103443961</CreationDate>
<ModifiedDate>0</ModifiedDate>
<Roster/>
</User>
<User>
<Username>anno</Username>
<Password>anno</Password>
<Email></Email>
<Name>A.A.M. Vliet</Name>
<CreationDate>1450104179268</CreationDate>
<ModifiedDate>1450104179268</ModifiedDate>
<Roster>
<Item jid="test1@openfire.xmpp.test" askstatus="-1" recvstatus="-1" substatus="3" name="Tester Een"/>
</Roster>
</User>
<User>
<Username>test1</Username>
<Password>test</Password>
<Email></Email>
<Name>Tester Een</Name>
<CreationDate>1450104197255</CreationDate>
<ModifiedDate>1450104197255</ModifiedDate>
<Roster>
<Item jid="anno@openfire.xmpp.test" askstatus="-1" recvstatus="-1" substatus="3"/>
</Roster>
</User>
<User>
<Username>test2</Username>
<Password>test</Password>
<Email></Email>
<Name>Tester Twee</Name>
<CreationDate>1450104215316</CreationDate>
<ModifiedDate>1450104215316</ModifiedDate>
<Roster/>
</User>
</Openfire>
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file stores bootstrap properties needed by Openfire.
Property names must be in the format: "prop.name.is.blah=value"
That will be stored as:
<prop>
<name>
<is>
<blah>value</blah>
</is>
</name>
</prop>
Most properties are stored in the Openfire database. A
property viewer and editor is included in the admin console.
-->
<!-- root element, all properties must be under this element -->
<jive>
<adminConsole>
<!-- Disable either port by setting the value to -1 -->
<port>9090</port>
<securePort>9091</securePort>
</adminConsole>
<admin>
<!-- Use this section to define users that will have admin privileges. Below,
you will find two ways to specify which users are admins. Admins will
have access to the admin console (only local users) and may have also access
to other functionalities like ad-hoc commands. -->
<!-- By default, only the user with the username "admin" can login
to the admin console. Alternatively, you can specify a comma-delimitted
list usernames that should be authorized to login to the admin console
by setting the <authorizedUsernames> field below. -->
<!-- <authorizedUsernames></authorizedUsernames> -->
<!-- Comma-delimitted list of bare JIDs. The JIDs may belong to local
or remote users. -->
<!-- <authorizedJIDs></authorizedJIDs> -->
</admin>
<locale>en</locale>
<!-- Network settings. By default, Openfire will bind to all network interfaces.
Alternatively, you can specify a specific network interfaces that the server
will listen on. For example, 127.0.0.1. This setting is generally only useful
on multi-homed servers. -->
<!--
<network>
<interface></interface>
</network>
-->
<!-- Example LDAP settings -->
<!-- Note, for Active Directory, try usernameField=sAMAccountName, nameField=displayName,
emailField=mail -->
<!--
<ldap>
<host></host>
<port>389</port>
<usernameField>uid</usernameField>
<nameField>cn</nameField>
<emailField>mail</emailField>
<baseDN></baseDN>
<adminDN></adminDN>
<adminPassword></adminPassword>
</ldap>
<provider>
<user>
<className>org.jivesoftware.openfire.ldap.LdapUserProvider</className>
</user>
<auth>
<className>org.jivesoftware.openfire.ldap.LdapAuthenticationProvider</className>
</auth>
</provider>
-->
<!-- End example LDAP settings -->
<connectionProvider>
<className>org.jivesoftware.database.EmbeddedConnectionProvider</className>
</connectionProvider>
<setup>true</setup>
</jive>
<?xml version="1.0" encoding="UTF-8"?>
<server-data xmlns="urn:xmpp:pie:0">
<host jid="openfire.xmpp.test">
<user name="admin" password="admin">
<offline-messages>
<message xmlns="jabber:client" from="openfire.xmpp.test" to="admin@openfire.xmpp.test" type="normal">
<body>A server or plugin update was found: Monitoring Service 1.5.0</body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Thu Jan 21 14:17:35 CET 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="openfire.xmpp.test" to="admin@openfire.xmpp.test" type="normal">
<body>A server or plugin update was found: Monitoring Service 1.5.0</body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Mon Jan 25 11:38:03 CET 2016">Offline Storage</delay>
</message>
</offline-messages>
<query xmlns="jabber:iq:roster"/>
</user>
<user name="anno" password="anno">
<offline-messages>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="anno@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Wed Sep 14 18:11:51 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="anno@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Wed Sep 14 18:11:51 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="anno@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 16 13:23:35 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="anno@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 16 13:23:35 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="anno@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Mon Sep 19 16:35:21 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="anno@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Mon Sep 19 16:35:21 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="anno@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Tue Sep 20 14:39:46 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="anno@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Tue Sep 20 14:39:46 CEST 2016">Offline Storage</delay>
</message>
</offline-messages>
<query xmlns="jabber:iq:roster">
<item jid="test1@openfire.xmpp.test" name="Tester Een" subscription="both">
<group/>
</item>
</query>
</user>
<user name="test1" password="test">
<offline-messages>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test1@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 16 13:23:35 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test1@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 16 13:23:35 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test1@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Mon Sep 19 16:35:21 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test1@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Mon Sep 19 16:35:21 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test1@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Tue Sep 20 14:39:46 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test1@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Tue Sep 20 14:39:46 CEST 2016">Offline Storage</delay>
</message>
</offline-messages>
<query xmlns="jabber:iq:roster">
<item jid="anno@openfire.xmpp.test" subscription="both">
<group/>
</item>
</query>
</user>
<user name="test2" password="test">
<offline-messages>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 09 17:48:00 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 09 17:48:00 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 09 17:48:00 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Wed Sep 14 11:02:37 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Wed Sep 14 11:02:37 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Wed Sep 14 11:02:37 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Wed Sep 14 18:11:51 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Wed Sep 14 18:11:51 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Wed Sep 14 18:11:51 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 16 13:23:35 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 16 13:23:35 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Fri Sep 16 13:23:35 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Mon Sep 19 16:35:21 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Mon Sep 19 16:35:21 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Mon Sep 19 16:35:21 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Tue Sep 20 14:39:46 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Tue Sep 20 14:39:46 CEST 2016">Offline Storage</delay>
</message>
<message xmlns="jabber:client" from="pubsub.openfire.xmpp.test" to="test2@openfire.xmpp.test" type="normal">
<body></body>
<delay xmlns="urn:xmpp:delay" from="openfire.xmpp.test" stamp="Tue Sep 20 14:39:46 CEST 2016">Offline Storage</delay>
</message>
</offline-messages>
<query xmlns="jabber:iq:roster"/>
<vCard xmlns="vcard-temp">
<NICKNAME>test2-nickname</NICKNAME>
<FN>My full name</FN>
<URL>www.home.net</URL>
<PHOTO>
<BINVAL>/9j/4AAQSkZJRgABAgAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCABLAEsDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD3Siis3VNUtbKxmllnMYjIBYKeOQOwrGUlFXZnKSirsvzSrDBJKwJVFLHHXAGawLvxnp1ls8yG6O/ONqr2/wCBe9ctqd/qOrX8VxpV5cyWCqEm2zFFJBJYFSRngjtToxbrn7aiN/c3ru+v9K4KuNSdonDPFtu0NPM2W+JGjqSDbX3/AHwn/wAVUkPxB0md0Vbe9Bdgoyiev+9XJXY07LbYofvdov8A61Z0zwIHMQVWAypVcYNc39oTuc7xlVPVo9d0/VINS8zyUkXy8Z3gDrn0PtV2vBZNU1W3x9m1C7i3fe8udlz6Zwa9P0HxXpmpXzww37yssZcqUfpkDPI967qGLjU06nVh8bGp7r0Z1dFNjkSWMOhyp6GnV1naMlmigUNLIkak4y7ADNeb32qXWraxd6VcRItg87r50akEqpJUhiSOcDt3rqPGl59i0eGTZvzcKuM4/hb/AArlguy2W+zncofZ/ve/41wY2q0uVHBi53fIum5IkcWjwPbW0m9Hy5MhBOSMdselUJrhpdu7aMelRXV75k6/u8cAfe96heTpxXhTm5PyPPlNbLYinI5571SfLyGOMb5G+VUXksT0AFS3M2xc7c/N610fhrwn/aJ0/Wftvl/vg/k+Vn7r4xnPfb6VdCjKrLlijOFOVWXLEx9M8M3195v2qwvo9mNuIWGc5z1HtWVbjUvCUhv0sZUMg8nN1EwXnnjpz8v8698gt/K3fNnPtXlPxEv/ALT4fgTytuLpTndn+B/avVq4SNBJp6nZXwkKEFNPVHZeD9aXVfDtlNPLbrdyb8xI2Dw7DoST0Ga6GvIfh3qOdV02y8r/AJ6/Pu/2XPTFevV30J88EzvwtT2lJM474ksV8O25BI/0ten+49cmkkl/pcVlBIyTNGgDMcAYwT0+ldh8Q4Wn0CBUjZyLpThQT/C9cALprOENFIEmjAGDjI7Hg15+Nsqqctjgxj5a13sdDpnhS/m0m4LzWzS5YK7MxI+UY5xWJqOk3uieX9snSTzs7fLcnGMZzkD1Fdh4K1hLnTHivL2Hz3uSqozKrMCq4wO/Oa6S78P6Xqmz7ba+b5edn7xlxnr0I9BW0sFSr0U6W5t9Up1qSlS38zkvCfhOZ7kXl8LS4tZ7ffHHJlypYqQSCMZxmu+htrbTbDCW8SRwqzbYkAA6ngUsENnptvGi7IYkURpufgADgZJ9q5LxR4oFqNRt7fVLZSsJ2IGQnJTPfnqa2UaeDp+Zuo08LT13GeKvGltY/ZNn2xN+/Pl4Gcbevze9eNXuoXVxCEnup5UDZCvIWGeeeTVvUNVvNS8v7XP5nl52/KoxnGeg9hWNcP8Auxhh1ry1UniKybPAxGJliKl+h1Pw5kY+O9NXc2397xnj/VPXvVeHfDa0Y+KNMuvJfH7395g4+44+le4162HVoteZ7eXpqj/XZFHVNO/tK1WHzfL2uHztz2I9fevEdXbydav7XGfLuJE3euGIzj8K98rk/Fug3WpaJfQwyQq0rKVLkgffB5wKzxdD2kdB43D+1hdbo8w0nUfsOp2j+Vv2To+N2M4YcdPavYdB8Q/2x9o/0XyfK2/8tN2c59h6V4fqulT6DcC1unjd3TzAYiSMEkdwPQ1QjuEGeGrzaNarhnZLTseRQxdXCy5WtD2jxl4r+w6eq/Yt+24Cf63GeG56e1eS6tqP9palNd+V5fmY+XdnGAB1x7VmvMuScHrULzrhhg9KVT2uIldoxxGJqYiWuxO8m3HFUM+Zx070E+Z07eteoeCPBGpaZrU0009oytbsgCOxOdynuo9K7KFBQVlq3+BphsM5s2/htom3w/pmp/aP+ev7vZ/tuvXP9K9AqCyga3tEicgsuc46dc1PXowjZWPo6UFCCigpkkaTRlHGVPUZp9FMs53WvB2jaqkk81h5t0sJSI+c6+pA4YDqe9cFqPw6vx5f2HSvXf8A6Qvtjq31r1+isp0IS3Rz1MLSqbo8Fk+HPiou23Svlycf6RF0/wC+qs2nw210vF9q0n5dw8z/AElOmeejele40VMcPGOzZksvop3/AMv8jz/RPhtpC+f/AGnpP93y/wDSX98/db6V3MFlb27l4o9rEYzknip6K2UUtjqhShBWigoooqiz/9k=</BINVAL>
<TYPE>image/jpeg</TYPE>
</PHOTO>
<EMAIL>
<WORK/>
<INTERNET/>
<PREF/>
<USERID>test@home.net</USERID>
</EMAIL>
<EMAIL>
<HOME/>
<INTERNET/>
<PREF/>
<USERID>test@home.net</USERID>
</EMAIL>
<TEL>
<HOME/>
<VOICE/>
<NUMBER>1234567</NUMBER>
</TEL>
</vCard>
</user>
</host>
</server-data>
<?xml version='1.0' encoding='UTF-8'?>
<user name='admin' password='admin' xmlns='urn:xmpp:pie:0'>
<query xmlns='jabber:iq:private'>
<mlink-wtmp-auth xmlns='mlink-wtmp-auth'>
<wtmp xmlns='http://isode.com/mlink/wtmp' type='auth'>
<trace-info jid='admin@mlink.xmpp.test' remote-address='127.0.0.1' conn-id='2' stamp='2016-09-23T11:06:11Z' />
<text>User admin@mlink.xmpp.test from [127.0.0.1] authenticated at 2016-09-23T11:06:11Z</text>
</wtmp>
</mlink-wtmp-auth>
</query>
<presence xmlns='jabber:client' type='subscribe' from='anno@mlink.xmpp.test'></presence>
<vCard xmlns='vcard-temp'>
<FN>admin</FN>
<N>
<FAMILY></FAMILY>
</N>
<EMAIL>
<INTERNET />
<WORK />
<USERID>admin@mlink.xmpp.test</USERID>
</EMAIL>
<DESC>This user has full control over the DSA</DESC>
</vCard>
</user>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<user name='anno' password='anno' xmlns='urn:xmpp:pie:0'>
<query xmlns='jabber:iq:roster' ver='37'>
<item jid='test1@mlink.xmpp.test' name='A Test' subscription='none' ask='subscribe'>
<group>testgroup</group>
</item>
<item jid='ju00122@mlink.xmpp.test' name='JU:00122' subscription='none' ask='subscribe' />
<item jid='ju001@mlink.xmpp.test' name='JU:001' subscription='none' ask='subscribe' />
<item jid='ju0011@mlink.xmpp.test' name='JU:0011' subscription='none' ask='subscribe'>
<group>testgroup</group>
</item>
</query>
<query xmlns='jabber:iq:private'>
<mlink-wtmp-auth xmlns='mlink-wtmp-auth'>
<wtmp xmlns='http://isode.com/mlink/wtmp' type='auth'>
<trace-info jid='anno@mlink.xmpp.test' remote-address='192.41.140.2' conn-id='8' stamp='2016-09-23T11:59:49Z' />
<text>User anno@mlink.xmpp.test from [192.41.140.2] authenticated at 2016-09-23T11:59:49Z</text>
</wtmp>
</mlink-wtmp-auth>
</query>
<query xmlns='jabber:iq:private'>
<storage xmlns='storage:bookmarks'>
<conference name="room1@conference.mlink.xmpp.test" jid="room1@conference.mlink.xmpp.test" autojoin="1">
<nick xmlns="storage:bookmarks">anno</nick>
</conference>
<conference name="xtg-room@conference.mlink.xmpp.test" jid="xtg-room@conference.mlink.xmpp.test" autojoin="1">
<nick xmlns="storage:bookmarks">anno</nick>
</conference>
</storage>
</query>
<vCard xmlns='vcard-temp'>
<FN>JChat XMPP to Tactical data-link Gateway (XTG)</FN>
<N>
<FAMILY>JChat XMPP to Tactical data-link Gateway (XTG)</FAMILY>
</N>
<NICKNAME>JChat XMPP to Tactical data-link Gateway (XTG)</NICKNAME>
<URL>www.home.nl-opop</URL>
<EMAIL>
<INTERNET />
<HOME />
<USERID>anno@mlink.xmpp.test</USERID>
</EMAIL>
<EMAIL>
<INTERNET />
<WORK />
<USERID>anno@mlink.xmpp.test</USERID>
</EMAIL>
<TEL>
<VOICE />
<HOME />
<NUMBER>123123123</NUMBER>
</TEL>
</vCard>
</user>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<user xmlns='urn:xmpp:pie:0' name='ju001'>
<query xmlns='jabber:iq:private'>
<mlink-wtmp-auth xmlns='mlink-wtmp-auth'>
<wtmp xmlns='http://isode.com/mlink/wtmp' type='auth'>
<trace-info jid='ju001@mlink.xmpp.test' remote-address='127.0.0.1' conn-id='83' stamp='2016-08-17T15:40:11Z' />
<text>User ju001@mlink.xmpp.test from [127.0.0.1] authenticated at 2016-08-17T15:40:11Z</text>
</wtmp>
</mlink-wtmp-auth>
</query>
<query xmlns='jabber:iq:private'>
<storage xmlns='storage:bookmarks'>
<conference name='whiteboard' autojoin='true' jid='whiteboard@conference.mlink.xmpp.test'></conference>
</storage>
</query>
<presence xmlns='jabber:client' type='subscribe' from='anno@mlink.xmpp.test'></presence>
<vCard xmlns='vcard-temp'>
<FN>JU:001</FN>
<N>
<FAMILY>JU:001</FAMILY>
</N>
<EMAIL>
<INTERNET />
<WORK />
<USERID>ju001@mlink.xmpp.test</USERID>
</EMAIL>
</vCard>
</user>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<user xmlns='urn:xmpp:pie:0' name='ju0011'>
<offline-messages>
<message xmlns="jabber:client" from='anno@mlink.xmpp.test/45da1d15b2641fba' to='ju0011@mlink.xmpp.test' id='39501f49-7505-481a-9a82-d3607091fef0'>
<active xmlns="http://jabber.org/protocol/chatstates" />
</message>
<message xmlns="jabber:client" from='anno@mlink.xmpp.test/45da1d15b2641fba' to='ju0011@mlink.xmpp.test' id='e3a86f1b-1a01-45b3-bd67-e3a0f6675149'>
<inactive xmlns="http://jabber.org/protocol/chatstates" />
</message>
</offline-messages>
<presence xmlns='jabber:client' type='subscribe' from='anno@mlink.xmpp.test'></presence>
<vCard xmlns='vcard-temp'>
<FN>JU:0011</FN>
<N>
<FAMILY>JU:0011</FAMILY>
</N>
<EMAIL>
<INTERNET />
<WORK />
<USERID>ju0011@mlink.xmpp.test</USERID>
</EMAIL>
</vCard>
</user>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<user xmlns='urn:xmpp:pie:0' name='ju0012'><vCard xmlns='vcard-temp'><FN>JU:0012</FN><N><FAMILY>JU:0012</FAMILY></N><EMAIL><INTERNET/><WORK/><USERID>ju0012@mlink.xmpp.test</USERID></EMAIL></vCard></user>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<user xmlns='urn:xmpp:pie:0' name='ju00122'>
<offline-messages>
<message xmlns="jabber:client" from='anno@mlink.xmpp.test/45da1d15b2641fba' to='ju00122@mlink.xmpp.test' id='44ec3d48-4719-4b52-89a6-dbd04a9399e7'>
<active xmlns="http://jabber.org/protocol/chatstates" />
</message>
<message xmlns="jabber:client" from='anno@mlink.xmpp.test/45da1d15b2641fba' to='ju00122@mlink.xmpp.test' id='db09c3a5-0bb6-403d-a284-8a805d32a6c7'>
<inactive xmlns="http://jabber.org/protocol/chatstates" />
</message>
</offline-messages>
<query xmlns='jabber:iq:private'>
<mlink-wtmp-auth xmlns='mlink-wtmp-auth'>
<wtmp xmlns='http://isode.com/mlink/wtmp' type='auth'>
<trace-info jid='ju00122@mlink.xmpp.test' remote-address='127.0.0.1' conn-id='67' stamp='2016-05-31T16:23:41Z' />
<text>User ju00122@mlink.xmpp.test from [127.0.0.1] authenticated at 2016-05-31T16:23:41Z</text>
</wtmp>
</mlink-wtmp-auth>
</query>
<presence xmlns='jabber:client' type='subscribe' from='test1@mlink.xmpp.test'></presence>
<presence xmlns='jabber:client' type='subscribe' from='anno@mlink.xmpp.test'></presence>
<vCard xmlns='vcard-temp'>
<FN>JU:00122</FN>
<N>
<FAMILY>JU:00122</FAMILY>
</N>
<NICKNAME>JU:00122</NICKNAME>
<EMAIL>
<INTERNET />
<WORK />
<USERID>ju00122@mlink.xmpp.test</USERID>
</EMAIL>
</vCard>
</user>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<user xmlns='urn:xmpp:pie:0' name='test1'>
<offline-messages>
<message xmlns="jabber:client" from='anno@mlink.xmpp.test/45da1d15b2641fba' to='test1@mlink.xmpp.test' id='e4ccbadc-29ed-44f6-af50-d887714b1a1b'>
<active xmlns="http://jabber.org/protocol/chatstates" />
</message>
<message xmlns="jabber:client" from='anno@mlink.xmpp.test/45da1d15b2641fba' to='test1@mlink.xmpp.test' id='18089241-4573-4532-9186-b354339a8683'>
<inactive xmlns="http://jabber.org/protocol/chatstates" />
</message>
<message xmlns="jabber:client" from='anno@mlink.xmpp.test/45da1d15b2641fba' to='test1@mlink.xmpp.test' id='10a862ba-2535-44ae-8319-3c4125e8c556'>
<active xmlns="http://jabber.org/protocol/chatstates" />
</message>
</offline-messages>
<query xmlns='jabber:iq:roster' ver='28'>
<item jid='test2@mlink.xmpp.test' subscription='both' />
<item jid='ju00144@mlink.xmpp.test' name='JU:00144' subscription='none' ask='subscribe'>
<group>test-group</group>
</item>
<item jid='ju00122@mlink.xmpp.test' name='JU:00122' subscription='none' ask='subscribe'>
<group>test-group</group>
</item>
<item jid='anno@openfire.xmpp.test' subscription='none' />
</query>
<query xmlns='jabber:iq:private'>
<mlink-wtmp-auth xmlns='mlink-wtmp-auth'>
<wtmp xmlns='http://isode.com/mlink/wtmp' type='auth'>
<trace-info jid='test1@mlink.xmpp.test' remote-address='127.0.0.1' conn-id='13' stamp='2016-08-24T11:50:45Z' />
<text>User test1@mlink.xmpp.test from [127.0.0.1] authenticated at 2016-08-24T11:50:45Z</text>
</wtmp>
</mlink-wtmp-auth>
</query>
<query xmlns='jabber:iq:private'>
<storage xmlns='storage:bookmarks'>
<url name='room1 -fav' url='room1@rooms.mlink.xmpp.test' />
<conference name='room1' autojoin='true' jid='room1@rooms.mlink.xmpp.test'></conference>
<conference name='room2' autojoin='true' jid='room2@rooms.mlink.xmpp.test'></conference>
<conference name='whiteboard' autojoin='true' jid='whiteboard@conference.mlink.xmpp.test'></conference>
</storage>
</query>
<presence xmlns='jabber:client' type='subscribe' from='anno@mlink.xmpp.test'></presence>
<vCard xmlns='vcard-temp'>
<FN>test1@mlink.xmpp.test</FN>
<N>
<PREFIX>Mr</PREFIX>
<GIVEN>A</GIVEN>
<MIDDLE>Sr</MIDDLE>
<FAMILY>Test</FAMILY>
<SUFFIX>van</SUFFIX>
</N>
<NICKNAME>swift-test</NICKNAME>
<EMAIL>
<INTERNET />
<WORK />
<USERID>test1@mlink.xmpp.test</USERID>
</EMAIL>
</vCard>
</user>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<user xmlns='urn:xmpp:pie:0' name='test2'><query xmlns='jabber:iq:roster' ver='8'><item jid='test1@mlink.xmpp.test' name='test1@mlink.xmpp.test' subscription='both'/><item jid='anno@mlink.xmpp.test' subscription='none'/></query><vCard xmlns='vcard-temp'><FN>test2@mlink.xmpp.test</FN><N><FAMILY>test2@mlink.xmpp.test</FAMILY></N><EMAIL><INTERNET/><WORK/><USERID>test2@mlink.xmpp.test</USERID></EMAIL></vCard></user>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<host xmlns='urn:xmpp:pie:0' xmlns:xi='http://www.w3.org/2001/XInclude' jid='sub'>
<xi:include href='sub/61/admin.xml' />
<xi:include href='sub/61/anno.xml' />
<xi:include href='sub/6a/ju001.xml' />
<xi:include href='sub/6a/ju0011.xml' />
<xi:include href='sub/6a/ju0012.xml' />
<xi:include href='sub/6a/ju00122.xml' />
<xi:include href='sub/74/test1.xml' />
<xi:include href='sub/74/test2.xml' />
</host>
\ No newline at end of file
<?xml version='1.0' encoding='UTF-8'?>
<server-data xmlns='urn:xmpp:pie:0' xmlns:xi='http://www.w3.org/2001/XInclude'>
<xi:include href='src/plugins/userImportExport/src/test/java/test-export-xinclude/test-import.xml' />
</server-data>
\ No newline at end of file
<%@ page import="java.io.OutputStream,
org.jivesoftware.util.ParamUtils,
org.jivesoftware.openfire.XMPPServer,
org.jivesoftware.openfire.plugin.ImportExportPlugin"
contentType="application/x-download"%><%
String fileName = request.getParameter("fileName");
boolean xep227Support = ParamUtils.getBooleanParameter(request, "xep227support", false);
response.setContentType("application/x-download");
response.setHeader("Content-Disposition","attachment;filename="+fileName+".xml");
ImportExportPlugin plugin = (ImportExportPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("userimportexport");
byte[] content = plugin.exportUsersToByteArray();
byte[] content = plugin.exportUsersToByteArray(xep227Support);
OutputStream os = response.getOutputStream();
os.write(content);
os.flush();
......
......@@ -12,6 +12,7 @@
boolean exportUsers = request.getParameter("exportUsers") != null;
boolean success = request.getParameter("success") != null;
boolean exportToFile = ParamUtils.getBooleanParameter(request, "exporttofile", true);
boolean xep227Support = ParamUtils.getBooleanParameter(request, "xep227support", false);
ImportExportPlugin plugin = (ImportExportPlugin)XMPPServer.getInstance().getPluginManager(
).getPlugin("userimportexport");
......@@ -26,12 +27,12 @@
errors.put("missingFile","missingFile");
}
else {
response.sendRedirect("export-file.jsp?fileName="+file);
response.sendRedirect("export-file.jsp?fileName="+file + ( xep227Support ? "&xep227support=true" : ""));
}
}
else {
try {
exportText = plugin.exportUsersToString();
exportText = plugin.exportUsersToString(xep227Support);
}
catch (IOException e) {
errors.put("IOException","IOException");
......@@ -94,7 +95,7 @@
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
<td width="1%"><input type="radio" name="exporttofile" value="true" <%= exportToFile ? "checked" : "" %> id="rb01"></td>
<td width="1%"><input type="radio" name="exporttofile" value="true" <%= exportToFile ? "checked" : "" %> id="rb01"/></td>
<td width="99%"><label for="rb01"><b>To File</b></label> - Save user data to the specified file location.</td>
</tr>
<tr>
......@@ -102,13 +103,17 @@
<td width="99%">Export File Name:&nbsp;<input type="text" size="30" maxlength="150" name="exportFile"></td>
</tr>
<tr>
<td width="1%"><input type="radio" name="exporttofile" value="false" <%= !exportToFile ? "checked" : "" %> id="rb02"></td>
<td width="1%"><input type="radio" name="exporttofile" value="false" <%= !exportToFile ? "checked" : "" %> id="rb02"/></td>
<td width="99%"><label for="rb02"><b>To Screen</b></label> - Display user data in the text area below.</td>
</tr>
<tr>
<td width="1%">&nbsp;</td>
<td width="99%"><textarea cols="80" rows="20" wrap=off><%=exportText %></textarea></td>
</tr>
<tr>
<td width="1%"><input type="checkbox" name="xep227support" <%= xep227Support ? "checked" : "" %> id="rb03"/></td>
<td width="99%"><label for="rb03"><b>XEP-0227</b></label> - Export using format defined in <a href="http://www.xmpp.org/extensions/xep-0227.html" target="_blank">XEP-0227</a>.</td>
</tr>
</tbody>
</table>
</div>
......
......@@ -10,6 +10,7 @@
<%
boolean importUsers = request.getParameter("importUsers") != null;
boolean xep227Support = request.getParameter("xep227support") != null;
ImportExportPlugin plugin = (ImportExportPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("userimportexport");
List<String> duplicateUsers = new ArrayList<String>();
......@@ -23,14 +24,16 @@
FileItem fi = (FileItem) i.next();
FileItem pd = (FileItem) i.next();
String previousDomain = pd.getString();
FileItem xsup = (FileItem) i.next();
xep227Support = new Boolean( xsup.getString() );
if (plugin.validateImportFile(fi)) {
if (plugin.validateImportFile(fi, xep227Support)) {
try {
if (isEmpty(previousDomain)) {
duplicateUsers.addAll(plugin.importUserData(fi, null));
duplicateUsers.addAll(plugin.importUserData(fi, null, xep227Support));
}
else if (!isEmpty(previousDomain)) {
duplicateUsers.addAll(plugin.importUserData(fi, previousDomain));
duplicateUsers.addAll(plugin.importUserData(fi, previousDomain, xep227Support));
}
else {
errors.put("missingDomain", "missingDomain");
......@@ -143,6 +146,10 @@ Use the form below to import a user data XML file.
See the migration section of the <a href="../../plugin-admin.jsp?plugin=userimportexport&showReadme=true&decorator=none">readme</a> for details.
</p>
Replace Domain: <input type="text" size="20" maxlength="150" name="previousDomain" value=""/>
<p>
<input type="checkbox" name="xep227support" <%= xep227Support ? "checked" : "" %> value="true" id="rb03"/> <label for="rb03"><b>XEP-0227</b></label> - Import using format defined in <a href="http://www.xmpp.org/extensions/xep-0227.html" target="_blank">XEP-0227</a>.
</div>
<input type="submit" value="Import">
......
......@@ -53,6 +53,11 @@
url="system-email.jsp"
description="${sidebar.server-email.descr}"/>
<!-- SMS -->
<item id="system-sms" name="${sidebar.server-sms}"
url="system-sms.jsp"
description="${sidebar.server-sms.descr}"/>
<!-- Security Audit Viewer -->
<item id="security-audit-viewer" name="${sidebar.security-audit-viewer}"
url="security-audit-viewer.jsp"
......
......@@ -275,10 +275,17 @@ function dodelete(propName) {
<input type="hidden" name="propName" value="">
<style type="text/css">
.hidebox {
.nameColumn {
text-overflow : ellipsis;
overflow : hidden;
white-space : nowrap;
max-width : 200px;
}
.valueColumn {
text-overflow : ellipsis;
overflow : hidden;
white-space : nowrap;
max-width : 300px;
}
</style>
......@@ -311,22 +318,14 @@ function dodelete(propName) {
%>
<tr class="<%= (n.equals(propName) ? "hilite" : "") %>">
<td>
<div class="hidebox" style="width:200px;">
<span title="<%= StringUtils.escapeForXML(n) %>">
<%= StringUtils.escapeHTMLTags(n) %>
</span>
</div>
</td>
<td>
<div class="hidebox" style="width:300px;">
<td class="nameColumn"><%= StringUtils.escapeHTMLTags(n) %></td>
<td class="valueColumn">
<% if (JiveGlobals.isPropertyEncrypted(n) ||
JiveGlobals.isPropertySensitive(n)) { %>
<span style="color:#999;"><i>hidden</i></span>
<% } else { %>
<span title="<%= ("".equals(v) ? "&nbsp;" : v) %>"><%= ("".equals(v) ? "&nbsp;" : v) %></span>
<%= ("".equals(v) ? "&nbsp;" : v) %>
<% } %>
</div>
</td>
<td align="center"><a href="#" onclick="doedit('<%= StringUtils.replace(StringUtils.escapeHTMLTags(n),"'","''") %>');"
><img src="images/edit-16x16.gif" width="16" height="16"
......
......@@ -39,8 +39,11 @@
Cookie csrfCookie = CookieUtils.getCookie(request, "csrf");
String csrfParam = ParamUtils.getParameter(request, "csrf");
Map<String,String> errors = new HashMap<String,String>();
if (save) {
if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) {
errors.put("csrf", "CSRF Failure!");
save = false;
}
}
......@@ -55,7 +58,6 @@
EmailService service = EmailService.getInstance();
// Save the email settings if requested
Map<String,String> errors = new HashMap<String,String>();
if (save) {
if (host != null) {
service.setHost(host);
......@@ -234,7 +236,7 @@
</tr>
</table>
</div>
<input type="hidden" name="csrf" value="${csrf}"/>
<input type="submit" name="save" value="<fmt:message key="system.email.save" />">
<input type="submit" name="test" value="<fmt:message key="system.email.send_test" />">
</form>
......
<%@ page import="java.util.*,
org.jivesoftware.util.*"
errorPage="error.jsp"
%>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% webManager.init(request, response, session, application, out ); %>
<%
// get parameters
String host = ParamUtils.getParameter(request,"host");
int port = ParamUtils.getIntParameter(request,"port",0);
String systemId = ParamUtils.getParameter(request,"systemId");
String password = ParamUtils.getParameter(request,"password");
String systemType = ParamUtils.getParameter(request,"systemType");
boolean save = request.getParameter("save") != null;
boolean test = request.getParameter("test") != null;
Cookie csrfCookie = CookieUtils.getCookie(request, "csrf");
String csrfParam = ParamUtils.getParameter(request, "csrf");
final Map<String,String> errors = new HashMap<String,String>();
if (save) {
if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) {
errors.put("csrf", "CSRF Failure!");
}
}
csrfParam = StringUtils.randomString(15);
CookieUtils.setCookie(request, response, "csrf", csrfParam, -1);
pageContext.setAttribute("csrf", csrfParam);
// Handle a test request
if (test) {
response.sendRedirect("system-smstest.jsp");
return;
}
if ( !save )
{
// Not trying to save new values? Use the existing values.
host = JiveGlobals.getProperty( "sms.smpp.host", "localhost" );
port = JiveGlobals.getIntProperty( "sms.smpp.port", 2775 );
systemId = JiveGlobals.getProperty( "sms.smpp.systemId" );
password = JiveGlobals.getProperty( "sms.smpp.password" );
systemType = JiveGlobals.getProperty( "sms.smpp.systemType" );
}
if ( host == null || host.isEmpty() )
{
errors.put( "host", "cannot be missing or empty." );
}
if ( port < 0 || port > 65535 )
{
errors.put( "port", "must be a number between 0 and 65535." );
}
if ( systemId == null || systemId.isEmpty() )
{
errors.put( "systemId", "cannot be missing or empty." );
}
if ( password == null || password.isEmpty() )
{
errors.put( "password", "cannot be missing or empty." );
}
if (errors.isEmpty() && save)
{
JiveGlobals.setProperty( "sms.smpp.host", host );
JiveGlobals.setProperty( "sms.smpp.port", Integer.toString(port) );
JiveGlobals.setProperty( "sms.smpp.systemId", systemId );
JiveGlobals.setProperty( "sms.smpp.password", password );
JiveGlobals.setProperty( "sms.smpp.systemType", systemType );
// Log the event
webManager.logEvent("updated sms service settings", "host = "+host+"\nport = "+port+"\nusername = "+ systemId +"\nsystemType = "+systemType);
JiveGlobals.setProperty("sms.configured", "true");
response.sendRedirect("system-sms.jsp?success=true");
}
pageContext.setAttribute( "errors", errors );
pageContext.setAttribute( "host", host );
pageContext.setAttribute( "port", port);
pageContext.setAttribute( "systemId", systemId );
pageContext.setAttribute( "password", password );
pageContext.setAttribute( "systemType", systemType );
%>
<html>
<head>
<title><fmt:message key="system.sms.title"/></title>
<meta name="pageID" content="system-sms"/>
</head>
<body>
<c:if test="${not empty errors}">
<admin:infobox type="error">
<fmt:message key="system.sms.config_failure" />
</admin:infobox>
</c:if>
<c:if test="${empty errors and param.success}">
<admin:infobox type="success"><fmt:message key="system.sms.update_success" /></admin:infobox>
</c:if>
<p>
<fmt:message key="system.sms.info" />
</p>
<form action="system-sms.jsp" method="post">
<fmt:message key="system.sms.name" var="plaintextboxtitle"/>
<admin:contentBox title="${plaintextboxtitle}">
<table width="80%" cellpadding="3" cellspacing="0" border="0">
<tr>
<td width="30%" nowrap>
<fmt:message key="system.sms.host" />:
</td>
<td nowrap>
<input type="text" name="host" value="${fn:escapeXml(host)}" size="40" maxlength="150">
</td>
</tr>
<c:if test="${ not empty errors['host']}">
<tr>
<td nowrap>
&nbsp;
</td>
<td nowrap class="jive-error-text">
<fmt:message key="system.sms.valid_host" />
</td>
</tr>
</c:if>
<tr>
<td nowrap>
<fmt:message key="system.sms.port" />:
</td>
<td nowrap>
<input type="text" name="port" value="${fn:escapeXml(port)}" size="10" maxlength="15">
</td>
</tr>
<c:if test="${ not empty errors['port']}">
<tr>
<td nowrap>
&nbsp;
</td>
<td nowrap class="jive-error-text">
<fmt:message key="system.sms.valid_port" />
</td>
</tr>
</c:if>
<!-- spacer -->
<tr><td colspan="2">&nbsp;</td></tr>
<tr>
<td width="30%" nowrap>
<fmt:message key="system.sms.systemId" />:
</td>
<td nowrap>
<input type="text" name="systemId" value="${fn:escapeXml(systemId)}" size="40" maxlength="150">
</td>
</tr>
<c:if test="${ not empty errors['systemId']}">
<tr>
<td nowrap>
&nbsp;
</td>
<td nowrap class="jive-error-text">
<fmt:message key="system.sms.valid_systemId" />
</td>
</tr>
</c:if>
<tr>
<td width="30%" nowrap>
<fmt:message key="system.sms.password" />:
</td>
<td nowrap>
<input type="password" name="password" value="${fn:escapeXml(password)}" size="40" maxlength="8">
</td>
</tr>
<c:if test="${ not empty errors['password']}">
<tr>
<td nowrap>
&nbsp;
</td>
<td nowrap class="jive-error-text">
<fmt:message key="system.sms.valid_password" />
</td>
</tr>
</c:if>
<!-- spacer -->
<tr><td colspan="2">&nbsp;</td></tr>
<tr>
<td width="30%" nowrap>
<fmt:message key="system.sms.systemType" />:
</td>
<td nowrap>
<input type="text" name="systemType" value="${fn:escapeXml(systemType)}" size="40" maxlength="150">
</td>
</tr>
<c:if test="${ not empty errors['systemType']}">
<tr>
<td nowrap>
&nbsp;
</td>
<td nowrap class="jive-error-text">
<fmt:message key="system.sms.valid_systemType" />
</td>
</tr>
</c:if>
</table>
</admin:contentBox>
<input type="hidden" name="csrf" value="${csrf}"/>
<input type="submit" name="save" value="<fmt:message key="system.sms.save" />">
<input type="submit" name="test" value="<fmt:message key="system.sms.send_test" />" ${ not empty errors ? 'disabled' : ''}>
</form>
</body>
</html>
<%--
- Copyright (C) 2005-2008 Jive Software. All rights reserved.
-
- 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.
--%>
<%@ page import="org.jivesoftware.util.*,
java.util.*"
errorPage="error.jsp"
%>
<%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%-- Define Administration Bean --%>
<jsp:useBean id="webManager" class="org.jivesoftware.util.WebManager" />
<% webManager.init(request, response, session, application, out ); %>
<% // Get paramters
boolean doTest = request.getParameter("test") != null;
boolean cancel = request.getParameter("cancel") != null;
String recipient = ParamUtils.getParameter(request, "recipient");
String message = ParamUtils.getParameter(request, "message");
// Cancel if requested
if (cancel) {
response.sendRedirect("system-sms.jsp");
return;
}
// Validate input
Map<String, String> errors = new HashMap<String, String>();
Cookie csrfCookie = CookieUtils.getCookie(request, "csrf");
String csrfParam = ParamUtils.getParameter(request, "csrf");
if (doTest) {
if (csrfCookie == null || csrfParam == null || !csrfCookie.getValue().equals(csrfParam)) {
doTest = false;
errors.put("csrf", "CSRF Failure!");
}
}
csrfParam = StringUtils.randomString(15);
CookieUtils.setCookie(request, response, "csrf", csrfParam, -1);
pageContext.setAttribute("csrf", csrfParam);
// See if the service has basic configuration.
if ( JiveGlobals.getProperty( "sms.smpp.host" ) == null ) {
errors.put( "host", "cannot be missing or empty." );
}
if ( JiveGlobals.getProperty( "sms.smpp.systemId" ) == null )
{
errors.put( "systemId", "cannot be missing or empty." );
}
if (JiveGlobals.getProperty( "sms.smpp.password" ) == null )
{
errors.put( "password", "cannot be missing or empty.");
}
if (doTest) {
if (recipient == null || recipient.trim().isEmpty() ) {
errors.put("recipient", "Recipient cannot be missing or empty.");
}
if (message == null || message.trim().isEmpty() ) {
errors.put("message", "Message cannot be missing or empty.");
}
if (errors.isEmpty())
{
final SmsService service = SmsService.getInstance();
try
{
service.sendImmediately( message, recipient );
response.sendRedirect("system-smstest.jsp?sent=true&success=true");
return;
}
catch ( Exception e )
{
errors.put( "sendfailed", SmsService.getDescriptiveMessage( e ) );
}
}
}
pageContext.setAttribute( "errors", errors );
pageContext.setAttribute( "recipient", recipient );
pageContext.setAttribute( "message", message );
%>
<html>
<head>
<title><fmt:message key="system.smstest.title"/></title>
<meta name="pageID" content="system-sms"/>
</head>
<body>
<c:if test="${not empty errors}">
<admin:infobox type="error">
<c:choose>
<c:when test="${ not empty errors['host'] or not empty errors['systemId'] or not empty errors['password']}">
<fmt:message key="system.smstest.invalid-service-config">
<fmt:param value="<a href=\"system-sms.jsp\">"/>
<fmt:param value="</a>"/>
</fmt:message>
</c:when>
<c:when test="${ not empty errors['sendfailed']}">
<fmt:message key="system.smstest.failure">
<fmt:param value="${errors['sendfailed']}"/>
</fmt:message>
</c:when>
<c:otherwise>
<fmt:message key="system.sms.config_failure" />
</c:otherwise>
</c:choose>
</admin:infobox>
</c:if>
<c:if test="${empty errors and param.success}">
<admin:infobox type="success"><fmt:message key="system.smstest.success" /></admin:infobox>
</c:if>
<p>
<fmt:message key="system.smstest.info" />
</p>
<form action="system-smstest.jsp" method="post">
<table cellpadding="3" cellspacing="0" border="0">
<tbody>
<tr>
<td>
<fmt:message key="system.smstest.recipient" />:
</td>
<td>
<input type="text" name="recipient" value="${fn:escapeXml(recipient)}"size="40" maxlength="100">
</td>
</tr>
<c:if test="${ not empty errors['recipient']}">
<tr>
<td nowrap>
&nbsp;
</td>
<td nowrap class="jive-error-text">
<fmt:message key="system.smstest.valid_recipient" />
</td>
</tr>
</c:if>
<tr valign="top">
<td>
<fmt:message key="system.smstest.message" />:
</td>
<td>
<textarea name="message" cols="45" rows="5" maxlength="140" wrap="virtual"><c:out value="${message}"/></textarea>
</td>
</tr>
<c:if test="${ not empty errors['message']}">
<tr>
<td nowrap>
&nbsp;
</td>
<td nowrap class="jive-error-text">
<fmt:message key="system.smstest.valid_message" />
</td>
</tr>
</c:if>
<tr>
<td colspan="2">
<br>
<input type="hidden" name="csrf" value="${csrf}">
<input type="submit" name="test" value="<fmt:message key="system.smstest.send" />" ${ not empty errors['host'] or not empty errors['systemId'] or not empty errors['password'] ? 'disabled' : ''}>
<input type="submit" name="cancel" value="<fmt:message key="system.smstest.cancel" />">
</td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment