/**
* $Revision$
* $Date$
*
* 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.
*/
package org.jivesoftware.openfire.clearspace;
import static org.jivesoftware.openfire.clearspace.ClearspaceManager.HttpType.GET;
import static org.jivesoftware.openfire.clearspace.ClearspaceManager.HttpType.PUT;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.lockout.LockOutFlag;
import org.jivesoftware.openfire.lockout.LockOutProvider;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
/**
* The ClearspaceLockOutProvider uses the UserService web service inside of Clearspace
* to retrieve user properties from Clearspace. One of these properties refers to whether
* the user is disabled or not. In the future we may implement this in a different manner
* that will require less overall communication with Clearspace.
*
* @author Daniel Henninger
*/
public class ClearspaceLockOutProvider implements LockOutProvider {
private static final Logger Log = LoggerFactory.getLogger(ClearspaceLockOutProvider.class);
protected static final String USER_URL_PREFIX = "userService/";
/**
* Generate a ClearspaceLockOutProvider instance.
*/
public ClearspaceLockOutProvider() {
}
/**
* The ClearspaceLockOutProvider will retrieve lockout information from Clearspace's user properties.
* @see org.jivesoftware.openfire.lockout.LockOutProvider#getDisabledStatus(String)
*/
public LockOutFlag getDisabledStatus(String username) {
try {
// Retrieve the disabled status, translate it into a LockOutFlag, and return it.
return checkUserDisabled(getUserByUsername(username));
}
catch (UserNotFoundException e) {
// Not a valid user? We will leave it up to the user provider to handle rejecting this user.
Log.warn(e.getMessage(), e);
return null;
}
}
/**
* The ClearspaceLockOutProvider will set lockouts in Clearspace itself.
* @see org.jivesoftware.openfire.lockout.LockOutProvider#setDisabledStatus(org.jivesoftware.openfire.lockout.LockOutFlag)
*/
public void setDisabledStatus(LockOutFlag flag) {
setEnabledStatus(flag.getUsername(), false);
}
/**
* The ClearspaceLockOutProvider will set lockouts in Clearspace itself.
* @see org.jivesoftware.openfire.lockout.LockOutProvider#unsetDisabledStatus(String)
*/
public void unsetDisabledStatus(String username) {
setEnabledStatus(username, true);
}
/**
* The ClearspaceLockOutProvider will set lockouts in Clearspace itself.
* @see org.jivesoftware.openfire.lockout.LockOutProvider#isReadOnly()
*/
public boolean isReadOnly() {
return false;
}
/**
* Clearspace only supports a strict "are you disabled or not".
* @see org.jivesoftware.openfire.lockout.LockOutProvider#isDelayedStartSupported()
*/
public boolean isDelayedStartSupported() {
return false;
}
/**
* Clearspace only supports a strict "are you disabled or not".
* @see org.jivesoftware.openfire.lockout.LockOutProvider#isTimeoutSupported()
*/
public boolean isTimeoutSupported() {
return false;
}
/**
* Clearspace needs to always be queried for disabled status.
* @see org.jivesoftware.openfire.lockout.LockOutProvider#shouldNotBeCached()
*/
public boolean shouldNotBeCached() {
return true;
}
/**
* Looks up and modifies a user's CS properties to indicate whether they are enabled or disabled.
* It is important for this to incorporate the existing user data and only tweak the field
* that we want to change.
*
* @param username Username of user to set status of.
* @param enabled Whether the account should be enabled or disabled.
*/
private void setEnabledStatus(String username, Boolean enabled) {
try {
Element user = getUserByUsername(username);
Element modifiedUser = modifyUser(user.element("return"), "enabled", enabled ? "true" : "false");
String path = USER_URL_PREFIX + "users";
ClearspaceManager.getInstance().executeRequest(PUT, path, modifiedUser.asXML());
}
catch (UserNotFoundException e) {
Log.error("User with name " + username + " not found.", e);
}
catch (Exception e) {
// It is not supported exception, wrap it into an UnsupportedOperationException
throw new UnsupportedOperationException("Unexpected error", e);
}
}
/**
* Modifies user properties XML by replacing a particular attribute setting to something new.
*
* @param user User data XML.
* @param attributeName Name of attribute to replace.
* @param newValue New value for attribute.
* @return Modified element.
*/
private Element modifyUser(Element user, String attributeName, String newValue) {
Document groupDoc = DocumentHelper.createDocument();
Element rootE = groupDoc.addElement("updateUser");
Element newUser = rootE.addElement("user");
List userAttributes = user.elements();
for (Object userAttributeObj : userAttributes) {
Element userAttribute = (Element)userAttributeObj;
if (userAttribute.getName().equals(attributeName)) {
newUser.addElement(userAttribute.getName()).setText(newValue);
} else {
newUser.addElement(userAttribute.getName()).setText(userAttribute.getText());
}
}
return rootE;
}
/**
* Examines the XML returned about a user to find out if they are enabled or disabled. Returns
* <tt>null</tt> when user can log in or a LockOutFlag if user cannot log in.
*
* @param responseNode Element returned from REST service. (@see #getUserByUsername)
* @return Either a LockOutFlag indicating that the user is disabled, or null if everything is fine.
*/
private LockOutFlag checkUserDisabled(Node responseNode) {
try {
Node userNode = responseNode.selectSingleNode("return");
// Gets the username
String username = userNode.selectSingleNode("username").getText();
// Escape the username so that it can be used as a JID.
username = JID.escapeNode(username);
// Gets the enabled field
boolean isEnabled = Boolean.valueOf(userNode.selectSingleNode("enabled").getText());
if (isEnabled) {
// We're good, indicate that they're not locked out.
return null;
}
else {
// Creates the lock out flag
return new LockOutFlag(username, null, null);
}
}
catch (Exception e) {
// Hrm. This is not good. We have to opt on the side of positive.
Log.error("Error while looking up user's disabled status from Clearspace: ", e);
return null;
}
}
/**
* Retrieves user properties for a Clearspace user in XML format.
*
* @param username Username to look up.
* @return XML Element including information about the user.
* @throws UserNotFoundException The user was not found in the Clearspace database or there was an error.
*/
private Element getUserByUsername(String username) throws UserNotFoundException {
// Checks if the user is local
if (username.contains("@")) {
if (!XMPPServer.getInstance().isLocal(new JID(username))) {
throw new UserNotFoundException("Cannot load user of remote server: " + username);
}
username = username.substring(0, username.lastIndexOf("@"));
}
try {
// Un-escape username.
username = JID.unescapeNode(username);
// Encode potentially non-ASCII characters
username = URLUTF8Encoder.encode(username);
// Requests the user
String path = USER_URL_PREFIX + "users/" + username;
// return the response
return ClearspaceManager.getInstance().executeRequest(GET, path);
}
catch (UserNotFoundException e) {
throw e;
}
catch (Exception e) {
// It is not supported exception, wrap it into an UserNotFoundException
throw new UserNotFoundException("Error loading the user from Clearspace: ", e);
}
}
}