Skip to content

Commit

Permalink
Initial prototype
Browse files Browse the repository at this point in the history
This is the state of the prototype that was created at the The Hague sprint, at IPerity.

The code is functional, to the extend that it has been observed to trigger notifications in Conversations. There's not much rhyme or reason to the frequency in which notifications are sent (currently: on any message that is sent to a user of Openfire, that has a non-empty body).
  • Loading branch information
guusdk committed Jun 10, 2019
0 parents commit 5b5e85e
Show file tree
Hide file tree
Showing 12 changed files with 903 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs.
#
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4
30 changes: 30 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
target/
work/
*.class

# Ignore Intellij Idea project files
*.iml
*.ipr
.idea
atlassian-ide-plugin.xml
out/

# Ignore Eclipse project files
.settings
.project
.classpath

# Ignore MacOSX files
.DS_Store

# Ignore Netbeans project files
nbproject/
nbbuild/
/bin/

# Microsoft Visual Studio Code
.vscode/

# build temp folders
rpmbuild/
debian/
47 changes: 47 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?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">
<modelVersion>4.0.0</modelVersion>

<parent>
<artifactId>plugins</artifactId>
<groupId>org.igniterealtime.openfire</groupId>
<version>4.3.0</version>
</parent>

<groupId>org.igniterealtime.openfire.plugins</groupId>
<artifactId>pushnotification</artifactId>
<version>0.5.0-SNAPSHOT</version>

<name>Push Notification</name>
<description>Adds Push Notification (XEP-0357) support to Openfire.</description>

<build>
<plugins>
<plugin>
<!-- this generates the JAR files in the Openfire-plugin specific format -->
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
</plugins>
</build>

<repositories>
<!-- Here, we get our dependencies, like the parent project. -->
<repository>
<id>igniterealtime</id>
<name>Ignite Realtime Repository</name>
<url>https://igniterealtime.org/archiva/repository/maven/</url>
</repository>
</repositories>

<pluginRepositories>
<!-- Used for the dependencies used by the plugins that we'll use. -->
<pluginRepository>
<id>igniterealtime</id>
<name>Ignite Realtime Repository</name>
<url>https://igniterealtime.org/archiva/repository/maven/</url>
</pluginRepository>
</pluginRepositories>

</project>
53 changes: 53 additions & 0 deletions src/changelog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">

<html>
<head>
<title>Push Notification 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>
Push Notification Plugin Changelog
</h1>

<p><b>0.5.0</b> -- (to be determined)</p>
<ul>
<li>Initial release.</li>
</ul>

</body>
</html>
Binary file added src/logo_large.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/logo_small.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* Copyright (C) 2019 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.pushnotification;

import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.openfire.IQHandlerInfo;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.handler.IQHandler;
import org.jivesoftware.openfire.user.User;
import org.jivesoftware.openfire.user.UserNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;

/**
* An IQ handler implementation for the protocol defined by namespace "urn:xmpp:push:0"
* in XEP-0357.
*
* @author Guus der Kinderen, [email protected]
* @see <a href="https://xmpp.org/extensions/xep-0357.html">XEP-0357: "Push Notifications"</a>
*/
public class Push0IQHandler extends IQHandler
{
private static final Logger Log = LoggerFactory.getLogger( Push0IQHandler.class );

public static final String ELEMENT_NAME = "enable";
public static final String ELEMENT_NAMESPACE = "urn:xmpp:push:0";

public Push0IQHandler()
{
super( "Push Notification IQ Handler (enable)." );
}

/**
* Handles the received IQ packet.
*
* @param packet the IQ packet to handle.
* @return the response to send back.
* @throws UnauthorizedException if the user that sent the packet is not
* authorized to request the given operation.
*/
@Override
public IQ handleIQ( final IQ packet ) throws UnauthorizedException
{
if ( packet.isResponse() ) {
Log.trace( "Silently ignoring an unexpected response stanza: {}", packet );
return null;
}

if ( IQ.Type.get.equals( packet.getType() ) )
{
Log.trace( "Ignoring an unexpected request stanza of type 'get': {}", packet );
final IQ result = IQ.createResultIQ( packet );
result.setError( PacketError.Condition.bad_request );
return result;
}

String action;
JID pushService;
String node;
try
{
action = packet.getChildElement().getName();
pushService = new JID( packet.getChildElement().attributeValue( "jid" ) );
node = packet.getChildElement().attributeValue( "node" );
}
catch ( Exception e )
{
Log.debug( "An exception occurred while trying to to parse push service and node from stanza: {}", packet, e );
action = null;
pushService = null;
node = null;
}

if ( action == null || pushService == null || (!action.equals( "disable" ) && node == null) )
{
Log.trace( "Ignoring a request stanza that could not be parsed: {}", packet );
final IQ result = IQ.createResultIQ( packet );
result.setError( PacketError.Condition.bad_request );
return result;
}

if ( !XMPPServer.getInstance().getUserManager().isRegisteredUser( packet.getFrom() ) )
{
Log.info( "Denying service for an entity that's not recognized as a registered user: {}", packet.getFrom() );
throw new UnauthorizedException( "This service is only available to registered, local users." );
}

final User user;
try
{
user = XMPPServer.getInstance().getUserManager().getUser( packet.getFrom().getNode() );
}
catch ( UserNotFoundException e )
{
Log.error( "Unable to load user, while user was confirmed to be a registered user: {}", packet.getFrom() );
final IQ result = IQ.createResultIQ( packet );
result.setError( PacketError.Condition.internal_server_error );
return result;
}

Log.trace( "intercepted {}", packet );

final IQ response;
switch( action )
{
case "enable":
final Element publishOptions = parsePublishOptions( packet );
PushServiceManager.register( user, pushService, node, publishOptions );

Log.debug( "Registered push service '{}', node '{}', for user '{}'.", new Object[]{ pushService.toString(), node, user.getUsername() } );
response = IQ.createResultIQ( packet );
break;

case "disable":
PushServiceManager.deregister( user, pushService, node );

Log.debug( "Deregistered push service '{}', node '{}', for user '{}'.", new Object[] { pushService.toString(), node, user.getUsername() } );
response = IQ.createResultIQ( packet );
break;

default:
Log.debug( "Unknown namespace element: {}", action );
response = IQ.createResultIQ( packet );
response.setError( PacketError.Condition.bad_request );
break;
}
return response;
}

private Element parsePublishOptions( final IQ packet )
{
final Element x = packet.getChildElement().element( QName.get( "x", "jabber:x:data" ) );
if ( x == null ) {
return null;
}

for (final Element element : x.elements( "field" ) )
{
if ( "FORM_TYPE".equals( element.attributeValue( "var" ) ) )
{
final Element value = element.element( "value" );
if ( value != null && "http://jabber.org/protocol/pubsub#publish-options".equals( value.getText() ) )
{
return x;
}
}
}
return null;
}

/**
* Returns the handler information to help generically handle IQ packets.
* IQHandlers that aren't local server iq handlers (e.g. chatbots, transports, etc)
* return <tt>null</tt>.
*
* @return The IQHandlerInfo for this handler
*/
@Override
public IQHandlerInfo getInfo()
{
return new IQHandlerInfo( ELEMENT_NAME, ELEMENT_NAMESPACE );
}
}
Loading

0 comments on commit 5b5e85e

Please sign in to comment.