Commit 321c282d authored by Guus der Kinderen's avatar Guus der Kinderen

OF-1469 New plugin: XEP-0215 External Service Discovery

parent 3e926a78
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>External Service Discovery Plugin Changelog</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
}
H2 {
font-size : 10pt;
font-weight : bold;
padding-left : 1em;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
</style>
</head>
<body>
<h1>
External Service Discovery Plugin Changelog
</h1>
<p><b>1.0.0</b> -- January 26, 2018</p>
<ul>
<li>Initial release.</li>
</ul>
</body>
</html>
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<class>org.igniterealtime.openfire.plugins.externalservicediscovery.ExternalServiceDiscoveryPlugin</class>
<name>External Service Discovery</name>
<description>Allows XMPP entities to discover services external to the XMPP network, such as STUN and TURN servers.</description>
<author>Guus der Kinderen</author>
<version>1.0.0</version>
<date>01/26/2018</date>
<databaseKey>externalservicediscovery</databaseKey>
<databaseVersion>1</databaseVersion>
<adminconsole>
<tab id="sidebar-media-services">
<item id="external-service-discovery-config" name="${sidebar.external-service-discovery}"
url="external-service-discovery-config.jsp"
description="${sidebar.external-service-discovery.descr}"/>
</tab>
</adminconsole>
</plugin>
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>plugins</artifactId>
<groupId>org.igniterealtime.openfire</groupId>
<version>4.3.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.igniterealtime.openfire.plugins</groupId>
<artifactId>externalservicediscovery</artifactId>
<version>1.0.0</version>
<name>External Service Discovery Plugin</name>
<description>Openfire plugin that implements XEP-0215: External Service Discovery, allowing XMPP entities to discover services external to the XMPP network.</description>
<build>
<sourceDirectory>src/java</sourceDirectory>
<testSourceDirectory>src/test</testSourceDirectory>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
<!-- Compiles the Openfire Admin Console JSP pages. -->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jspc-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Versions of Openfire up to 4.2.1 included an older slf4j-api artifact (1.6.6). Fixating the dependency. -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>2.4</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>External Service Discovery Plugin Readme</title>
<style type="text/css">
BODY {
font-size : 100%;
}
BODY, TD, TH {
font-family : tahoma, verdana, arial, helvetica, sans-serif;
font-size : 0.8em;
}
H3 {
font-size : 10pt;
font-style: italic;
color: #004444;
}
H2 {
font-size : 10pt;
font-weight : bold;
}
A:hover {
text-decoration : none;
}
H1 {
font-family : tahoma, arial, helvetica, sans-serif;
font-size : 1.4em;
font-weight: bold;
border-bottom : 1px #ccc solid;
padding-bottom : 2px;
}
TT {
font-family : courier new;
font-weight : bold;
color : #060;
}
PRE {
font-family : courier new;
font-size : 100%;
}
#datatable TH {
color : #fff;
background-color : #2A448C;
text-align : left;
}
#datatable TD {
background-color : #FAF6EF;
}
#datatable .name {
background-color : #DCE2F5;
}
</style>
</head>
<body>
<h1>
External Service Discovery Plugin Readme
</h1>
<h2>Overview</h2>
<p>
The External Service Discovery plugin allows XMPP entities to discover services that are external to the XMPP
network. It is typically used to allow clients to interact with STUN and TURN services, possibly making use of
temporary credentials for those servers. This plugin provides an implementation of
<a href="https://xmpp.org/extensions/xep-0215.html">XEP-0215: External Service Discovery</a>.
</p>
<h2>Installation</h2>
<p>
Copy externalservicediscovery.jar into the plugins directory of your Openfire installation. The plugin will then be
automatically deployed. To upgrade to a new version, copy the new externalservicediscovery.jar file over the
existing file.
</p>
<h2>Configuration</h2>
<p>
The plugin is configured via the Openfire Admin Console. After installation, a new Admin Console page is available.
The page can be found on a main menu in the "Server" tab, "Media Services" sub-tab. The name of the page is
"Ext. Service Discovery".
</p>
<h2>Attribution</h2>
<p>
Icons made by <a href="http://www.freepik.com" title="Freepik">Freepik</a> from
<a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by
<a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a>
</p>
</body>
</html>
CREATE TABLE ofExternalServices (
serviceID INT NOT NULL,
name VARCHAR(255),
host VARCHAR(255) NOT NULL,
port INT,
restricted BOOLEAN,
transport CHAR(3),
type VARCHAR(10) NOT NULL,
username VARCHAR(255),
password VARCHAR(1024),
sharedSecret VARCHAR(1024)
);
INSERT INTO ofID (idType, id) VALUES (937, 1);
INSERT INTO ofVersion (name, version) VALUES ('externalservicediscovery', 1);
CREATE TABLE ofExternalServices (
serviceID BIGINT NOT NULL,
name VARCHAR(255),
host VARCHAR(255) NOT NULL,
port INT,
restricted BOOLEAN,
transport CHAR(3),
type VARCHAR(10) NOT NULL,
username VARCHAR(255),
password VARCHAR(1024),
sharedSecret VARCHAR(1024)
);
INSERT INTO ofID (idType, id) VALUES (937, 1);
INSERT INTO ofVersion (name, version) VALUES ('externalservicediscovery', 1);
CREATE TABLE ofExternalServices (
serviceID BIGINT NOT NULL,
name VARCHAR(255),
host VARCHAR(255) NOT NULL,
port INT,
restricted BOOLEAN,
transport CHAR(3),
type VARCHAR(10) NOT NULL,
username VARCHAR(255),
password VARCHAR(1024),
sharedSecret VARCHAR(1024)
);
INSERT INTO ofID (idType, id) VALUES (937, 1);
INSERT INTO ofVersion (name, version) VALUES ('externalservicediscovery', 1);
CREATE TABLE ofExternalServices (
serviceID INTEGER NOT NULL,
name VARCHAR2(255),
host VARCHAR2(255) NOT NULL,
port INT,
restricted SMALLINT,
transport CHAR(3),
type VARCHAR2(10) NOT NULL,
username VARCHAR2(255),
password VARCHAR2(1024),
sharedSecret VARCHAR2(1024)
);
INSERT INTO ofID (idType, id) VALUES (937, 1);
INSERT INTO ofVersion (name, version) VALUES ('externalservicediscovery', 1);
CREATE TABLE ofExternalServices (
serviceID BIGINT NOT NULL,
name VARCHAR(255),
host VARCHAR(255) NOT NULL,
port INT,
restricted BOOLEAN,
transport CHAR(3),
type VARCHAR(10) NOT NULL,
username VARCHAR(255),
password VARCHAR(1024),
sharedSecret VARCHAR(1024)
);
INSERT INTO ofID (idType, id) VALUES (937, 1);
INSERT INTO ofVersion (name, version) VALUES ('externalservicediscovery', 1);
CREATE TABLE ofExternalServices (
serviceID INTEGER NOT NULL,
name NVARCHAR(255),
host NVARCHAR(255) NOT NULL,
port INT,
restricted BIT,
transport NCHAR(3),
type NVARCHAR(10) NOT NULL,
username NVARCHAR(255),
password NVARCHAR(1024),
sharedSecret NVARCHAR(1024)
);
INSERT INTO ofID (idType, id) VALUES (937, 1);
INSERT INTO ofVersion (name, version) VALUES ('externalservicediscovery', 1);
CREATE TABLE ofExternalServices (
serviceID INTEGER NOT NULL,
name NVARCHAR(255),
host NVARCHAR(255) NOT NULL,
port INT,
restricted BIT,
transport NCHAR(3),
type NVARCHAR(10) NOT NULL,
username NVARCHAR(255),
password NVARCHAR(1024),
sharedSecret NVARCHAR(1024)
);
INSERT INTO ofID (idType, id) VALUES (937, 1);
INSERT INTO ofVersion (name, version) VALUES ('externalservicediscovery', 1);
global.csrf.failed=CSRF Error: No changes made, you'll need to retry.
global.click_delete=Delete
admin.error=Internal server error
sidebar.external-service-discovery=Ext. Service Discovery
sidebar.external-service-discovery.descr=Settings that allow users to detect and use STUN and TURN services that are offered by external servers.
external-service-discovery-config.success-delete=Service removed.
external-service-discovery-config.success-add=Service added.
external-service-discovery-config.title=External Service Discovery Configuration
external-service-discovery-config.descr=On this page, you can configure external services (like STUN and TURN servers) that can be used by your users.
external-service-discovery-config.host=Host
external-service-discovery-config.host-required=A 'host' value is required.
external-service-discovery-config.port=Port
external-service-discovery-config.port-number=The 'port' value must be numeric.
external-service-discovery-config.name=Description
external-service-discovery-config.transport=Transport
external-service-discovery-config.tcp=TCP
external-service-discovery-config.udp=UDP
external-service-discovery-config.type=Type
external-service-discovery-config.type-required=A 'type' value is required.
external-service-discovery-config.stun=STUN
external-service-discovery-config.turn=TURN
external-service-discovery-config.turns=TURNS
external-service-discovery-config.credentials=Credentials
external-service-discovery-config.table.empty=There currently are no external services configured.
external-service-discovery-config.add-service.title=Add External Service
external-service-discovery-config.add-service.descr=You can add an external service by filling out the form below.
external-service-discovery-config.add-service.button-caption=Add Service
external-service-discovery-config.credentials.nocredentials=No credentials needed
external-service-discovery-config.credentials.using-nocredentials=Not using any credentials (implies unauthenticated access)
external-service-discovery-config.credentials.userpass=Hardcoded
external-service-discovery-config.credentials.using-userpass=Using hardcoded credentials with username:
external-service-discovery-config.credentials.username=Username
external-service-discovery-config.credentials.username-required=When using hardcoded credentials, a 'username' value is required.
external-service-discovery-config.credentials.password=Password
external-service-discovery-config.credentials.password-required=When using hardcoded credentials, a 'password' value is required.
external-service-discovery-config.credentials.sharedsecret=Shared Secret (for generating ephemeral passwords)
external-service-discovery-config.credentials.sharedsecret-required=When generating ephemeral passwords, a 'shared secret' value is required.
external-service-discovery-config.credentials.using-sharedsecret=Using shared secret to generate ephemeral passwords.
/*
* Copyright (C) 2017 Ignite Realtime Foundation. 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.igniterealtime.openfire.plugins.externalservicediscovery;
import java.util.Date;
/**
* Representation of service credentials.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class Credentials
{
/**
* A timestamp indicating when the provided username and password credentials will expire. The format MUST adhere
* to the dateTime format specified in XMPP Date and Time Profiles (XEP-0082) [12] and MUST be expressed in UTC.
*
* Optional.
*/
private final Date expires;
/**
* A service- or server-generated password for use at the service.
*
* Optional.
*/
private final String password;
/**
* A service- or server-generated username for use at the service.
*
* Optional.
*/
private final String username;
public Credentials( String username, String password, Date expires )
{
this.username = username;
this.password = password;
this.expires = expires;
}
public Date getExpires()
{
return expires;
}
public String getPassword()
{
return password;
}
public String getUsername()
{
return username;
}
@Override
public boolean equals( Object o )
{
if ( this == o )
{
return true;
}
if ( o == null || getClass() != o.getClass() )
{
return false;
}
final Credentials that = (Credentials) o;
if ( expires != null ? !expires.equals( that.expires ) : that.expires != null )
{
return false;
}
if ( password != null ? !password.equals( that.password ) : that.password != null )
{
return false;
}
return username != null ? username.equals( that.username ) : that.username == null;
}
@Override
public int hashCode()
{
int result = expires != null ? expires.hashCode() : 0;
result = 31 * result + ( password != null ? password.hashCode() : 0 );
result = 31 * result + ( username != null ? username.hashCode() : 0 );
return result;
}
}
/*
* Copyright (C) 2017 Ignite Realtime Foundation. 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.igniterealtime.openfire.plugins.externalservicediscovery;
import org.dom4j.Element;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
import org.jivesoftware.openfire.handler.IQHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.PacketError;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
/**
* An IQ handler that implements XEP-0215: External Service Discovery.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
* @see <a href="https://xmpp.org/extensions/xep-0215.html">XEP-0215: External Service Discovery</a>
*/
public abstract class ExternalServiceDiscoveryIQHandler extends IQHandler implements ServerFeaturesProvider
{
private static final Logger Log = LoggerFactory.getLogger( ExternalServiceDiscoveryIQHandler.class );
public ExternalServiceDiscoveryIQHandler()
{
super( "XEP-0215: External Service Discovery" );
}
@Override
public Iterator<String> getFeatures()
{
return Collections.singleton( getInfo().getNamespace() ).iterator();
}
@Override
public IQ handleIQ( IQ packet ) throws UnauthorizedException
{
if ( packet.isResponse() )
{
Log.debug( "Silently ignoring IQ response: {}", packet );
return null;
}
if ( IQ.Type.set == packet.getType() )
{
Log.info( "Responding with an error to an IQ request of type 'set': {}", packet );
final IQ response = IQ.createResultIQ( packet );
response.setError( PacketError.Condition.service_unavailable );
return response;
}
final IQ response;
switch ( packet.getChildElement().getName() )
{
case "services":
response = handleServices( packet );
break;
case "credentials":
response = handleCredentials( packet );
break;
default:
Log.info( "Responding with an error to an IQ request for which the element name escaped by namespace is not understood: {}", packet );
response = IQ.createResultIQ( packet );
response.setError( PacketError.Condition.service_unavailable );
}
Log.info( "Responding with {} to request {}", response.toXML(), packet.toXML() );
return response;
}
/**
* Handles requests for services.
*
* @param request the request (cannot be null).
* @return An answer (never null).
*/
protected IQ handleServices( IQ request )
{
final Map<Service, Credentials> services;
final ServiceManager serviceManager = ServiceManager.getInstance();
final String requestedType = request.getChildElement().attributeValue( "type" );
if ( requestedType == null || requestedType.isEmpty() )
{
Log.debug( "Handling request for all services." );
services = serviceManager.getServicesFor( request.getFrom() );
}
else
{
Log.debug( "Handling request for services of a specific type: {}.", requestedType );
services = serviceManager.getServicesFor( request.getFrom(), requestedType );
}
// Formulate response.
final IQ response = IQ.createResultIQ( request );
final Element childElement = response.setChildElement( request.getChildElement().getName(), request.getChildElement().getNamespaceURI() );
if ( requestedType != null && !requestedType.isEmpty() )
{
childElement.addAttribute( "type", requestedType );
}
for ( final Map.Entry<Service, Credentials> entry : services.entrySet() )
{
addServiceXml( childElement, entry.getKey(), null, entry.getValue() );
}
return response;
}
/**
* Handles requests for credentials
*
* @param request the request (cannot be null).
* @return An answer (never null).
*/
protected IQ handleCredentials( IQ request )
{
final Element requestedService = request.getChildElement().element( "service" );
if ( requestedService == null )
{
Log.debug( "Responding with an error to a request for credentials that did not specify any service: {}", request );
final IQ response = IQ.createResultIQ( request );
response.setError( PacketError.Condition.bad_request );
return response;
}
final String requestedHost = requestedService.attributeValue( "host" );
final String requestedType = requestedService.attributeValue( "type" );
final Integer requestedPort;
if ( requestedService.attribute( "port" ) != null )
{
try
{
requestedPort = Integer.parseInt( requestedService.attributeValue( "port" ) );
}
catch ( NumberFormatException ex )
{
Log.debug( "Responding with an error to a request for credentials that specified a malformed port number for a service: {}", request, ex );
final IQ response = IQ.createResultIQ( request );
response.setError( PacketError.Condition.bad_request );
return response;
}
}
else
{
requestedPort = null;
}
if ( requestedHost == null || requestedHost.isEmpty() || requestedType == null || requestedType.isEmpty() )
{
Log.debug( "Responding with an error to a request for credentials that did not specify any service: {}", request );
final IQ response = IQ.createResultIQ( request );
response.setError( PacketError.Condition.bad_request );
return response;
}
final ServiceManager serviceManager = ServiceManager.getInstance();
final Map<Service, Credentials> services;
if ( requestedPort == null )
{
Log.debug( "Handling request for credentials by {} for the {} service: {}", request.getFrom(), requestedType, requestedHost );
services = serviceManager.getServicesFor( request.getFrom(), requestedHost, requestedType );
}
else
{
Log.debug( "Handling request for credentials by {} for the {} service: {}:{}", request.getFrom(), requestedType, requestedHost, requestedPort);
services = serviceManager.getServicesFor( request.getFrom(), requestedHost, requestedType, requestedPort );
}
// Formulate response.
final IQ response = IQ.createResultIQ( request );
final Element childElement = response.setChildElement( "credentials", request.getChildElement().getNamespaceURI() );
for ( final Map.Entry<Service, Credentials> service : services.entrySet() )
{
addServiceXml( childElement, service.getKey(), null, service.getValue() );
}
return response;
}
abstract void addServiceXml( Element parent, Service service, final Service.Action action, final Credentials credentials );
}
/*
* Copyright (C) 2017 Ignite Realtime Foundation. 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.igniterealtime.openfire.plugins.externalservicediscovery;
import org.dom4j.Element;
import org.jivesoftware.openfire.IQHandlerInfo;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.util.XMPPDateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.PacketError;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
/**
* An IQ handler that implements XEP-0215: External Service Discovery, Version 0.6 (2014-02-27)
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
* @see <a href="https://xmpp.org/extensions/xep-0215.html">XEP-0215: External Service Discovery</a>
*/
public class ExternalServiceDiscoveryIQHandlerV1 extends ExternalServiceDiscoveryIQHandler
{
private static final Logger Log = LoggerFactory.getLogger( ExternalServiceDiscoveryIQHandlerV1.class );
private final static IQHandlerInfo INFO = new IQHandlerInfo( "services", "urn:xmpp:extdisco:1" );
@Override
public IQHandlerInfo getInfo()
{
return INFO;
}
@Override
public void addServiceXml( Element parent, Service service, final Service.Action action, final Credentials credentials )
{
final Element result = parent.addElement( "service" );
if ( credentials != null )
{
if ( credentials.getUsername() != null )
{
result.addAttribute( "username", credentials.getUsername() );
}
if ( credentials.getPassword() != null )
{
result.addAttribute( "password", credentials.getPassword() );
}
}
if ( service.getHost() != null )
{
result.addAttribute( "host", service.getHost() );
}
if ( service.getName() != null )
{
result.addAttribute( "name", service.getName() );
}
if ( service.getPort() != null )
{
result.addAttribute( "port", Integer.toString( service.getPort() ) );
}
if ( service.getTransport() != null )
{
result.addAttribute( "transport", service.getTransport() );
}
if ( service.getType() != null )
{
result.addAttribute( "type", service.getType() );
}
}
}
/*
* Copyright (C) 2017 Ignite Realtime Foundation. 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.igniterealtime.openfire.plugins.externalservicediscovery;
import org.dom4j.Element;
import org.jivesoftware.openfire.IQHandlerInfo;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.disco.ServerFeaturesProvider;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.util.XMPPDateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.PacketError;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
/**
* An IQ handler that implements XEP-0215: External Service Discovery, Version 0.7 (2015-10-20).
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
* @see <a href="https://xmpp.org/extensions/xep-0215.html">XEP-0215: External Service Discovery</a>
*/
public class ExternalServiceDiscoveryIQHandlerV2 extends ExternalServiceDiscoveryIQHandler
{
private static final Logger Log = LoggerFactory.getLogger( ExternalServiceDiscoveryIQHandlerV2.class );
private final static IQHandlerInfo INFO = new IQHandlerInfo( "services", "urn:xmpp:extdisco:2" );
@Override
public IQHandlerInfo getInfo()
{
return INFO;
}
@Override
public void addServiceXml( Element parent, Service service, final Service.Action action, final Credentials credentials )
{
final Element result = parent.addElement( "service" );
if ( action != null )
{
result.addAttribute( "action", action.toString() );
}
if ( credentials != null )
{
if ( credentials.getExpires() != null )
{
result.addAttribute( "expires", XMPPDateTimeFormat.format( credentials.getExpires() ) );
}
if ( credentials.getUsername() != null )
{
result.addAttribute( "username", credentials.getUsername() );
}
if ( credentials.getPassword() != null )
{
result.addAttribute( "password", credentials.getPassword() );
}
}
if ( service.getHost() != null )
{
result.addAttribute( "host", service.getHost() );
}
if ( service.getName() != null )
{
result.addAttribute( "name", service.getName() );
}
if ( service.getPort() != null )
{
result.addAttribute( "port", Integer.toString( service.getPort() ) );
}
if ( service.getRestricted() != null )
{
result.addAttribute( "restricted", Boolean.toString( service.getRestricted() ) );
}
if ( service.getTransport() != null )
{
result.addAttribute( "transport", service.getTransport() );
}
if ( service.getType() != null )
{
result.addAttribute( "type", service.getType() );
}
}
}
/*
* Copyright (C) 2017 Ignite Realtime Foundation. 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.igniterealtime.openfire.plugins.externalservicediscovery;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Iterator;
/**
* An Openfire plugin that implements XEP-0215: External Service Discovery.
*
* This plugin allowS XMPP entities to discover services external to the XMPP network, such as STUN and TURN servers.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
* @see <a href="https://xmpp.org/extensions/xep-0215.html">XEP-0215: External Service Discovery</a>
*/
public class ExternalServiceDiscoveryPlugin implements Plugin
{
private static final Logger Log = LoggerFactory.getLogger( ExternalServiceDiscoveryPlugin.class );
private ExternalServiceDiscoveryIQHandlerV1 iqHandlerV1;
private ExternalServiceDiscoveryIQHandlerV2 iqHandlerV2;
@Override
public void initializePlugin( PluginManager manager, File pluginDirectory )
{
Log.debug( "Registering IQ Handlers..." );
iqHandlerV1 = new ExternalServiceDiscoveryIQHandlerV1();
iqHandlerV2 = new ExternalServiceDiscoveryIQHandlerV2();
XMPPServer.getInstance().getIQRouter().addHandler( iqHandlerV1 );
XMPPServer.getInstance().getIQRouter().addHandler( iqHandlerV2 );
Log.debug( "Registering Server Features..." );
for ( final Iterator<String> it = iqHandlerV1.getFeatures(); it.hasNext(); )
{
XMPPServer.getInstance().getIQDiscoInfoHandler().addServerFeature( it.next() );
}
for ( final Iterator<String> it = iqHandlerV2.getFeatures(); it.hasNext(); )
{
XMPPServer.getInstance().getIQDiscoInfoHandler().addServerFeature( it.next() );
}
Log.debug( "Initialized." );
}
@Override
public void destroyPlugin()
{
Log.debug( "Removing Server Features..." );
for ( final Iterator<String> it = iqHandlerV2.getFeatures(); it.hasNext(); )
{
XMPPServer.getInstance().getIQDiscoInfoHandler().removeServerFeature( it.next() );
}
for ( final Iterator<String> it = iqHandlerV1.getFeatures(); it.hasNext(); )
{
XMPPServer.getInstance().getIQDiscoInfoHandler().removeServerFeature( it.next() );
}
if ( iqHandlerV2 != null )
{
Log.debug( "Removing IQ Handler..." );
XMPPServer.getInstance().getIQRouter().removeHandler( iqHandlerV2 );
}
if ( iqHandlerV1 != null )
{
Log.debug( "Removing IQ Handler..." );
XMPPServer.getInstance().getIQRouter().removeHandler( iqHandlerV1 );
}
Log.debug( "Destroyed." );
}
}
package org.igniterealtime.openfire.plugins.externalservicediscovery;
import nl.jqno.equalsverifier.EqualsVerifier;
import org.junit.Test;
/**
* Simple tests that verify the implementation of {@link Service}.
*
* @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/
public class ServiceTest
{
/**
* Happy flow - should not cause any issues.
*/
@Test
public void testPackageConstructor()
{
int databaseId = -1;
String name = "description";
String host = "host";
Integer port = 123;
Boolean restricted = false;
String transport = "udp";
String type = "turn";
String username = "username";
String password = "password";
String sharedSecret = "secret";
new Service( databaseId, name, host, port, restricted, transport, type, username, password, sharedSecret );
}
/**
* Verifies that an IllegalArgumentException is thrown by the constructor when the provided 'host' argument value is null.
*/
@Test( expected = IllegalArgumentException.class )
public void testNullHost()
{
int databaseId = -1;
String name = "description";
String host = null;
Integer port = 123;
Boolean restricted = false;
String transport = "udp";
String type = "turn";
String username = "username";
String password = "password";
String sharedSecret = "secret";
new Service( databaseId, name, host, port, restricted, transport, type, username, password, sharedSecret );
}
/**
* Verifies that an IllegalArgumentException is thrown by the constructor when the provided 'type' argument value is null.
*/
@Test( expected = IllegalArgumentException.class )
public void testNullType()
{
int databaseId = -1;
String name = "description";
String host = "host";
Integer port = 123;
Boolean restricted = false;
String transport = "udp";
String type = null;
String username = "username";
String password = "password";
String sharedSecret = "secret";
new Service( databaseId, name, host, port, restricted, transport, type, username, password, sharedSecret );
}
/**
* Verifies that the implementation adheres to the contract specified in {@link Object#equals(Object)}.
*/
@Test
public void equalsContract()
{
EqualsVerifier.forClass( Service.class )
.withNonnullFields( "host", "type" ) // checked by constructor.
.verify();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
</web-app>
......@@ -105,6 +105,7 @@
<module>dbaccess</module>
<module>emailListener</module>
<module>emailOnAway</module>
<module>externalservicediscovery</module>
<module>fastpath</module>
<module>gojara</module>
<module>hazelcast</module>
......
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