ExternalComponentManager.java 23.1 KB
Newer Older
1
/**
2
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
3
 *
4 5 6 7 8 9 10 11 12 13 14
 * 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.
15 16
 */

17
package org.jivesoftware.openfire.component;
18

19
import java.sql.Connection;
20 21 22 23 24 25 26 27
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;

28
import org.jivesoftware.database.DbConnectionManager;
29
import org.jivesoftware.openfire.ConnectionManager;
30 31 32 33 34
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;
35
import org.jivesoftware.util.JiveGlobals;
36
import org.jivesoftware.util.ModificationNotAllowedException;
37 38
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
39 40 41 42 43 44 45 46 47 48 49

/**
 * 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 {

50 51
	private static final Logger Log = LoggerFactory.getLogger(ExternalComponentManager.class);

52
    private static final String ADD_CONFIGURATION =
53
        "INSERT INTO ofExtComponentConf (subdomain,wildcard,secret,permission) VALUES (?,?,?,?)";
54
    private static final String DELETE_CONFIGURATION =
55
        "DELETE FROM ofExtComponentConf WHERE subdomain=? and wildcard=?";
56
    private static final String LOAD_CONFIGURATION =
57 58 59
        "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";
60
    private static final String LOAD_CONFIGURATIONS =
61
        "SELECT subdomain,wildcard,secret FROM ofExtComponentConf where permission=?";
62

63 64 65 66
    /**
     * List of listeners that will be notified when vCards are created, updated or deleted.
     */
    private static List<ExternalComponentManagerListener> listeners =
67
            new CopyOnWriteArrayList<>();
68

69 70 71 72
    /**
     * @deprecated Obtain and use the corresponding {@link org.jivesoftware.openfire.spi.ConnectionListener} instead.
     */
    @Deprecated
73
    public static void setServiceEnabled(boolean enabled) throws ModificationNotAllowedException {
74 75 76 77 78 79 80 81
        // Alert listeners about this event
        for (ExternalComponentManagerListener listener : listeners) {
            listener.serviceEnabled(enabled);
        }
        ConnectionManager connectionManager = XMPPServer.getInstance().getConnectionManager();
        connectionManager.enableComponentListener(enabled);
    }

82 83 84 85
    /**
     * @deprecated Obtain and use the corresponding {@link org.jivesoftware.openfire.spi.ConnectionListener} instead.
     */
    @Deprecated
86 87 88 89 90
    public static boolean isServiceEnabled() {
        ConnectionManager connectionManager = XMPPServer.getInstance().getConnectionManager();
        return connectionManager.isComponentListenerEnabled();
    }

91 92 93 94
    /**
     * @deprecated Obtain and use the corresponding {@link org.jivesoftware.openfire.spi.ConnectionListener} instead.
     */
    @Deprecated
95
    public static void setServicePort(int port) throws ModificationNotAllowedException {
96 97 98 99 100 101 102 103
        // Alert listeners about this event
        for (ExternalComponentManagerListener listener : listeners) {
            listener.portChanged(port);
        }
        ConnectionManager connectionManager = XMPPServer.getInstance().getConnectionManager();
        connectionManager.setComponentListenerPort(port);
    }

104 105 106 107
    /**
     * @deprecated Obtain and use the corresponding {@link org.jivesoftware.openfire.spi.ConnectionListener} instead.
     */
    @Deprecated
108 109 110 111 112
    public static int getServicePort() {
        ConnectionManager connectionManager = XMPPServer.getInstance().getConnectionManager();
        return connectionManager.getComponentListenerPort();
    }

113 114 115 116
    /**
     * Allows an external component to connect to the local server with the specified configuration.
     *
     * @param configuration the configuration for the external component.
117
     * @throws ModificationNotAllowedException if the operation was denied.
118
     */
