Skip to content

Commit 979d537

Browse files
authored
Merge pull request #251 from pusher/decrypt-integration
Release 2.1.0
2 parents 8df24c1 + e311d55 commit 979d537

29 files changed

+3314
-38
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# pusher-websocket-java changelog
22

3-
This Changelog is no longer being updated. For any further changes please see the Releases section on this Github repository - https://github.com/pusher/pusher-websocket-java/releases
3+
## Version 2.1.0 - 8th April 2020
4+
5+
* Added support for [private encrypted channels](https://pusher.com/docs/channels/using_channels/encrypted-channels)
46

57
## Version 2.0.2
68

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
[![Build Status](https://travis-ci.org/pusher/pusher-websocket-java.svg?branch=master)](https://travis-ci.org/pusher/pusher-websocket-java)
44
[![codecov](https://codecov.io/gh/pusher/pusher-websocket-java/branch/master/graph/badge.svg)](https://codecov.io/gh/pusher/pusher-websocket-java)
5+
[![Maven Central](https://img.shields.io/maven-central/v/com.pusher/pusher-java-client.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22com.pusher%22%20AND%20a:%22pusher-java-client%22)
56

67
Pusher Channels client library for Java targeting **Android** and general Java.
78

@@ -30,6 +31,7 @@ This README covers the following topics:
3031
- [Subscribing to channels](#subscribing-to-channels)
3132
- [Public channels](#public-channels)
3233
- [Private channels](#private-channels)
34+
- [Private encrypted channels [BETA]](#private-encrypted-channels)
3335
- [Presence channels](#presence-channels)
3436
- [The User object](#the-user-object)
3537
- [Binding and handling events](#binding-and-handling-events)
@@ -59,7 +61,7 @@ The pusher-java-client is available in Maven Central.
5961
<dependency>
6062
<groupId>com.pusher</groupId>
6163
<artifactId>pusher-java-client</artifactId>
62-
<version>2.0.2</version>
64+
<version>2.1.0</version>
6365
</dependency>
6466
</dependencies>
6567
```
@@ -68,7 +70,7 @@ The pusher-java-client is available in Maven Central.
6870

6971
```groovy
7072
dependencies {
71-
compile 'com.pusher:pusher-java-client:2.0.2'
73+
compile 'com.pusher:pusher-java-client:2.1.0'
7274
}
7375
```
7476

@@ -271,6 +273,37 @@ PrivateChannel channel = pusher.subscribePrivate("private-channel",
271273
});
272274
```
273275

276+
### Private encrypted channels [BETA]
277+
278+
Similar to Private channels, you can also subscribe to a
279+
[private encrypted channel](https://pusher.com/docs/channels/using_channels/encrypted-channels).
280+
This library now fully supports end-to-end encryption. This means that only you and your connected clients will be able to read your messages. Pusher cannot decrypt them.
281+
282+
Like the private channel, you must provide your own authentication endpoint,
283+
with your own encryption master key. There is a
284+
[demonstration endpoint to look at using nodejs](https://github.com/pusher/pusher-channels-auth-example#using-e2e-encryption).
285+
286+
To get started you need to subscribe to your channel, provide a `PrivateEncryptedChannelEventListener`, and a list of the events you are
287+
interested in, for example:
288+
289+
```java
290+
PrivateEncryptedChannel privateEncryptedChannel =
291+
pusher.subscribePrivateEncrypted("private-encrypted-channel", listener, "my-event");
292+
```
293+
294+
In addition to the events that are possible on public channels the
295+
`PrivateEncryptedChannelEventListener` also has the following methods:
296+
* `onAuthenticationFailure(String message, Exception e)` - This is called if
297+
the `Authorizer` does not successfully authenticate the subscription:
298+
* `onDecryptionFailure(String event, String reason);` - This is called if the message cannot be
299+
decrypted. The decryption will attempt to refresh the shared secret key once
300+
from the `Authorizer`.
301+
302+
There is a
303+
[working example in the repo](https://github.com/pusher/pusher-websocket-java/blob/master/src/main/java/com/pusher/client/example/PrivateEncryptedChannelExampleApp.java)
304+
which you can use with the
305+
[demonstration authorization endpoint](https://github.com/pusher/pusher-channels-auth-example#using-e2e-encryption)
306+
274307
### Presence channels
275308

276309
[Presence channels](https://pusher.com/docs/channels/using_channels/presence-channels) are private channels which provide additional events exposing who is currently subscribed to the channel. Since they extend private channels they also need to be authenticated (see [authenticating channel subscriptions](https://pusher.com/docs/channels/server_api/authenticating-users)).

build.gradle

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ apply plugin: 'signing'
2222
apply plugin: 'jacoco'
2323

2424
group = "com.pusher"
25-
version = "2.0.2"
25+
version = "2.1.0"
2626
sourceCompatibility = "1.8"
2727
targetCompatibility = "1.8"
2828

@@ -45,9 +45,12 @@ repositories {
4545
dependencies {
4646
compile "com.google.code.gson:gson:2.2.2"
4747
compile "org.java-websocket:Java-WebSocket:1.4.0"
48+
4849
testCompile "org.mockito:mockito-all:1.8.5"
4950
testCompile "org.powermock:powermock-module-junit4:1.4.11"
5051
testCompile "org.powermock:powermock-api-mockito:1.4.11"
52+
53+
testImplementation "com.google.truth:truth:1.0.1"
5154
}
5255

5356

@@ -64,11 +67,13 @@ javadoc {
6467
options.overview = file("src/main/javadoc/overview.html")
6568
// uncomment this to use the custom javadoc styles
6669
//options.stylesheetFile = file("src/main/javadoc/css/styles.css")
67-
exclude "**/com/pusher/client/channel/impl/*"
68-
exclude "**/com/pusher/client/connection/impl/*"
69-
exclude "**/com/pusher/client/connection/websocket/*"
70-
exclude "**/org/java_websocket/*"
71-
exclude "**/com/pusher/client/example/*"
70+
exclude "com/pusher/client/channel/impl/*"
71+
exclude "com/pusher/client/connection/impl/*"
72+
exclude "com/pusher/client/connection/websocket/*"
73+
exclude "com/pusher/client/crypto/nacl/*"
74+
exclude "com/pusher/client/util/internal/*"
75+
exclude "org/java_websocket/*"
76+
exclude "com/pusher/client/example/*"
7277
options.linkSource = true
7378
}
7479

src/main/java/com/pusher/client/Pusher.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import com.pusher.client.channel.Channel;
44
import com.pusher.client.channel.ChannelEventListener;
5+
import com.pusher.client.channel.PrivateEncryptedChannel;
6+
import com.pusher.client.channel.PrivateEncryptedChannelEventListener;
57
import com.pusher.client.channel.PresenceChannel;
68
import com.pusher.client.channel.PresenceChannelEventListener;
79
import com.pusher.client.channel.PrivateChannel;
@@ -11,6 +13,7 @@
1113
import com.pusher.client.channel.impl.InternalChannel;
1214
import com.pusher.client.channel.impl.PresenceChannelImpl;
1315
import com.pusher.client.channel.impl.PrivateChannelImpl;
16+
import com.pusher.client.channel.impl.PrivateEncryptedChannelImpl;
1417
import com.pusher.client.connection.Connection;
1518
import com.pusher.client.connection.ConnectionEventListener;
1619
import com.pusher.client.connection.ConnectionState;
@@ -284,6 +287,38 @@ public PrivateChannel subscribePrivate(final String channelName, final PrivateCh
284287
return channel;
285288
}
286289

290+
291+
/**
292+
* Subscribes to a {@link com.pusher.client.channel.PrivateEncryptedChannel} which
293+
* requires authentication.
294+
*
295+
* @param channelName The name of the channel to subscribe to.
296+
* @param listener A listener to be informed of both Pusher channel protocol events and
297+
* subscription data events.
298+
* @param eventNames An optional list of names of events to be bound to on the channel.
299+
* The equivalent of calling
300+
* {@link com.pusher.client.channel.Channel#bind(String, SubscriptionEventListener)}
301+
* one or more times.
302+
* @return A new {@link com.pusher.client.channel.PrivateEncryptedChannel} representing
303+
* the subscription.
304+
* @throws IllegalStateException if a {@link com.pusher.client.Authorizer} has not been set for
305+
* the {@link Pusher} instance via {@link #Pusher(String, PusherOptions)}.
306+
*/
307+
public PrivateEncryptedChannel subscribePrivateEncrypted(
308+
final String channelName,
309+
final PrivateEncryptedChannelEventListener listener,
310+
final String... eventNames) {
311+
312+
throwExceptionIfNoAuthorizerHasBeenSet();
313+
314+
final PrivateEncryptedChannelImpl channel = factory.newPrivateEncryptedChannel(
315+
connection, channelName, pusherOptions.getAuthorizer());
316+
channelManager.subscribeTo(channel, listener, eventNames);
317+
318+
return channel;
319+
}
320+
321+
287322
/**
288323
* Subscribes to a {@link com.pusher.client.channel.PresenceChannel} which
289324
* requires authentication.
@@ -363,6 +398,16 @@ public PrivateChannel getPrivateChannel(String channelName){
363398
return channelManager.getPrivateChannel(channelName);
364399
}
365400

401+
/**
402+
*
403+
* @param channelName The name of the private encrypted channel to be retrieved
404+
* @return A private encrypted channel, or null if it could not be found
405+
* @throws IllegalArgumentException if you try to retrieve a public or presence channel.
406+
*/
407+
public PrivateEncryptedChannel getPrivateEncryptedChannel(String channelName){
408+
return channelManager.getPrivateEncryptedChannel(channelName);
409+
}
410+
366411
/**
367412
*
368413
* @param channelName The name of the presence channel to be retrieved

src/main/java/com/pusher/client/channel/PrivateChannelEventListener.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,8 @@ public interface PrivateChannelEventListener extends ChannelEventListener {
77
/**
88
* Called when an attempt to authenticate a private channel fails.
99
*
10-
* @param message
11-
* A description of the problem.
12-
* @param e
13-
* An associated exception, if available.
10+
* @param message A description of the problem.
11+
* @param e An associated exception, if available.
1412
*/
1513
void onAuthenticationFailure(String message, Exception e);
1614
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.pusher.client.channel;
2+
3+
/**
4+
* Represents a subscription to an encrypted private channel.
5+
*/
6+
public interface PrivateEncryptedChannel extends Channel {
7+
8+
// it's not currently possible to send a message using private encrypted channels
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.pusher.client.channel;
2+
3+
/**
4+
* Interface to listen to private encrypted channel events.
5+
* Note: This needs to extend the PrivateChannelEventListener because in the
6+
* ChannelManager handleAuthenticationFailure we assume it's safe to cast to a
7+
* PrivateChannelEventListener
8+
*/
9+
public interface PrivateEncryptedChannelEventListener extends PrivateChannelEventListener {
10+
11+
void onDecryptionFailure(String event, String reason);
12+
}

src/main/java/com/pusher/client/channel/impl/ChannelImpl.java

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import com.pusher.client.util.Factory;
1414

1515
public class ChannelImpl implements InternalChannel {
16-
private final Gson GSON;
16+
protected final Gson GSON;
1717
private static final String INTERNAL_EVENT_PREFIX = "pusher_internal:";
1818
protected static final String SUBSCRIPTION_SUCCESS_EVENT = "pusher_internal:subscription_succeeded";
1919
protected final String name;
@@ -89,33 +89,29 @@ public boolean isSubscribed() {
8989

9090
/* InternalChannel implementation */
9191

92+
@Override
93+
public PusherEvent prepareEvent(String event, String message) {
94+
return GSON.fromJson(message, PusherEvent.class);
95+
}
96+
9297
@Override
9398
public void onMessage(final String event, final String message) {
9499

95100
if (event.equals(SUBSCRIPTION_SUCCESS_EVENT)) {
96101
updateState(ChannelState.SUBSCRIBED);
97-
}
98-
else {
99-
final Set<SubscriptionEventListener> listeners;
100-
synchronized (lock) {
101-
final Set<SubscriptionEventListener> sharedListeners = eventNameToListenerMap.get(event);
102-
if (sharedListeners != null) {
103-
listeners = new HashSet<SubscriptionEventListener>(sharedListeners);
104-
}
105-
else {
106-
listeners = null;
107-
}
108-
}
109-
102+
} else {
103+
final Set<SubscriptionEventListener> listeners = getInterestedListeners(event);
110104
if (listeners != null) {
111-
for (final SubscriptionEventListener listener : listeners) {
112-
final PusherEvent e = GSON.fromJson(message, PusherEvent.class);
113-
factory.queueOnEventThread(new Runnable() {
114-
@Override
115-
public void run() {
116-
listener.onEvent(e);
117-
}
118-
});
105+
final PusherEvent pusherEvent = prepareEvent(event, message);
106+
if (pusherEvent != null) {
107+
for (final SubscriptionEventListener listener : listeners) {
108+
factory.queueOnEventThread(new Runnable() {
109+
@Override
110+
public void run() {
111+
listener.onEvent(pusherEvent);
112+
}
113+
});
114+
}
119115
}
120116
}
121117
}
@@ -213,4 +209,18 @@ private void validateArguments(final String eventName, final SubscriptionEventLi
213209
"Cannot bind or unbind to events on a channel that has been unsubscribed. Call Pusher.subscribe() to resubscribe to this channel");
214210
}
215211
}
212+
213+
protected Set<SubscriptionEventListener> getInterestedListeners(String event) {
214+
synchronized (lock) {
215+
216+
final Set<SubscriptionEventListener> sharedListeners =
217+
eventNameToListenerMap.get(event);
218+
219+
if (sharedListeners == null) {
220+
return null;
221+
}
222+
223+
return new HashSet<>(sharedListeners);
224+
}
225+
}
216226
}

src/main/java/com/pusher/client/channel/impl/ChannelManager.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.pusher.client.channel.Channel;
99
import com.pusher.client.channel.ChannelEventListener;
1010
import com.pusher.client.channel.ChannelState;
11+
import com.pusher.client.channel.PrivateEncryptedChannel;
1112
import com.pusher.client.channel.PresenceChannel;
1213
import com.pusher.client.channel.PrivateChannel;
1314
import com.pusher.client.channel.PrivateChannelEventListener;
@@ -46,6 +47,14 @@ public PrivateChannel getPrivateChannel(String channelName) throws IllegalArgume
4647
}
4748
}
4849

50+
public PrivateEncryptedChannel getPrivateEncryptedChannel(String channelName) throws IllegalArgumentException{
51+
if (!channelName.startsWith("private-encrypted-")) {
52+
throw new IllegalArgumentException("Encrypted private channels must begin with 'private-encrypted-'");
53+
} else {
54+
return (PrivateEncryptedChannel) findChannelInChannelMap(channelName);
55+
}
56+
}
57+
4958
public PresenceChannel getPresenceChannel(String channelName) throws IllegalArgumentException{
5059
if (!channelName.startsWith("presence-")) {
5160
throw new IllegalArgumentException("Presence channels must begin with 'presence-'");
@@ -141,7 +150,7 @@ public void run() {
141150
connection.sendMessage(message);
142151
channel.updateState(ChannelState.SUBSCRIBE_SENT);
143152
} catch (final AuthorizationFailureException e) {
144-
clearDownSubscription(channel, e);
153+
handleAuthenticationFailure(channel, e);
145154
}
146155
}
147156
}
@@ -158,7 +167,7 @@ public void run() {
158167
});
159168
}
160169

161-
private void clearDownSubscription(final InternalChannel channel, final Exception e) {
170+
private void handleAuthenticationFailure(final InternalChannel channel, final Exception e) {
162171

163172
channelNameToChannelMap.remove(channel.getName());
164173
channel.updateState(ChannelState.FAILED);

src/main/java/com/pusher/client/channel/impl/InternalChannel.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import com.pusher.client.channel.Channel;
44
import com.pusher.client.channel.ChannelEventListener;
55
import com.pusher.client.channel.ChannelState;
6+
import com.pusher.client.channel.PusherEvent;
67

78
public interface InternalChannel extends Channel, Comparable<InternalChannel> {
89

910
String toSubscribeMessage();
1011

1112
String toUnsubscribeMessage();
1213

14+
PusherEvent prepareEvent(String event, String message);
15+
1316
void onMessage(String event, String message);
1417

1518
void updateState(ChannelState state);

src/main/java/com/pusher/client/channel/impl/PrivateChannelImpl.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,10 @@ public String toSubscribeMessage() {
116116

117117
@Override
118118
protected String[] getDisallowedNameExpressions() {
119-
return new String[] { "^(?!private-).*" };
119+
return new String[] {
120+
"^(?!private-).*", // double negative, don't not start with private-
121+
"^private-encrypted-.*" // doesn't start with private-encrypted-
122+
};
120123
}
121124

122125
/**

0 commit comments

Comments
 (0)