Commit b4422b27 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

1) Added permission checking by IP address.

2) User can now be added or removed from groups
3) username paramter is now always used for all operations (no long mix of jid and username)

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@9308 b35dd754-fafc-0310-a699-88a17e54d16e
parent 9be973f9
......@@ -44,6 +44,13 @@
User Service Plugin Changelog
</h1>
<p><b>1.3.0</b> -- October 15, 2007</p>
<ul>
<li>Added support for filtering by IP address.</li>
<li>Added support for assigning user to groups.</li>
<li>Replaced <b>jid</b> parameter with <b>username</b> for all operations.</li>
</ul>
<p><b>1.2.0</b> -- April 12, 2007</p>
<ul>
<li>Now requires Openfire 3.3.0.</li>
......
......@@ -96,30 +96,29 @@ The following parameters can be passed into the request:<p>
Possible values are add, delete, update</td>
</tr>
<tr>
<td class="name">secret</td><td>required</td>
<td class="name">secret</td><td>Required</td>
<td>The secret key that allows access to the User Service.</td>
</tr>
<tr>
<td class="name">jid</td><td>required for delete and update operations</td>
<td>The bare JID (address) of the user to be administered(ie includes @yourdomain.com).
</td>
</tr>
<tr>
<td class="name">username</td><td>required for add operation</td>
<td>The username of the new user. ie the part before the @ symbol.</td>
<td class="name">username</td><td>Required</td>
<td>The username of the user to add, update or delete. ie the part before the @ symbol.</td>
</tr>
<tr>
<td class="name">password</td><td>required for add operation</td>
<td class="name">password</td><td>Required for add operation</td>
<td>The password of the new user or the user being updated.</td>
</tr>
<tr>
<td class="name">name</td><td>optional</td>
<td class="name">name</td><td>Optional</td>
<td>The display name of the new user or the user being updated.</td>
</tr>
<tr>
<td class="name">email</td><td>optional</td>
<td class="name">email</td><td>Optional</td>
<td>The email address of the new user or the user being updated.</td>
</tr>
<tr>
<td class="name">groups</td><td>Optional</td>
<td>List of groups where the user is a member. Values are comma delimited.</td>
</tr>
</table><p>
......@@ -142,7 +141,7 @@ The following example deletes a user
<ul>
<form>
<textarea cols=65 rows=4 wrap=virtual>
http://example.com:9090/plugins/userService/userservice?type=delete&secret=bigsecret&jid=kafka@example.com
http://example.com:9090/plugins/userService/userservice?type=delete&secret=bigsecret&username=kafka
</textarea>
</form>
</ul>
......@@ -152,7 +151,7 @@ This example updates a user
<ul>
<form>
<textarea cols=65 rows=3 wrap=virtual>
http://example.com:9090/plugins/userService/userservice?type=update&secret=bigsecret&jid=kafka@example.com&password=drowssap&name=franz&email=beetle@kafka.com
http://example.com:9090/plugins/userService/userservice?type=update&secret=bigsecret&username=kafka&password=drowssap&name=franz&email=beetle@kafka.com
</textarea>
</form>
</ul>
......@@ -192,7 +191,7 @@ If the request was unsuccessful, the return will be an "error" element with a te
</tr>
<tr>
<td class="name">RequestNotAuthorised</td>
<td>The supplied secret does not match the secret specified in the Admin Console.</td>
<td>The supplied secret does not match the secret specified in the Admin Console or the requester is not a valid IP address.</td>
</tr>
<tr>
<td class="name">UserServiceDisabled</td>
......
......@@ -10,35 +10,41 @@
package org.jivesoftware.openfire.plugin;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.group.Group;
import org.jivesoftware.openfire.group.GroupManager;
import org.jivesoftware.openfire.group.GroupNotFoundException;
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.UserAlreadyExistsException;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.xmpp.packet.JID;
import java.io.File;
import java.util.*;
/**
* Plugin that allows the administration of users via HTTP requests.
*
* @author Justin Hunt
*/
public class UserServicePlugin implements Plugin {
public class UserServicePlugin implements Plugin, PropertyEventListener {
private UserManager userManager;
private String hostname;
private XMPPServer server;
private String secret;
private boolean enabled;
private Collection<String> allowedIPs;
public void initializePlugin(PluginManager manager, File pluginDirectory) {
XMPPServer server = XMPPServer.getInstance();
server = XMPPServer.getInstance();
userManager = server.getUserManager();
hostname = server.getServerInfo().getName();
secret = JiveGlobals.getProperty("plugin.userservice.secret", "");
// If no secret key has been assigned to the user service yet, assign a random one.
......@@ -49,52 +55,96 @@ public class UserServicePlugin implements Plugin {
// See if the service is enabled or not.
enabled = JiveGlobals.getBooleanProperty("plugin.userservice.enabled", false);
// Get the list of IP addresses that can use this service. An empty list means that this filter is disabled.
allowedIPs = StringUtils.stringToCollection(JiveGlobals.getProperty("plugin.userservice.allowedIPs", ""));
// Listen to system property events
PropertyEventDispatcher.addListener(this);
}
public void destroyPlugin() {
userManager = null;
// Stop listening to system property events
PropertyEventDispatcher.removeListener(this);
}
public void createUser(String username, String password, String name, String email)
public void createUser(String username, String password, String name, String email, String groupNames)
throws UserAlreadyExistsException
{
userManager.createUser(username, password, name, email);
userManager.createUser(username, password, name, email);
if (groupNames != null) {
Collection<Group> groups = new ArrayList<Group>();
StringTokenizer tkn = new StringTokenizer(groupNames, ",");
while (tkn.hasMoreTokens()) {
try {
groups.add(GroupManager.getInstance().getGroup(tkn.nextToken()));
} catch (GroupNotFoundException e) {
// Ignore this group
}
}
for (Group group : groups) {
group.getMembers().add(server.createJID(username, null));
}
}
}
public void deleteUser(String jid) throws UserNotFoundException{
User user = getUser(jid);
public void deleteUser(String username) throws UserNotFoundException{
User user = getUser(username);
userManager.deleteUser(user);
}
public void updateUser(String jid, String password, String name, String email)
public void updateUser(String username, String password, String name, String email, String groupNames)
throws UserNotFoundException
{
User user = getUser(jid);
User user = getUser(username);
user.setPassword(password);
user.setName(name);
user.setEmail(email);
if (groupNames != null) {
Collection<Group> newGroups = new ArrayList<Group>();
StringTokenizer tkn = new StringTokenizer(groupNames, ",");
while (tkn.hasMoreTokens()) {
try {
newGroups.add(GroupManager.getInstance().getGroup(tkn.nextToken()));
} catch (GroupNotFoundException e) {
// Ignore this group
}
}
Collection<Group> existingGroups = GroupManager.getInstance().getGroups(user);
// Get the list of groups to add to the user
Collection<Group> groupsToAdd = new ArrayList<Group>(newGroups);
groupsToAdd.removeAll(existingGroups);
// Get the list of groups to remove from the user
Collection<Group> groupsToDelete = new ArrayList<Group>(existingGroups);
groupsToDelete.removeAll(newGroups);
// Add the user to the new groups
for (Group group : groupsToAdd) {
group.getMembers().add(server.createJID(username, null));
}
// Remove the user from the old groups
for (Group group : groupsToDelete) {
group.getMembers().remove(server.createJID(username, null));
}
}
}
/**
* Returns the the requested user or <tt>null</tt> if there are any
* problems that don't throw an error.
*
* @param jid the bare JID of the entity whose presence is being probed.
* @param username the username of the local user to retrieve.
* @return the requested user.
* @throws UserNotFoundException if the requested user
* does not exist in the local server.
*/
private User getUser(String jid) throws UserNotFoundException {
JID targetJID = new JID(jid);
private User getUser(String username) throws UserNotFoundException {
JID targetJID = server.createJID(username, null);
// Check that the sender is not requesting information of a remote server entity
if (targetJID.getDomain() == null || XMPPServer.getInstance().isRemote(targetJID)) {
throw new UserNotFoundException("Domain does not matches local server domain");
}
if (!hostname.equals(targetJID.getDomain())) {
// Sender is requesting information about component presence
// TODO Implement this
throw new UserNotFoundException("Presence of components not supported yet!");
}
if (targetJID.getNode() == null) {
// Sender is requesting presence information of an anonymous user
throw new UserNotFoundException("Username is null");
......@@ -121,6 +171,15 @@ public class UserServicePlugin implements Plugin {
this.secret = secret;
}
public Collection<String> getAllowedIPs() {
return allowedIPs;
}
public void setAllowedIPs(Collection<String> allowedIPs) {
JiveGlobals.setProperty("plugin.userservice.allowedIPs", StringUtils.collectionToString(allowedIPs));
this.allowedIPs = allowedIPs;
}
/**
* Returns true if the user service is enabled. If not enabled, it will not accept
* requests to create new accounts.
......@@ -141,4 +200,36 @@ public class UserServicePlugin implements Plugin {
this.enabled = enabled;
JiveGlobals.setProperty("plugin.userservice.enabled", enabled ? "true" : "false");
}
public void propertySet(String property, Map<String, Object> params) {
if (property.equals("plugin.userservice.secret")) {
this.secret = (String)params.get("value");
}
else if (property.equals("plugin.userservice.enabled")) {
this.enabled = Boolean.parseBoolean((String)params.get("value"));
}
else if (property.equals("plugin.userservice.allowedIPs")) {
this.allowedIPs = StringUtils.stringToCollection((String)params.get("value"));
}
}
public void propertyDeleted(String property, Map<String, Object> params) {
if (property.equals("plugin.userservice.secret")) {
this.secret = "";
}
else if (property.equals("plugin.userservice.enabled")) {
this.enabled = false;
}
else if (property.equals("plugin.userservice.allowedIPs")) {
this.allowedIPs = Collections.emptyList();
}
}
public void xmlPropertySet(String property, Map<String, Object> params) {
// Do nothing
}
public void xmlPropertyDeleted(String property, Map<String, Object> params) {
// Do nothing
}
}
\ No newline at end of file
......@@ -56,19 +56,38 @@ public class UserServiceServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
// Printwriter for writing out responses to browser
PrintWriter out = response.getWriter();
if (!plugin.getAllowedIPs().isEmpty()) {
// Get client's IP address
String ipAddress = request.getHeader("x-forwarded-for");
if (ipAddress == null) {
ipAddress = request.getHeader("X_FORWARDED_FOR");
if (ipAddress == null) {
ipAddress = request.getHeader("X-Forward-For");
if (ipAddress == null) {
ipAddress = request.getRemoteAddr();
}
}
}
if (!plugin.getAllowedIPs().contains(ipAddress)) {
Log.warn("User service rejected service to IP address: " + ipAddress);
replyError("RequestNotAuthorised",response, out);
return;
}
}
String username = request.getParameter("username");
String password = request.getParameter("password");
String name = request.getParameter("name");
String email = request.getParameter("email");
String jid = request.getParameter("jid");
String type = request.getParameter("type");
String secret = request.getParameter("secret");
String groupNames = request.getParameter("groups");
//No defaults, add, delete, update only
//type = type == null ? "image" : type;
// Printwriter for writing out responses to browser
PrintWriter out = response.getWriter();
// Check that our plugin is enabled.
if (!plugin.isEnabled()) {
Log.warn("User service plugin is disabled: " + request.getQueryString());
......@@ -85,17 +104,17 @@ public class UserServiceServlet extends HttpServlet {
// Check the request type and process accordingly
try {
if ("add".equals(type)) {
plugin.createUser(username, password, name, email);
plugin.createUser(username, password, name, email, groupNames);
replyMessage("ok",response, out);
//imageProvider.sendInfo(request, response, presence);
}
else if ("delete".equals(type)) {
plugin.deleteUser(jid);
plugin.deleteUser(username);
replyMessage("ok",response,out);
//xmlProvider.sendInfo(request, response, presence);
}
else if ("update".equals(type)) {
plugin.updateUser(jid, password,name,email);
plugin.updateUser(username, password,name,email, groupNames);
replyMessage("ok",response,out);
//xmlProvider.sendInfo(request, response, presence);
}
......
<%@ page import="java.util.*,
org.jivesoftware.admin.*,
org.jivesoftware.openfire.XMPPServer,
org.jivesoftware.util.*,
org.jivesoftware.openfire.plugin.UserServicePlugin"
......@@ -19,6 +18,7 @@
boolean success = request.getParameter("success") != null;
String secret = ParamUtils.getParameter(request, "secret");
boolean enabled = ParamUtils.getBooleanParameter(request, "enabled");
String allowedIPs = ParamUtils.getParameter(request, "allowedIPs");
UserServicePlugin plugin = (UserServicePlugin) XMPPServer.getInstance().getPluginManager().getPlugin("userservice");
......@@ -26,8 +26,9 @@
Map errors = new HashMap();
if (save) {
if (errors.size() == 0) {
plugin.setEnabled(enabled);
plugin.setEnabled(enabled);
plugin.setSecret(secret);
plugin.setAllowedIPs(StringUtils.stringToCollection(allowedIPs));
response.sendRedirect("user-service.jsp?success=true");
return;
}
......@@ -35,6 +36,7 @@
secret = plugin.getSecret();
enabled = plugin.isEnabled();
allowedIPs = StringUtils.collectionToString(plugin.getAllowedIPs());
%>
<html>
......@@ -76,9 +78,10 @@ HTTP requests to the service will be ignored.
simple integration with other applications.</p>
<p>However, the presence of this service exposes a security risk. Therefore,
a secret key is used to validate legitimate requests to this service. For
full security, it's recommended that you deploy other security measures in front
of the user service, such as restricted network access.
a secret key is used to validate legitimate requests to this service. Moreover,
for extra security you can specify the list of IP addresses that are allowed to
use this service. An empty list means that the service can be accessed from any
location. Addresses are delimited by commas.
</p>
<ul>
<input type="radio" name="enabled" value="true" id="rb01"
......@@ -92,6 +95,10 @@ HTTP requests to the service will be ignored.
<label for="text_secret">Secret key:</label>
<input type="text" name="secret" value="<%= secret %>" id="text_secret">
<br><br>
<label for="text_secret">Allowed IP Addresses:</label>
<textarea name="allowedIPs" cols="40" rows="3" wrap="virtual"><%= ((allowedIPs != null) ? allowedIPs : "") %></textarea>
</ul>
</div>
</fieldset>
......
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