119
    public static void allowAccess(ExternalComponentConfiguration configuration) throws ModificationNotAllowedException {
120 121
        // Alert listeners about this event
        for (ExternalComponentManagerListener listener : listeners) {
122
            listener.componentAllowed(configuration.getSubdomain(), configuration);
123
        }
124
        // Remove any previous configuration for this external component
125
        deleteConfigurationFromDB(configuration);
126 127 128 129 130 131 132 133 134 135
        // 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.
136
     * @throws ModificationNotAllowedException if the operation was denied.
137
     */
138
    public static void blockAccess(String subdomain) throws ModificationNotAllowedException {
139 140 141 142
        // Alert listeners about this event
        for (ExternalComponentManagerListener listener : listeners) {
            listener.componentBlocked(subdomain);
        }
143
        // Remove any previous configuration for this external component
144
        deleteConfigurationFromDB(getConfiguration(subdomain, false));
145
        // Update the database with the new revoked permission
146
        ExternalComponentConfiguration config = new ExternalComponentConfiguration(subdomain, false, Permission.blocked, null);
147 148
        addConfiguration(config);
        // Check if the component was connected and proceed to close the connection
149
        String domain = subdomain + "." + XMPPServer.getInstance().getServerInfo().getXMPPDomain();
150 151
        Session session = SessionManager.getInstance().getComponentSession(domain);
        if (session != null) {
152
            session.close();
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
        }
    }

    /**
     * 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;

168
        ExternalComponentConfiguration config = getConfiguration(subdomain, true);
169 170 171 172 173 174
        if (config != null) {
            permission = config.getPermission();
        }

        if (PermissionPolicy.blacklist == getPermissionPolicy()) {
            // Anyone can access except those entities listed in the blacklist
175
            return Permission.blocked != permission;
176 177 178
        }
        else {
            // Access is limited to those present in the whitelist
179
            return Permission.allowed == permission;
180 181 182
        }
    }

183 184 185 186 187 188 189 190 191 192 193 194
    /**
     * 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;
    }

195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
    /**
     * 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<ExternalComponentConfiguration> 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<ExternalComponentConfiguration> getBlockedComponents() {
        return getConfigurations(Permission.blocked);
    }

217
    public static void updateComponentSecret(String subdomain, String secret) throws ModificationNotAllowedException {
218 219 220 221
        // Alert listeners about this event
        for (ExternalComponentManagerListener listener : listeners) {
            listener.componentSecretUpdated(subdomain, secret);
        }
222
        ExternalComponentConfiguration configuration = getConfiguration(subdomain, false);
223 224 225 226
        if (configuration != null) {
            configuration.setPermission(Permission.allowed);
            configuration.setSecret(secret);
            // Remove any previous configuration for this external component
227
            deleteConfigurationFromDB(configuration);
228 229
        }
        else {
230
            configuration = new ExternalComponentConfiguration(subdomain, false, Permission.allowed, secret);
231 232 233 234
        }
        addConfiguration(configuration);
    }

235 236 237 238 239
    /**
     * Removes any existing defined permission and configuration for the specified
     * external component.
     *
     * @param subdomain the subdomain of the external component.
240
     * @throws ModificationNotAllowedException if the operation was denied.
241
     */
242
    public static void deleteConfiguration(String subdomain) throws ModificationNotAllowedException {
243 244 245 246
        // Alert listeners about this event
        for (ExternalComponentManagerListener listener : listeners) {
            listener.componentConfigurationDeleted(subdomain);
        }
247

248
        // Proceed to delete the configuration of the component
249
        deleteConfigurationFromDB(getConfiguration(subdomain, false));
250 251 252 253 254 255
    }

    /**
     * Removes any existing defined permission and configuration for the specified
     * external component from the database.
     *
256
     * @param configuration the external component configuration to delete.
257
     */
258 259 260 261 262
    private static void deleteConfigurationFromDB(ExternalComponentConfiguration configuration) {
        if (configuration == null) {
            // Do nothing
            return;
        }
263
        // Remove the permission for the entity from the database
264
        Connection con = null;
265 266 267 268
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(DELETE_CONFIGURATION);
269
            pstmt.setString(1, configuration.getSubdomain() + (configuration.isWildcard() ? "%" : ""));
270
            pstmt.setInt(2, configuration.isWildcard() ? 1 : 0);
271 272 273
            pstmt.executeUpdate();
        }
        catch (SQLException sqle) {
274
            Log.error(sqle.getMessage(), sqle);
275 276
        }
        finally {
277
            DbConnectionManager.closeConnection(pstmt, con);
278 279 280 281 282
        }
    }

    /**
     * Adds a new permission for the specified external component.
283 284
     *
     * @param configuration the new configuration for a component.
285 286 287
     */
    private static void addConfiguration(ExternalComponentConfiguration configuration) {
        // Remove the permission for the entity from the database
288
        Connection con = null;
289 290 291 292
        PreparedStatement pstmt = null;
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(ADD_CONFIGURATION);
293 294 295 296
            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());
297 298 299
            pstmt.executeUpdate();
        }
        catch (SQLException sqle) {
300
            Log.error(sqle.getMessage(), sqle);
301 302
        }
        finally {
303
            DbConnectionManager.closeConnection(pstmt, con);
304 305 306 307
        }
    }

    /**
308 309 310
     * 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.
311 312
     *
     * @param subdomain the subdomain of the external component.
313
     * @param useWildcard true if an attempt to find a subdomain with wildcards should be attempted.
314 315
     * @return the configuration for an external component.
     */
