Commit 1df0b9a0 authored by Ryan Graham's avatar Ryan Graham Committed by ryang

* Added the ability to import user roster data when using a read-only user...

* Added the ability to import user roster data when using a read-only user store such as LDAP or POP3.
* Added the ability to allow user elements in the import file to be in any order.
* Added a check to make sure that the username is valid.
* Removed an unnecessary check when creating roster items.
* Updated UI to match later versions of Wildfire.

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@6859 b35dd754-fafc-0310-a699-88a17e54d16e
parent 8a3ff84a
......@@ -6,75 +6,75 @@
<style type="text/css">
/* global font and body settings */
body {
font-size : 100%;
background-color : #d3d6d9;
padding: 0px;
margin: 0px 0px 30px 0px;
font-size : 100%;
background-color : #d3d6d9;
padding: 0px;
margin: 0px 0px 30px 0px;
}
body, td, th {
font-family : arial, helvetica, sans-serif;
font-size : 10pt;
font-family : arial, helvetica, sans-serif;
font-size : 10pt;
}
pre, tt, code {
font-family : courier new, monospaced;
font-size : 9pt;
font-family : courier new, monospaced;
font-size : 9pt;
}
#pageContainer {
display: block;
position: relative;
clear: both;
background-color: #fff;
border: 1px solid #999;
padding: 40px;
margin: 30px;
-moz-border-radius: 6px;
display: block;
position: relative;
clear: both;
background-color: #fff;
border: 1px solid #999;
padding: 40px;
margin: 30px;
-moz-border-radius: 6px;
}
#pageHeader {
display: block;
position: relative;
height: 80px;
background-color: #e7eaee;
border: 1px solid #cccccc;
border-bottom: none;
-moz-border-radius: 5px 5px 0px 0px;
margin: 10px 0px 0px 0px;
display: block;
position: relative;
height: 80px;
background-color: #e7eaee;
border: 1px solid #cccccc;
border-bottom: none;
-moz-border-radius: 5px 5px 0px 0px;
margin: 10px 0px 0px 0px;
}
#pageBody {
margin: 0px 18px 0px 20px;
margin: 0px 18px 0px 20px;
}
/* anchors */
a:link {
color: #11568c;
color: #11568c;
}
a:visited {
color: #571c8d;
color: #571c8d;
}
a:hover {
color: #7a1d42;
text-decoration : underline;
color: #7a1d42;
text-decoration : underline;
}
a:active {
color: #7a1d42;
color: #7a1d42;
}
/* page header elements (logo and navigation) */
.navigation {
display: block;
position: relative;
height: 20px;
background-color: #335588;
border: 1px solid #cccccc;
border-top: none;
color: #ffffff;
font-size: 11px;
line-height: 18px;
padding: 0px 0px 0px 0px;
margin: 0px 0px 25px 0px;
overflow: hidden;
display: block;
position: relative;
height: 20px;
background-color: #335588;
border: 1px solid #cccccc;
border-top: none;
color: #ffffff;
font-size: 11px;
line-height: 18px;
padding: 0px 0px 0px 0px;
margin: 0px 0px 25px 0px;
overflow: hidden;
}
.navigation a {
margin: 0px 20px 0px 20px;
margin: 0px 20px 0px 20px;
}
.navigation a:link { color: #ffffff; }
.navigation a:visited { color: #ffffff; }
......@@ -83,86 +83,93 @@ a:active {
/* headings */
h1 {
display: block;
position: relative;
font-size : 1.7em;
font-weight : bold;
color: #670e15;
padding: 0px;
margin: 30px 0px 0px 20px;
display: block;
position: relative;
font-size : 1.7em;
font-weight : bold;
color: #670e15;
padding: 0px;
margin: 30px 0px 0px 20px;
}
h2 {
font-size : 1.3em;
font-weight : bold;
margin: 40px 0px 6px 0px;
padding: 0px;
color: #335588;
font-size : 1.3em;
font-weight : bold;
margin: 40px 0px 6px 0px;
padding: 0px;
color: #335588;
}
h3 {
font-size : 1.0em;
font-weight : bold;
margin: 25px 0px 3px 0px;
padding: 0px;
color: #334466;
font-size : 1.0em;
font-weight : bold;
margin: 25px 0px 3px 0px;
padding: 0px;
color: #334466;
}
/* general elements */
p {
margin: 0px 0px 15px 0px;
margin: 0px 0px 15px 0px;
}
ul {
margin: 5px 0px 15px 35px;
margin: 5px 0px 15px 35px;
}
li {
padding-bottom : 4px;
padding-bottom : 4px;
}
tt {
font-family : courier new;
font-weight : bold;
color : #060;
font-family : courier new;
font-weight : bold;
color : #060;
}
hr {
display: block;
height: 1px;
background-color: #999999;
border: none;
margin: 40px 0px 20px 0px;
display: block;
height: 1px;
background-color: #999999;
border: none;
margin: 40px 0px 20px 0px;
}
.footer {
font-size : 8pt;
color : #666;
text-align : center;
margin-top : 2em;
padding-top : 0.5em;
border-top : 1px #CCC solid;
font-size : 8pt;
color : #666;
text-align : center;
margin-top : 2em;
padding-top : 0.5em;
border-top : 1px #CCC solid;
}
</style>
</head>
<body>
<div id="pageContainer">
<div id="pageHeader">
<h1>User Import/Export Plugin Changelog</h1>
</div>
<div id="pageHeader">
<h1>User Import/Export Plugin Changelog</h1>
</div>
<div id="pageBody">
<h2>2.1.0 -- <span style="font-weight: normal;">Januar 29, 2007</span></h2>
<ul>
<li>Added the ability to import user roster data when using a read-only user store such as LDAP or POP3.</li>
<li>Added the ability to allow user elements in the import file to be in any order.</li>
<li>Added a check to make sure that the username is valid.</li>
<li>Removed an unnecessary check when creating roster items.</li>
<li>Updated UI to match later versions of Wildfire.</li>
</ul>
<h2>2.0.4 -- <span style="font-weight: normal;">October 06, 2006</span></h2>
<ul>
<li>Updated to use compression offered by Wildfire 3.1</li>
<li>Updated to use compression offered by Wildfire 3.1</li>
</ul>
<h2>2.0.3 -- <span style="font-weight: normal;">July 10, 2006</span></h2>
<ul>
<li>Updated to work with Wildfire 3.0.1.</li>
<li>Updated to work with Wildfire 3.0.1.</li>
</ul>
<h2>2.0.2 -- <span style="font-weight: normal;">December 15, 2005</span></h2>
<ul>
<li>Now requires Wildfire 2.4.0.</li>
<li>Now requires Wildfire 2.4.0.</li>
</ul>
<h2>2.0.1 -- <span style="font-weight: normal;">November 29, 2005</span></h2>
......@@ -186,10 +193,10 @@ hr {
<h2>1.0 -- <span style="font-weight: normal;">June 1, 2005</span></h2>
<ul>
<li>Initial release.</li>
<li>Initial release.</li>
</ul>
</div>
</div>
</div>
</body>
......
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="Wildfire">
<xs:complexType>
<xs:sequence>
<xs:element ref="User" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="User">
<xs:complexType>
<xs:sequence>
<xs:element ref="Username" use="required"/>
<xs:element ref="Password" use="required"/>
<xs:element ref="Email"/>
<xs:element ref="Name"/>
<xs:element ref="CreationDate"/>
<xs:element ref="ModifiedDate"/>
<xs:element ref="Roster"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Username" type="xs:string"/>
<xs:element name="Password" type="xs:string"/>
<xs:element name="Email" type="xs:string"/>
<xs:element name="Name" type="xs:string"/>
<xs:element name="CreationDate" type="jive-date"/>
<xs:element name="ModifiedDate" type="jive-date"/>
<xs:element name="Roster">
<xs:complexType>
<xs:sequence>
<xs:element ref="Item" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Item">
<xs:complexType>
<xs:sequence>
<xs:element ref="Group" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute ref="jid" use="required"/>
<xs:attribute name="askstatus" use="required"/>
<xs:attribute name="recvstatus" use="required"/>
<xs:attribute name="substatus" use="required"/>
<xs:attribute name="name"/>
</xs:complexType>
</xs:element>
<xs:element name="Group" type="xs:string"/>
<xs:attribute name="jid" type="xs:string"/>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="askstatus">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="-1"/>
<xs:maxInclusive value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="recvstatus">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="-1"/>
<xs:maxInclusive value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="substatus">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="-1"/>
<xs:maxInclusive value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:simpleType name="jive-date">
<xs:restriction base="xs:string">
</xs:restriction>
</xs:simpleType>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="Wildfire">
<xs:complexType>
<xs:sequence>
<xs:element ref="User" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="User">
<xs:complexType>
<xs:all>
<xs:element ref="Username" use="required" />
<xs:element ref="Password" use="required" />
<xs:element ref="Email" />
<xs:element ref="Name" />
<xs:element ref="CreationDate" />
<xs:element ref="ModifiedDate" />
<xs:element ref="Roster" />
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="Username" type="xs:string" />
<xs:element name="Password" type="xs:string" />
<xs:element name="Email" type="xs:string" />
<xs:element name="Name" type="xs:string" />
<xs:element name="CreationDate" type="jive-date" />
<xs:element name="ModifiedDate" type="jive-date" />
<xs:element name="Roster">
<xs:complexType>
<xs:sequence>
<xs:element ref="Item" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Item">
<xs:complexType>
<xs:sequence>
<xs:element ref="Group" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute ref="jid" use="required" />
<xs:attribute name="askstatus" use="required" />
<xs:attribute name="recvstatus" use="required" />
<xs:attribute name="substatus" use="required" />
<xs:attribute name="name" />
</xs:complexType>
</xs:element>
<xs:element name="Group" type="xs:string" />
<xs:attribute name="jid" type="xs:string" />
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="askstatus">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="-1" />
<xs:maxInclusive value="3" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="recvstatus">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="-1" />
<xs:maxInclusive value="3" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="substatus">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="-1" />
<xs:maxInclusive value="3" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:simpleType name="jive-date">
<xs:restriction base="xs:string"></xs:restriction>
</xs:simpleType>
</xs:schema>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<class>org.jivesoftware.wildfire.plugin.ImportExportPlugin</class>
<class>org.jivesoftware.wildfire.plugin.ImportExportPlugin</class>
<name>User Import Export</name>
<description>Enables import and export of user data</description>
<author>Ryan Graham</author>
<version>2.0.4</version>
<date>10/06/2006</date>
<minServerVersion>3.1.0</minServerVersion>
<name>User Import Export</name>
<description>Enables import and export of user data</description>
<author>Ryan Graham</author>
<version>2.1.0</version>
<date>01/29/2007</date>
<minServerVersion>3.1.0</minServerVersion>
<adminconsole>
<tab id="tab-users">
<sidebar id="user-import-export" name="Import &amp; Export">
<item id="import-export-selection" name="User Import &amp; Export"
url="import-export-selection.jsp"
description="Allows the importing and exporting of Wildfire user data." />
</sidebar>
</tab>
</adminconsole>
<adminconsole>
<tab id="tab-users">
<sidebar id="user-import-export" name="Import &amp; Export">
<item id="import-export-selection" name="User Import &amp; Export"
url="import-export-selection.jsp"
description="Allows the importing and exporting of Wildfire user data." />
</sidebar>
</tab>
</adminconsole>
</plugin>
......@@ -6,75 +6,75 @@
<style type="text/css">
/* global font and body settings */
body {
font-size : 100%;
background-color : #d3d6d9;
padding: 0px;
margin: 0px 0px 30px 0px;
font-size : 100%;
background-color : #d3d6d9;
padding: 0px;
margin: 0px 0px 30px 0px;
}
body, td, th {
font-family : arial, helvetica, sans-serif;
font-size : 10pt;
font-family : arial, helvetica, sans-serif;
font-size : 10pt;
}
pre, tt, code {
font-family : courier new, monospaced;
font-size : 9pt;
font-family : courier new, monospaced;
font-size : 9pt;
}
#pageContainer {
display: block;
position: relative;
clear: both;
background-color: #fff;
border: 1px solid #999;
padding: 40px;
margin: 30px;
-moz-border-radius: 6px;
display: block;
position: relative;
clear: both;
background-color: #fff;
border: 1px solid #999;
padding: 40px;
margin: 30px;
-moz-border-radius: 6px;
}
#pageHeader {
display: block;
position: relative;
height: 80px;
background-color: #e7eaee;
border: 1px solid #cccccc;
border-bottom: none;
-moz-border-radius: 5px 5px 0px 0px;
margin: 10px 0px 0px 0px;
display: block;
position: relative;
height: 80px;
background-color: #e7eaee;
border: 1px solid #cccccc;
border-bottom: none;
-moz-border-radius: 5px 5px 0px 0px;
margin: 10px 0px 0px 0px;
}
#pageBody {
margin: 0px 18px 0px 20px;
margin: 0px 18px 0px 20px;
}
/* anchors */
a:link {
color: #11568c;
color: #11568c;
}
a:visited {
color: #571c8d;
color: #571c8d;
}
a:hover {
color: #7a1d42;
text-decoration : underline;
color: #7a1d42;
text-decoration : underline;
}
a:active {
color: #7a1d42;
color: #7a1d42;
}
/* page header elements (logo and navigation) */
.navigation {
display: block;
position: relative;
height: 20px;
background-color: #335588;
border: 1px solid #cccccc;
border-top: none;
color: #ffffff;
font-size: 11px;
line-height: 18px;
padding: 0px 0px 0px 0px;
margin: 0px 0px 25px 0px;
overflow: hidden;
display: block;
position: relative;
height: 20px;
background-color: #335588;
border: 1px solid #cccccc;
border-top: none;
color: #ffffff;
font-size: 11px;
line-height: 18px;
padding: 0px 0px 0px 0px;
margin: 0px 0px 25px 0px;
overflow: hidden;
}
.navigation a {
margin: 0px 20px 0px 20px;
margin: 0px 20px 0px 20px;
}
.navigation a:link { color: #ffffff; }
.navigation a:visited { color: #ffffff; }
......@@ -83,79 +83,79 @@ a:active {
/* headings */
h1 {
display: block;
position: relative;
font-size : 1.7em;
font-weight : bold;
color: #670e15;
padding: 0px;
margin: 30px 0px 0px 20px;
display: block;
position: relative;
font-size : 1.7em;
font-weight : bold;
color: #670e15;
padding: 0px;
margin: 30px 0px 0px 20px;
}
h2 {
font-size : 1.3em;
font-weight : bold;
margin: 40px 0px 6px 0px;
padding: 0px;
color: #335588;
font-size : 1.3em;
font-weight : bold;
margin: 40px 0px 6px 0px;
padding: 0px;
color: #335588;
}
h3 {
font-size : 1.0em;
font-weight : bold;
margin: 25px 0px 3px 0px;
padding: 0px;
color: #334466;
font-size : 1.0em;
font-weight : bold;
margin: 25px 0px 3px 0px;
padding: 0px;
color: #334466;
}
/* general elements */
p {
margin: 0px 0px 15px 0px;
margin: 0px 0px 15px 0px;
}
ul {
margin: 5px 0px 15px 35px;
margin: 5px 0px 15px 35px;
}
li {
padding-bottom : 4px;
padding-bottom : 4px;
}
tt {
font-family : courier new;
font-weight : bold;
color : #060;
font-family : courier new;
font-weight : bold;
color : #060;
}
hr {
display: block;
height: 1px;
background-color: #999999;
border: none;
margin: 40px 0px 20px 0px;
display: block;
height: 1px;
background-color: #999999;
border: none;
margin: 40px 0px 20px 0px;
}
.footer {
font-size : 8pt;
color : #666;
text-align : center;
margin-top : 2em;
padding-top : 0.5em;
border-top : 1px #CCC solid;
font-size : 8pt;
color : #666;
text-align : center;
margin-top : 2em;
padding-top : 0.5em;
border-top : 1px #CCC solid;
}
.datatable TH {
color : #fff;
background-color : #2A448C;
text-align : left;
color : #fff;
background-color : #2A448C;
text-align : left;
}
.datatable TD {
background-color : #FAF6EF;
background-color : #FAF6EF;
}
.datatable .name {
background-color : #DCE2F5;
text-align : center;
background-color : #DCE2F5;
text-align : center;
}
.xmltable TD {
background-color : #EFEFEF;
background-color : #EFEFEF;
}
</style>
</head>
......@@ -163,16 +163,16 @@ hr {
<div id="pageContainer">
<div id="pageHeader">
<h1>User Import/Export Plugin Readme</h1>
</div>
<div id="pageHeader">
<h1>User Import/Export Plugin Readme</h1>
</div>
<div id="pageBody">
<h2>Overview</h2>
<p>The user import/export plugin provides a way to import and export Wildfire user data via
the Admin Console. The user data consists of jid (aka "username"), name, email address, password
and roster list (aka "buddy list"). This plugin also can aid in the migration of users from other
Jabber/XMPP based systems to Wildfire.</p>
<p>The user import/export plugin provides a way to import and export Wildfire user data via
the Admin Console. The user data consists of username, password, name, email address, creation
and modified date, and roster list (aka "buddy list"). This plugin also can aid in the migration
of users from other Jabber/XMPP based systems to Wildfire.</p>
<h2>Installation</h2>
<p>Copy the userImportExport.jar into the plugins directory of your Wildfire installation.
......@@ -184,36 +184,39 @@ userImportExport.jar file over the existing file.</p>
<h2>Using the Plugin</h2>
<p>The plugin is accessed via the "User Import & Export" sidebar item located under the
"Users/Groups" tab in the Admin Console. Note: if you are using LDAP as your user data source the
following message will appear: "Sorry, because you are using LDAP as your user store this plugin
will not work with your Wildfire installation."
<li><b>Importing</b> - Select the "Import User Data" option from the user import/export selection
"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.
</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
in importing all user data, you will be presented with the message: "All users added successfully".
If the plugin was not successful in importing all user data you, will receive a message indicating
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.</li>
information, except in the case of using a read-only user store.</li>
<br>
<li><b>Exporting</b> - Select the "Export User Data" option from the user import/export selection
<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
"Export File Name" and then click on the "Export" button. Note: the plugin will automatically append
"Export File Name" and then click on the "Export" button. Note: the plugin will automatically append
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>
<li><b>Migration</b> - To import user data from another instant messaging system using the plugin,
the import file must conform to the wildfire-user-schema.xsd.xml schema file (located in the classes
<li><strong>Migration</strong> - To import user data from another instant messaging system using the plugin,
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
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
entries domain names to server name of your Wildfire installation. For example, say you have a user
entries domain names to server name of your Wildfire installation. For example, say you have a user
whose roster looks like:
</li>
<br>
<p>
<div class="xmltable">
<table>
<tr>
......@@ -238,15 +241,18 @@ whose roster looks like:
</tr>
</table>
</div>
<br>
</p>
<p>
Mike and Jane's accounts both reside on server whose domain name is "im.olddomain.net" and are being
imported to a Wildfire installation whose server name is "im.newdomain.net". If on the import screen
imported to a Wildfire installation whose server name is "im.newdomain.net". If on the import screen
the "Optional Existing Domain" field is filled in with "im.olddomain.net" (without the quotes) any
roster item jid that contains "im.olddomain.net" will be replaced with "im.newdomain.net". So, in
effect, the import file would be transformed to look like:
<br>
<br>
</p>
<p>
<div class="xmltable">
<table>
<tr>
......@@ -271,14 +277,14 @@ effect, the import file would be transformed to look like:
</tr>
</table>
</div>
</p>
<br>
<br>
Below is a sample of an exported user list from Wildfire than contains two users, Joe and Sally, who
<p>
Below is a sample of an exported user list from Wildfire than contains two users, Joe and Sally, who
have added each other to their respective rosters.
<br>
<br>
</p>
<p>
<div class="xmltable">
<table>
<tr>
......@@ -317,12 +323,11 @@ have added each other to their respective rosters.
</tr>
</table>
</div>
</p>
<br>
<br>
<p>
Below is a list of the different status types and what their associated numbers mean.
<br>
<br>
</p>
<div class="datatable">
<table cellpadding="3" cellspacing="1" border="0" width="700">
......@@ -406,7 +411,7 @@ Below is a list of the different status types and what their associated numbers
</table>
</div>
</div>
</div>
</div>
</body>
......
......@@ -19,6 +19,8 @@ import org.jivesoftware.wildfire.user.UserAlreadyExistsException;
import org.jivesoftware.wildfire.user.UserManager;
import org.jivesoftware.wildfire.user.UserNotFoundException;
import org.jivesoftware.wildfire.user.UserProvider;
import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException;
import org.jivesoftware.util.Log;
import org.xmpp.packet.JID;
......@@ -28,32 +30,26 @@ import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
/**
* The user import/export plugin provides a way to import and export Wildfire
* user data via the Admin Console. The user data consists of jid (aka "username"),
* user data via the Admin Console. The user data consists of username,
* name, email address, password and roster list (aka "buddy list"). This plugin also
* can aid in the migration of users from other Jabber/XMPP based systems to Jive
* Wildfire.
*
* @author Ryan Graham
* @author <a href="mailto:ryan@version2software.com">Ryan Graham</a>
*/
public class ImportExportPlugin implements Plugin {
private UserManager userManager;
private UserProvider provider;
private String serverName;
public ImportExportPlugin() {
userManager = XMPPServer.getInstance().getUserManager();
provider = UserManager.getUserProvider();
serverName = XMPPServer.getInstance().getServerInfo().getName();
}
......@@ -66,11 +62,23 @@ public class ImportExportPlugin implements Plugin {
serverName = null;
}
/**
* Convenience method that returns true if this UserProvider is read-only.
*
* @return true if the user provider is read-only.
*/
public boolean isUserProviderReadOnly() {
return provider.isReadOnly();
}
public byte[] exportUsersToFile() throws IOException {
/**
* Converts the user data that is to be exported to a byte[]. If a read-only
* user store is being used a user's password will be the same as their username.
*
* @return a byte[] of the user data.
* @throws IOException if there's a problem writing to the XMLWriter.
*/
public byte[] exportUsersToByteArray() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
......@@ -79,12 +87,19 @@ public class ImportExportPlugin implements Plugin {
return out.toByteArray();
}
/**
* Converts the exported user data to a String. If a read-only
* user store is being used a user's password will be the same as their username.
*
* @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 {
StringWriter stringWriter = new StringWriter();
XMLWriter writer = null;
try {
writer = new XMLWriter(stringWriter, OutputFormat.createPrettyPrint());
writer.write(exportUsers());
writer = new XMLWriter(stringWriter, OutputFormat.createPrettyPrint());
writer.write(exportUsers());
} catch (IOException ioe) {
Log.error(ioe);
throw ioe;
......@@ -97,20 +112,40 @@ public class ImportExportPlugin implements Plugin {
return stringWriter.toString();
}
public List<String> importUserData(FileItem file, String previousDomain) throws IOException, DocumentException {
/**
* Returns a list of usernames that were unable to be imported or whose rosters could not imported. Users are not able to be
* imported for the following reasons:
* <li>Their username is not properly formatted.
* <li>If a read-only user data store is being used and the user could not be found.
* <li>If a writeable user data store is being used and the user already exists.
*
* @param file a FileItem containing the user data to be imported.
* @param previousDomain a String an optional parameter that if supplied will replace the user roster entries domain names to
* server name of current Wildfire installation.
* @return True if FileItem matches the wildfire user schema.
* @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);
}
/**
* Returns whether or not the supplied FileItem matches the wildfire user schema
*
* @param file a FileItem to be validated.
* @return True if FileItem matches the wildfire user schema.
*/
public boolean validateImportFile(FileItem file) {
try {
try {
return new UserSchemaValidator(file, "wildfire-user-schema.xsd.xml").validate();
}
}
catch (Exception e) {
Log.error(e);
return false;
}
}
}
private Document exportUsers() {
......@@ -122,25 +157,28 @@ public class ImportExportPlugin implements Plugin {
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) {
//this should never happen
Log.info("User not found: " + userName + ", setting password to their username");
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) {
......@@ -150,7 +188,7 @@ public class ImportExportPlugin implements Plugin {
itemElement.addAttribute("recvstatus", String.valueOf(ri.getRecvStatus().getValue()));
itemElement.addAttribute("substatus", String.valueOf(ri.getSubStatus().getValue()));
itemElement.addAttribute("name", ri.getNickname());
Element groupElement = itemElement.addElement("Group");
List<String> groups = ri.getGroups();
for (String group : groups) {
......@@ -162,13 +200,11 @@ public class ImportExportPlugin implements Plugin {
return document;
}
private List<String> importUsers(Document document, String previousDomain) {
List<String> duplicateUsers = new ArrayList<String>();
private List<String> importUsers(Document document, String previousDomain) {
List<String> invalidUsers = new ArrayList<String>();
UserManager userManager = UserManager.getInstance();
RosterItemProvider rosterItemProvider = RosterItemProvider.getInstance();
Map<String, List<RosterItem>> rosterMap = new HashMap<String, List<RosterItem>>();
Element users = document.getRootElement();
......@@ -185,9 +221,8 @@ public class ImportExportPlugin implements Plugin {
Iterator userElements = user.elementIterator();
while (userElements.hasNext()) {
Element userElement = (Element) userElements.next();
String nameElement = userElement.getName();
if ("Username".equals(nameElement)) {
userName = userElement.getText();
}
......@@ -201,7 +236,7 @@ public class ImportExportPlugin implements Plugin {
email = userElement.getText();
}
else if ("Roster".equals(nameElement)) {
Iterator rosterIter = userElement.elementIterator("Item");
Iterator rosterIter = userElement.elementIterator("Item");
while (rosterIter.hasNext()) {
Element rosterElement = (Element) rosterIter.next();
......@@ -236,44 +271,33 @@ public class ImportExportPlugin implements Plugin {
if ((userName != null) && (password != null)) {
try {
userManager.createUser(userName, password, name, email);
rosterMap.put(userName, rosterItems);
}
catch (UserAlreadyExistsException e) {
Log.info("User already exists: " + userName);
duplicateUsers.add(userName);
}
}
}
//this prevents a user from adding a non-existent user to their roster
for (String userName: rosterMap.keySet()) {
for (RosterItem ri: rosterMap.get(userName)) {
try {
// If the contact is a local user then check that the user exists
if (serverName.equals(ri.getJid().getDomain())) {
userManager.getUser(removeDoman(ri.getJid()));
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);
}
rosterItemProvider.createItem(userName, ri);
}
catch (UserNotFoundException e) {
Log.info("User '" + removeDoman(ri.getJid()) + "' not found, will not be added to '" + userName + "' roster.");
catch (StringprepException se) {
Log.info("Invalid username " + userName);
invalidUsers.add(userName);
}
catch (UserAlreadyExistsException e) {
Log.info("User '" + removeDoman(ri.getJid()) + "' already belongs to '" + userName + "' roster.");
Log.info("User already exists " + userName);
invalidUsers.add(userName);
}
catch (UserNotFoundException e) {
Log.info("User not found " + userName);
invalidUsers.add(userName);
}
}
}
return duplicateUsers;
}
private static String removeDoman(JID jid) {
StringTokenizer tokens = new StringTokenizer(jid.toBareJID(), "@");
if (tokens.hasMoreTokens()) {
return tokens.nextToken();
}
return null;
return invalidUsers;
}
}
......@@ -6,7 +6,7 @@
response.setContentType("application/x-download");
response.setHeader("Content-Disposition","attachment;filename="+fileName+".xml");
ImportExportPlugin plugin = (ImportExportPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("userimportexport");
byte[] content = plugin.exportUsersToFile();
byte[] content = plugin.exportUsersToByteArray();
OutputStream os = response.getOutputStream();
os.write(content);
os.flush();
......
<%@ page import="java.io.IOException,
java.util.*,
java.util.*,
org.jivesoftware.wildfire.plugin.ImportExportPlugin,
org.jivesoftware.wildfire.XMPPServer,
org.jivesoftware.util.ParamUtils"
......@@ -85,13 +85,12 @@
<form action="export-user-data.jsp?exportUsers" method="post">
<fieldset>
<legend>Export Options</legend>
<div>
<div class="jive-contentBoxHeader">Export Options</div>
<div class="jive-contentBox">
<p>
Select the radio button next to the desired export option and then click on the Export button.
</p>
<table cellpadding="3" cellspacing="0" border="0" width="100%">
<tbody>
<tr>
......@@ -112,10 +111,7 @@
</tr>
</tbody>
</table>
</div>
</fieldset>
<br><br>
</div>
<input type="submit" value="Export">
</form>
......
......@@ -16,12 +16,6 @@
<p>
<% if (plugin.isUserProviderReadOnly()) { %>
Sorry, because you are using LDAP as your user store, this plugin will not work with your Wildfire installation.
<% } else { %>
The import and export functions allow you to read data into and write user
data from your Wildfire installation.
......@@ -30,7 +24,13 @@ data from your Wildfire installation.
<li><a href="export-user-data.jsp">Export User Data</a></li>
</ul>
<% if (plugin.isUserProviderReadOnly()) { %>
Note: because you are using a read-only user data store such as LDAP or POP3 you will only be able to import user roster data, not users themselves.
Please see the <a href="../../plugin-admin.jsp?plugin=userimportexport&showReadme=true&decorator=none">readme</a> for details.
<% } %>
</p>
</body>
</html>
......@@ -10,7 +10,6 @@
<%
boolean importUsers = request.getParameter("importUsers") != null;
boolean success = request.getParameter("success") != null;
ImportExportPlugin plugin = (ImportExportPlugin) XMPPServer.getInstance().getPluginManager().getPlugin("userimportexport");
List<String> duplicateUsers = new ArrayList<String>();
......@@ -41,8 +40,8 @@
response.sendRedirect("import-user-data.jsp?success=true");
return;
}
errors.put("userAlreadyExists", "userAlreadyExists");
errors.put("invalidUser", "invalidUser");
}
catch (MalformedURLException e) {
errors.put("IOException", "IOException");
......@@ -80,8 +79,13 @@
Import failed.
<% } else if (errors.containsKey("invalidUserFile")) { %>
The import file does not match the user schema.
<% } else if (errors.containsKey("userAlreadyExists")) { %>
The following users are already exist in the system and were not loaded:<br>
<% } else if (errors.containsKey("invalidUser")) { %>
<% if (plugin.isUserProviderReadOnly()) { %>
The following users did not exist in the system or have invalid username so their roster was not loaded:<br>
<% } else { %>
The following users already exist in the system or have invalid username and were not loaded:<br>
<% } %>
<%
Iterator iter = duplicateUsers.iterator();
while (iter.hasNext()) {
......@@ -108,7 +112,11 @@
<tbody>
<tr>
<td class="jive-icon"><img src="images/success-16x16.gif" width="16" height="16" border="0"></td>
<td class="jive-icon-label">All users added successfully.</td>
<% if (plugin.isUserProviderReadOnly()) { %>
<td class="jive-icon-label">User roster data added successfully.</td>
<% } else { %>
<td class="jive-icon-label">All users added successfully.</td>
<% } %>
</tr>
</tbody>
</table>
......@@ -122,9 +130,8 @@ Use the form below to import a user data XML file.
<form action="import-user-data.jsp?importUsers" method="post" enctype="multipart/form-data">
<fieldset>
<legend>Import</legend>
<div>
<div class="jive-contentBoxHeader">Import</div>
<div class="jive-contentBox">
<p>
Choose a file to import:</p>
<input type="file" name="thefile">
......@@ -136,10 +143,7 @@ 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=""/>
</div>
</fieldset>
<br><br>
</div>
<input type="submit" value="Import">
</form>
......
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