org.jivesoftware.openfire.component.ExternalComponentManager Maven / Gradle / Ivy
The newest version!
/*
* 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.component;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.ConnectionManager;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.component.ExternalComponentConfiguration.Permission;
import org.jivesoftware.openfire.session.ComponentSession;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.ModificationNotAllowedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages the connection permissions for external components. When an external component is
* allowed to connect to this server then a special configuration for the component will be kept.
* The configuration holds information such as the shared secret that the component should use
* when authenticating with the server.
*
* @author Gaston Dombiak
*/
public class ExternalComponentManager {
private static final Logger Log = LoggerFactory.getLogger(ExternalComponentManager.class);
private static final String ADD_CONFIGURATION =
"INSERT INTO ofExtComponentConf (subdomain,wildcard,secret,permission) VALUES (?,?,?,?)";
private static final String DELETE_CONFIGURATION =
"DELETE FROM ofExtComponentConf WHERE subdomain=? and wildcard=?";
private static final String LOAD_CONFIGURATION =
"SELECT secret,permission FROM ofExtComponentConf where subdomain=? AND wildcard=0";
private static final String LOAD_WILDCARD_CONFIGURATION =
"SELECT secret,permission FROM ofExtComponentConf where ? like subdomain AND wildcard=1";
private static final String LOAD_CONFIGURATIONS =
"SELECT subdomain,wildcard,secret FROM ofExtComponentConf where permission=?";
/**
* List of listeners that will be notified when vCards are created, updated or deleted.
*/
private static List listeners =
new CopyOnWriteArrayList<>();
/**
* @deprecated Obtain and use the corresponding {@link org.jivesoftware.openfire.spi.ConnectionListener} instead.
*/
@Deprecated
public static void setServiceEnabled(boolean enabled) throws ModificationNotAllowedException {
// Alert listeners about this event
for (ExternalComponentManagerListener listener : listeners) {
listener.serviceEnabled(enabled);
}
ConnectionManager connectionManager = XMPPServer.getInstance().getConnectionManager();
connectionManager.enableComponentListener(enabled);
}
/**
* @deprecated Obtain and use the corresponding {@link org.jivesoftware.openfire.spi.ConnectionListener} instead.
*/
@Deprecated
public static boolean isServiceEnabled() {
ConnectionManager connectionManager = XMPPServer.getInstance().getConnectionManager();
return connectionManager.isComponentListenerEnabled();
}
/**
* @deprecated Obtain and use the corresponding {@link org.jivesoftware.openfire.spi.ConnectionListener} instead.
*/
@Deprecated
public static void setServicePort(int port) throws ModificationNotAllowedException {
// Alert listeners about this event
for (ExternalComponentManagerListener listener : listeners) {
listener.portChanged(port);
}
ConnectionManager connectionManager = XMPPServer.getInstance().getConnectionManager();
connectionManager.setComponentListenerPort(port);
}
/**
* @deprecated Obtain and use the corresponding {@link org.jivesoftware.openfire.spi.ConnectionListener} instead.
*/
@Deprecated
public static int getServicePort() {
ConnectionManager connectionManager = XMPPServer.getInstance().getConnectionManager();
return connectionManager.getComponentListenerPort();
}
/**
* Allows an external component to connect to the local server with the specified configuration.
*
* @param configuration the configuration for the external component.
* @throws ModificationNotAllowedException if the operation was denied.
*/
public static void allowAccess(ExternalComponentConfiguration configuration) throws ModificationNotAllowedException {
// Alert listeners about this event
for (ExternalComponentManagerListener listener : listeners) {
listener.componentAllowed(configuration.getSubdomain(), configuration);
}
// Remove any previous configuration for this external component
deleteConfigurationFromDB(configuration);
// Update the database with the new granted permission and configuration
configuration.setPermission(Permission.allowed);
addConfiguration(configuration);
}
/**
* Blocks an external component from connecting to the local server. If the component was
* connected when the permission was revoked then the connection of the entity will be closed.
*
* @param subdomain the subdomain of the external component that is not allowed to connect.
* @throws ModificationNotAllowedException if the operation was denied.
*/
public static void blockAccess(String subdomain) throws ModificationNotAllowedException {
// Alert listeners about this event
for (ExternalComponentManagerListener listener : listeners) {
listener.componentBlocked(subdomain);
}
// Remove any previous configuration for this external component
deleteConfigurationFromDB(getConfiguration(subdomain, false));
// Update the database with the new revoked permission
ExternalComponentConfiguration config = new ExternalComponentConfiguration(subdomain, false, Permission.blocked, null);
addConfiguration(config);
// Check if the component was connected and proceed to close the connection
String domain = subdomain + "." + XMPPServer.getInstance().getServerInfo().getXMPPDomain();
Session session = SessionManager.getInstance().getComponentSession(domain);
if (session != null) {
session.close();
}
}
/**
* Returns true if the external component with the specified subdomain can connect to the
* local server.
*
* @param subdomain the subdomain of the external component.
* @return true if the external component with the specified subdomain can connect to the
* local server.
*/
public static boolean canAccess(String subdomain) {
// By default there is no permission defined for the XMPP entity
Permission permission = null;
ExternalComponentConfiguration config = getConfiguration(subdomain, true);
if (config != null) {
permission = config.getPermission();
}
if (PermissionPolicy.blacklist == getPermissionPolicy()) {
// Anyone can access except those entities listed in the blacklist
return Permission.blocked != permission;
}
else {
// Access is limited to those present in the whitelist
return Permission.allowed == permission;
}
}
/**
* Returns true if there is a configuration for the specified subdomain. This
* checking can be used as an indirect way of checking that the specified
* subdomain belongs to an external component.
*
* @param subdomain the subdomain of the external component.
* @return true if there is a configuration for the specified subdomain.
*/
public static boolean hasConfiguration(String subdomain) {
return getConfiguration(subdomain, true) != null;
}
/**
* Returns the list of registered external components that are allowed to connect to this
* server when using a whitelist policy. However, when using a blacklist policy (i.e. anyone
* may connect to the server) the returned list of configurations will be used for obtaining
* the shared secret specific for each component.
*
* @return the configuration of the registered external components.
*/
public static Collection getAllowedComponents() {
return getConfigurations(Permission.allowed);
}
/**
* Returns the list of external components that are NOT allowed to connect to this
* server.
*
* @return the configuration of the blocked external components.
*/
public static Collection getBlockedComponents() {
return getConfigurations(Permission.blocked);
}
public static void updateComponentSecret(String subdomain, String secret) throws ModificationNotAllowedException {
// Alert listeners about this event
for (ExternalComponentManagerListener listener : listeners) {
listener.componentSecretUpdated(subdomain, secret);
}
ExternalComponentConfiguration configuration = getConfiguration(subdomain, false);
if (configuration != null) {
configuration.setPermission(Permission.allowed);
configuration.setSecret(secret);
// Remove any previous configuration for this external component
deleteConfigurationFromDB(configuration);
}
else {
configuration = new ExternalComponentConfiguration(subdomain, false, Permission.allowed, secret);
}
addConfiguration(configuration);
}
/**
* Removes any existing defined permission and configuration for the specified
* external component.
*
* @param subdomain the subdomain of the external component.
* @throws ModificationNotAllowedException if the operation was denied.
*/
public static void deleteConfiguration(String subdomain) throws ModificationNotAllowedException {
// Alert listeners about this event
for (ExternalComponentManagerListener listener : listeners) {
listener.componentConfigurationDeleted(subdomain);
}
// Proceed to delete the configuration of the component
deleteConfigurationFromDB(getConfiguration(subdomain, false));
}
/**
* Removes any existing defined permission and configuration for the specified
* external component from the database.
*
* @param configuration the external component configuration to delete.
*/
private static void deleteConfigurationFromDB(ExternalComponentConfiguration configuration) {
if (configuration == null) {
// Do nothing
return;
}
// Remove the permission for the entity from the database
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(DELETE_CONFIGURATION);
pstmt.setString(1, configuration.getSubdomain() + (configuration.isWildcard() ? "%" : ""));
pstmt.setInt(2, configuration.isWildcard() ? 1 : 0);
pstmt.executeUpdate();
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
/**
* Adds a new permission for the specified external component.
*
* @param configuration the new configuration for a component.
*/
private static void addConfiguration(ExternalComponentConfiguration configuration) {
// Remove the permission for the entity from the database
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ADD_CONFIGURATION);
pstmt.setString(1, configuration.getSubdomain() + (configuration.isWildcard() ? "%" : ""));
pstmt.setInt(2, configuration.isWildcard() ? 1 : 0);
pstmt.setString(3, configuration.getSecret());
pstmt.setString(4, configuration.getPermission().toString());
pstmt.executeUpdate();
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
/**
* Returns the configuration for an external component. A query for the exact requested
* subdomain will be made. If nothing was found and using wildcards is requested then
* another query will be made but this time using wildcards.
*
* @param subdomain the subdomain of the external component.
* @param useWildcard true if an attempt to find a subdomain with wildcards should be attempted.
* @return the configuration for an external component.
*/
private static ExternalComponentConfiguration getConfiguration(String subdomain, boolean useWildcard) {
ExternalComponentConfiguration configuration = null;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// Check if there is a configuration for the subdomain
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_CONFIGURATION);
pstmt.setString(1, subdomain);
rs = pstmt.executeQuery();
while (rs.next()) {
configuration = new ExternalComponentConfiguration(subdomain, false, Permission.valueOf(rs.getString(2)),
rs.getString(1));
}
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
if (configuration == null && useWildcard) {
// Check if there is a configuration that is using wildcards for domains
try {
// Check if there is a configuration for the subdomain
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_WILDCARD_CONFIGURATION);
pstmt.setString(1, subdomain);
rs = pstmt.executeQuery();
while (rs.next()) {
configuration = new ExternalComponentConfiguration(subdomain, true, Permission.valueOf(rs.getString(2)),
rs.getString(1));
}
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
}
return configuration;
}
private static Collection getConfigurations(
Permission permission) {
Collection answer =
new ArrayList<>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_CONFIGURATIONS);
pstmt.setString(1, permission.toString());
rs = pstmt.executeQuery();
ExternalComponentConfiguration configuration;
while (rs.next()) {
String subdomain = rs.getString(1);
boolean wildcard = rs.getInt(2) == 1;
// Remove the trailing % if using wildcards
subdomain = wildcard ? subdomain.substring(0, subdomain.length()-1) : subdomain;
configuration = new ExternalComponentConfiguration(subdomain, wildcard, permission,
rs.getString(3));
answer.add(configuration);
}
}
catch (SQLException sqle) {
Log.error(sqle.getMessage(), sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return answer;
}
/**
* Returns the default secret key to use for those external components that don't have an
* individual configuration.
*
* @return the default secret key to use for those external components that don't have an
* individual configuration.
*/
public static String getDefaultSecret() {
return JiveGlobals.getProperty("xmpp.component.defaultSecret");
}
/**
* Sets the default secret key to use for those external components that don't have an
* individual configuration.
*
* @param defaultSecret the default secret key to use for those external components that
* don't have an individual configuration.
* @throws ModificationNotAllowedException if the operation was denied.
*/
public static void setDefaultSecret(String defaultSecret) throws ModificationNotAllowedException {
// Alert listeners about this event
for (ExternalComponentManagerListener listener : listeners) {
listener.defaultSecretChanged(defaultSecret);
}
JiveGlobals.setProperty("xmpp.component.defaultSecret", defaultSecret);
}
/**
* Returns the shared secret with the specified external component. If no shared secret was
* defined then use the default shared secret.
*
* @param subdomain the subdomain of the external component to get his shared secret.
* (e.g. conference)
* @return the shared secret with the specified external component or the default shared secret.
*/
public static String getSecretForComponent(String subdomain) {
// By default there is no shared secret defined for the XMPP entity
String secret = null;
ExternalComponentConfiguration config = getConfiguration(subdomain, true);
if (config != null) {
secret = config.getSecret();
}
secret = (secret == null ? getDefaultSecret() : secret);
if (secret == null) {
Log.error("Setup for external components is incomplete. Property " +
"xmpp.component.defaultSecret does not exist.");
}
return secret;
}
/**
* Returns the permission policy being used for new XMPP entities that are trying to
* connect to the server. There are two types of policies: 1) blacklist: where any entity
* is allowed to connect to the server except for those listed in the black list and
* 2) whitelist: where only the entities listed in the white list are allowed to connect to
* the server.
*
* @return the permission policy being used for new XMPP entities that are trying to
* connect to the server.
*/
public static PermissionPolicy getPermissionPolicy() {
try {
return PermissionPolicy.valueOf(JiveGlobals.getProperty("xmpp.component.permission",
PermissionPolicy.blacklist.toString()));
}
catch (Exception e) {
Log.error(e.getMessage(), e);
return PermissionPolicy.blacklist;
}
}
/**
* Sets the permission policy being used for new XMPP entities that are trying to
* connect to the server. There are two types of policies: 1) blacklist: where any entity
* is allowed to connect to the server except for those listed in the black list and
* 2) whitelist: where only the entities listed in the white list are allowed to connect to
* the server.
*
* @param policy the new PermissionPolicy to use.
* @throws ModificationNotAllowedException if the operation was denied.
*/
public static void setPermissionPolicy(PermissionPolicy policy) throws ModificationNotAllowedException {
// Alert listeners about this event
for (ExternalComponentManagerListener listener : listeners) {
listener.permissionPolicyChanged(policy);
}
JiveGlobals.setProperty("xmpp.component.permission", policy.toString());
// Check if connected components can remain connected to the server
for (ComponentSession session : SessionManager.getInstance().getComponentSessions()) {
for (String domain : session.getExternalComponent().getSubdomains()) {
if (!canAccess(domain)) {
session.close();
break;
}
}
}
}
/**
* Sets the permission policy being used for new XMPP entities that are trying to
* connect to the server. There are two types of policies: 1) blacklist: where any entity
* is allowed to connect to the server except for those listed in the black list and
* 2) whitelist: where only the entities listed in the white list are allowed to connect to
* the server.
*
* @param policy the new policy to use.
* @throws ModificationNotAllowedException if the operation was denied.
*/
public static void setPermissionPolicy(String policy) throws ModificationNotAllowedException {
setPermissionPolicy(PermissionPolicy.valueOf(policy));
}
/**
* Registers a listener to receive events when a configuration change happens. Listeners
* have the chance to deny the operation from happening.
*
* @param listener the listener.
*/
public static void addListener(ExternalComponentManagerListener listener) {
if (listener == null) {
throw new NullPointerException();
}
listeners.add(listener);
}
/**
* Unregisters a listener to receive events.
*
* @param listener the listener.
*/
public static void removeListener(ExternalComponentManagerListener listener) {
listeners.remove(listener);
}
public enum PermissionPolicy {
/**
* Any XMPP entity is allowed to connect to the server except for those listed in
* the not allowed list.
*/
blacklist,
/**
* Only the XMPP entities listed in the allowed list are able to connect to
* the server.
*/
whitelist
}
}