316
    private static ExternalComponentConfiguration getConfiguration(String subdomain, boolean useWildcard) {
317
        ExternalComponentConfiguration configuration = null;
318
        Connection con = null;
319
        PreparedStatement pstmt = null;
320
        ResultSet rs = null;
321
        try {
322
            // Check if there is a configuration for the subdomain
323 324 325
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(LOAD_CONFIGURATION);
            pstmt.setString(1, subdomain);
326
            rs = pstmt.executeQuery();
327 328

            while (rs.next()) {
329
                configuration = new ExternalComponentConfiguration(subdomain, false, Permission.valueOf(rs.getString(2)),
330
                        rs.getString(1));
331 332 333
            }
        }
        catch (SQLException sqle) {
334
            Log.error(sqle.getMessage(), sqle);
335 336
        }
        finally {
337
            DbConnectionManager.closeConnection(rs, pstmt, con);
338
        }
339 340 341 342 343 344 345 346

        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);
347
                rs = pstmt.executeQuery();
348 349 350 351 352 353 354

                while (rs.next()) {
                    configuration = new ExternalComponentConfiguration(subdomain, true, Permission.valueOf(rs.getString(2)),
                            rs.getString(1));
                }
            }
            catch (SQLException sqle) {
355
                Log.error(sqle.getMessage(), sqle);
356 357
            }
            finally {
358 359
                DbConnectionManager.closeConnection(rs, pstmt, con);
           }
360
        }
361 362 363 364 365 366
        return configuration;
    }

    private static Collection<ExternalComponentConfiguration> getConfigurations(
            Permission permission) {
        Collection<ExternalComponentConfiguration> answer =
367
                new ArrayList<>();
368
        Connection con = null;
369
        PreparedStatement pstmt = null;
370
        ResultSet rs = null;
371 372 373 374
        try {
            con = DbConnectionManager.getConnection();
            pstmt = con.prepareStatement(LOAD_CONFIGURATIONS);
            pstmt.setString(1, permission.toString());
375
            rs = pstmt.executeQuery();
376 377
            ExternalComponentConfiguration configuration;
            while (rs.next()) {
378 379 380 381 382 383
                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));
384 385 386 387
                answer.add(configuration);
            }
        }
        catch (SQLException sqle) {
388
            Log.error(sqle.getMessage(), sqle);
389 390
        }
        finally {
391
            DbConnectionManager.closeConnection(rs, pstmt, con);
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412
        }
        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.
413
     * @throws ModificationNotAllowedException if the operation was denied.
414
     */
415
    public static void setDefaultSecret(String defaultSecret) throws ModificationNotAllowedException {
416 417 418 419
        // Alert listeners about this event
        for (ExternalComponentManagerListener listener : listeners) {
            listener.defaultSecretChanged(defaultSecret);
        }
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
        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;

435
        ExternalComponentConfiguration config = getConfiguration(subdomain, true);
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
        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) {
464
            Log.error(e.getMessage(), e);
465 466 467 468 469 470 471 472 473 474 475 476
            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.
477
     * @throws ModificationNotAllowedException if the operation was denied.
478
     */
479
    public static void setPermissionPolicy(PermissionPolicy policy) throws ModificationNotAllowedException {
480 481 482 483
        // Alert listeners about this event
        for (ExternalComponentManagerListener listener : listeners) {
            listener.permissionPolicyChanged(policy);
        }
484
        JiveGlobals.setProperty("xmpp.component.permission", policy.toString());
485
        // Check if connected components can remain connected to the server
486
        for (ComponentSession session : SessionManager.getInstance().getComponentSessions()) {
487 488
            for (String domain : session.getExternalComponent().getSubdomains()) {
                if (!canAccess(domain)) {
489
                    session.close();
490 491
                    break;
                }
492 493 494 495 496 497 498 499 500 501 502 503
            }
        }
    }

    /**
     * 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.
504
     * @throws ModificationNotAllowedException if the operation was denied.
505
     */
506
    public static void setPermissionPolicy(String policy) throws ModificationNotAllowedException {
507 508 509
        setPermissionPolicy(PermissionPolicy.valueOf(policy));
    }

510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
    /**
     * 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);
    }

532 533 534 535 536 537 538 539 540 541 542
    public enum PermissionPolicy {
        /**
         * Any XMPP entity is allowed to connect to the server except for those listed in
         * the <b>not allowed list</b>.
         */
        blacklist,

        /**
         * Only the XMPP entities listed in the <b>allowed list</b> are able to connect to
         * the server.
         */
543
        whitelist
544 545
    }
}