Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
3f2462f
Add NaCl's symmetric key authenticated decryption code
marekoid Mar 23, 2020
451a3f2
Add copyright for Pusher
marekoid Mar 24, 2020
a336ab7
Exclude crypt/nacl package from Javadoc
marekoid Mar 24, 2020
fa6a54d
Merge pull request #235 from pusher/decrypt
marekoid Mar 25, 2020
d851213
Add Base64 decoding and test for decryption
marekoid Mar 25, 2020
540ff2d
Merge pull request #236 from pusher/decrypt-test
marekoid Mar 26, 2020
ce14b65
Add Base64 char validation
marekoid Mar 26, 2020
1d43fdb
Use a better term
marekoid Mar 26, 2020
4fed3e8
Merge pull request #237 from pusher/base64Validation
marekoid Mar 26, 2020
d035064
Exclude Base64 class from javadoc
marekoid Mar 26, 2020
5c00b4a
Merge pull request #238 from pusher/base64JavadocExclusion
marekoid Mar 27, 2020
5438e6c
Add a test for crypto authenticity failure
marekoid Mar 30, 2020
a880c55
Merge pull request #239 from pusher/decrypt-testFail
marekoid Mar 30, 2020
b539a3e
Add precondition check util
marekoid Mar 30, 2020
dbdea4e
Alter precondition failure massage
marekoid Mar 31, 2020
bb925cd
Use "must" instead of "should" for precondition failure
marekoid Mar 31, 2020
593ca15
Merge pull request #240 from pusher/preconditions
marekoid Mar 31, 2020
428bca0
Fix warnings about C-style array declarations in NaCl
marekoid Mar 31, 2020
3092d93
Merge pull request #241 from pusher/fixCArrays
marekoid Mar 31, 2020
a97bcbd
Fix warnings about manual array copying
marekoid Mar 31, 2020
7514384
Add validation for nonce length
marekoid Mar 31, 2020
034efbb
Merge pull request #242 from pusher/fixManualArrayCopying
marekoid Mar 31, 2020
d6b6bfd
Fix warning about redundant array init
marekoid Apr 1, 2020
7763927
Fix warnings about pointless operations
marekoid Apr 1, 2020
ccc2691
Fix warning about unnecessary semicolon
marekoid Apr 1, 2020
090025e
Fix warnings about lowercase 'l' for long literals
marekoid Apr 1, 2020
bc366a8
Make constants marked and named as such
marekoid Apr 1, 2020
90231f7
Merge pull request #243 from pusher/fixWarningsInNaCl
marekoid Apr 1, 2020
a884795
Merge remote-tracking branch 'remotes/origin/master' into decrypt-int…
marekoid Apr 1, 2020
36747fd
Private Encrypted Channel (#234)
daniellevass Apr 1, 2020
ac42fb2
Remove clear key revisit TODO
marekoid Apr 1, 2020
39f16b2
Merge pull request #244 from pusher/clearClearKeyTODO
marekoid Apr 2, 2020
571607c
Add test around clearing the key
marekoid Apr 2, 2020
d003f33
Mach test data order with param order
marekoid Apr 2, 2020
8ea7340
Add clearing of shared secret on disconnected
marekoid Apr 2, 2020
7afd57e
Merge pull request #246 from pusher/testOpenFailsAfterClearKey
marekoid Apr 2, 2020
ddda077
Make unsubscribe to remove disconnect listener
marekoid Apr 3, 2020
8ae2cb2
Add info about the need for tmp log for the semi-manual test
marekoid Apr 3, 2020
670609e
Fix IndexOutOfBounds for no args in example app
marekoid Apr 3, 2020
b5059d3
Make naming consistent and more clear
marekoid Apr 3, 2020
9c8bf51
Add PrivateEncryptedChannelClearsKeyTest
marekoid Apr 3, 2020
95d5ca3
Revert making example app unsubscribe/disconnect
marekoid Apr 3, 2020
0250472
Remove unused ArgumentCaptor
marekoid Apr 3, 2020
3a8a7bc
Add. prepareEvent method to InternalChannel interface and implement i…
daniellevass Apr 6, 2020
d346cea
Merge pull request #247 from pusher/clearSharedSecretOnDisconnected
daniellevass Apr 6, 2020
c8ea300
Refactor getInterestedListeners to it's own method in ChannelImpl
daniellevass Apr 6, 2020
f290cb6
Decrypt PrivateEncrypted messages and retry once
daniellevass Apr 6, 2020
8ee7b59
Merge branch 'decrypt-integration' into dev-decrypt-plus-retry
daniellevass Apr 6, 2020
0cf29b7
Add tests for retrying decrypting messages
daniellevass Apr 6, 2020
0e178e8
Simplify the getInterestedListeners method in ChannelImpl
daniellevass Apr 6, 2020
4bfc70d
Handle multiple failed decryption calls better
daniellevass Apr 6, 2020
3a8f63e
When decrypting a message, pass the json map to the PusherEvent const…
daniellevass Apr 6, 2020
b03e466
Refactor decryptMessage to package up as a PusherEvent
daniellevass Apr 6, 2020
a87547f
Move the retry logic into it's own method
daniellevass Apr 6, 2020
2f5b7e2
Revert "Move the retry logic into it's own method"
daniellevass Apr 6, 2020
0a62b93
Keep the shared_secret after the second retry so any subsequent messa…
daniellevass Apr 7, 2020
e694400
Return a copy of the interestedListeners
daniellevass Apr 7, 2020
f4fa2dd
Merge pull request #249 from pusher/dev-decrypt-plus-retry
daniellevass Apr 7, 2020
63ca0d0
Private Encrypted Channels docs (#250)
daniellevass Apr 7, 2020
2ec4a6d
Add maven central badge to readme
daniellevass Apr 7, 2020
7c202e3
Prepare 2.1.0 release
daniellevass Apr 7, 2020
b98413e
Add beta notices to private encrypted channels
daniellevass Apr 8, 2020
e311d55
Update comment on PrivateEncryptedChannelEventListener
daniellevass Apr 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# pusher-websocket-java changelog

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
## Version 2.1.0 - 8th April 2020

* Added support for [private encrypted channels](https://pusher.com/docs/channels/using_channels/encrypted-channels)

## Version 2.0.2

Expand Down
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

[![Build Status](https://travis-ci.org/pusher/pusher-websocket-java.svg?branch=master)](https://travis-ci.org/pusher/pusher-websocket-java)
[![codecov](https://codecov.io/gh/pusher/pusher-websocket-java/branch/master/graph/badge.svg)](https://codecov.io/gh/pusher/pusher-websocket-java)
[![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)

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

Expand Down Expand Up @@ -30,6 +31,7 @@ This README covers the following topics:
- [Subscribing to channels](#subscribing-to-channels)
- [Public channels](#public-channels)
- [Private channels](#private-channels)
- [Private encrypted channels [BETA]](#private-encrypted-channels)
- [Presence channels](#presence-channels)
- [The User object](#the-user-object)
- [Binding and handling events](#binding-and-handling-events)
Expand Down Expand Up @@ -59,7 +61,7 @@ The pusher-java-client is available in Maven Central.
<dependency>
<groupId>com.pusher</groupId>
<artifactId>pusher-java-client</artifactId>
<version>2.0.2</version>
<version>2.1.0</version>
</dependency>
</dependencies>
```
Expand All @@ -68,7 +70,7 @@ The pusher-java-client is available in Maven Central.

```groovy
dependencies {
compile 'com.pusher:pusher-java-client:2.0.2'
compile 'com.pusher:pusher-java-client:2.1.0'
}
```

Expand Down Expand Up @@ -271,6 +273,37 @@ PrivateChannel channel = pusher.subscribePrivate("private-channel",
});
```

### Private encrypted channels [BETA]

Similar to Private channels, you can also subscribe to a
[private encrypted channel](https://pusher.com/docs/channels/using_channels/encrypted-channels).
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.

Like the private channel, you must provide your own authentication endpoint,
with your own encryption master key. There is a
[demonstration endpoint to look at using nodejs](https://github.com/pusher/pusher-channels-auth-example#using-e2e-encryption).

To get started you need to subscribe to your channel, provide a `PrivateEncryptedChannelEventListener`, and a list of the events you are
interested in, for example:

```java
PrivateEncryptedChannel privateEncryptedChannel =
pusher.subscribePrivateEncrypted("private-encrypted-channel", listener, "my-event");
```

In addition to the events that are possible on public channels the
`PrivateEncryptedChannelEventListener` also has the following methods:
* `onAuthenticationFailure(String message, Exception e)` - This is called if
the `Authorizer` does not successfully authenticate the subscription:
* `onDecryptionFailure(String event, String reason);` - This is called if the message cannot be
decrypted. The decryption will attempt to refresh the shared secret key once
from the `Authorizer`.

There is a
[working example in the repo](https://github.com/pusher/pusher-websocket-java/blob/master/src/main/java/com/pusher/client/example/PrivateEncryptedChannelExampleApp.java)
which you can use with the
[demonstration authorization endpoint](https://github.com/pusher/pusher-channels-auth-example#using-e2e-encryption)

### Presence channels

[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)).
Expand Down
17 changes: 11 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ apply plugin: 'signing'
apply plugin: 'jacoco'

group = "com.pusher"
version = "2.0.2"
version = "2.1.0"
sourceCompatibility = "1.8"
targetCompatibility = "1.8"

Expand All @@ -45,9 +45,12 @@ repositories {
dependencies {
compile "com.google.code.gson:gson:2.2.2"
compile "org.java-websocket:Java-WebSocket:1.4.0"

testCompile "org.mockito:mockito-all:1.8.5"
testCompile "org.powermock:powermock-module-junit4:1.4.11"
testCompile "org.powermock:powermock-api-mockito:1.4.11"

testImplementation "com.google.truth:truth:1.0.1"
}


Expand All @@ -64,11 +67,13 @@ javadoc {
options.overview = file("src/main/javadoc/overview.html")
// uncomment this to use the custom javadoc styles
//options.stylesheetFile = file("src/main/javadoc/css/styles.css")
exclude "**/com/pusher/client/channel/impl/*"
exclude "**/com/pusher/client/connection/impl/*"
exclude "**/com/pusher/client/connection/websocket/*"
exclude "**/org/java_websocket/*"
exclude "**/com/pusher/client/example/*"
exclude "com/pusher/client/channel/impl/*"
exclude "com/pusher/client/connection/impl/*"
exclude "com/pusher/client/connection/websocket/*"
exclude "com/pusher/client/crypto/nacl/*"
exclude "com/pusher/client/util/internal/*"
exclude "org/java_websocket/*"
exclude "com/pusher/client/example/*"
options.linkSource = true
}

Expand Down
45 changes: 45 additions & 0 deletions src/main/java/com/pusher/client/Pusher.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.pusher.client.channel.Channel;
import com.pusher.client.channel.ChannelEventListener;
import com.pusher.client.channel.PrivateEncryptedChannel;
import com.pusher.client.channel.PrivateEncryptedChannelEventListener;
import com.pusher.client.channel.PresenceChannel;
import com.pusher.client.channel.PresenceChannelEventListener;
import com.pusher.client.channel.PrivateChannel;
Expand All @@ -11,6 +13,7 @@
import com.pusher.client.channel.impl.InternalChannel;
import com.pusher.client.channel.impl.PresenceChannelImpl;
import com.pusher.client.channel.impl.PrivateChannelImpl;
import com.pusher.client.channel.impl.PrivateEncryptedChannelImpl;
import com.pusher.client.connection.Connection;
import com.pusher.client.connection.ConnectionEventListener;
import com.pusher.client.connection.ConnectionState;
Expand Down Expand Up @@ -284,6 +287,38 @@ public PrivateChannel subscribePrivate(final String channelName, final PrivateCh
return channel;
}


/**
* Subscribes to a {@link com.pusher.client.channel.PrivateEncryptedChannel} which
* requires authentication.
*
* @param channelName The name of the channel to subscribe to.
* @param listener A listener to be informed of both Pusher channel protocol events and
* subscription data events.
* @param eventNames An optional list of names of events to be bound to on the channel.
* The equivalent of calling
* {@link com.pusher.client.channel.Channel#bind(String, SubscriptionEventListener)}
* one or more times.
* @return A new {@link com.pusher.client.channel.PrivateEncryptedChannel} representing
* the subscription.
* @throws IllegalStateException if a {@link com.pusher.client.Authorizer} has not been set for
* the {@link Pusher} instance via {@link #Pusher(String, PusherOptions)}.
*/
public PrivateEncryptedChannel subscribePrivateEncrypted(
final String channelName,
final PrivateEncryptedChannelEventListener listener,
final String... eventNames) {

throwExceptionIfNoAuthorizerHasBeenSet();

final PrivateEncryptedChannelImpl channel = factory.newPrivateEncryptedChannel(
connection, channelName, pusherOptions.getAuthorizer());
channelManager.subscribeTo(channel, listener, eventNames);

return channel;
}


/**
* Subscribes to a {@link com.pusher.client.channel.PresenceChannel} which
* requires authentication.
Expand Down Expand Up @@ -363,6 +398,16 @@ public PrivateChannel getPrivateChannel(String channelName){
return channelManager.getPrivateChannel(channelName);
}

/**
*
* @param channelName The name of the private encrypted channel to be retrieved
* @return A private encrypted channel, or null if it could not be found
* @throws IllegalArgumentException if you try to retrieve a public or presence channel.
*/
public PrivateEncryptedChannel getPrivateEncryptedChannel(String channelName){
return channelManager.getPrivateEncryptedChannel(channelName);
}

/**
*
* @param channelName The name of the presence channel to be retrieved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ public interface PrivateChannelEventListener extends ChannelEventListener {
/**
* Called when an attempt to authenticate a private channel fails.
*
* @param message
* A description of the problem.
* @param e
* An associated exception, if available.
* @param message A description of the problem.
* @param e An associated exception, if available.
*/
void onAuthenticationFailure(String message, Exception e);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.pusher.client.channel;

/**
* Represents a subscription to an encrypted private channel.
*/
public interface PrivateEncryptedChannel extends Channel {

// it's not currently possible to send a message using private encrypted channels
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.pusher.client.channel;

/**
* Interface to listen to private encrypted channel events.
* Note: This needs to extend the PrivateChannelEventListener because in the
* ChannelManager handleAuthenticationFailure we assume it's safe to cast to a
* PrivateChannelEventListener
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this comment should be updated to say handleAuthenticationFailure as we refactored that method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have updated.

public interface PrivateEncryptedChannelEventListener extends PrivateChannelEventListener {

void onDecryptionFailure(String event, String reason);
}
54 changes: 32 additions & 22 deletions src/main/java/com/pusher/client/channel/impl/ChannelImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import com.pusher.client.util.Factory;

public class ChannelImpl implements InternalChannel {
private final Gson GSON;
protected final Gson GSON;
private static final String INTERNAL_EVENT_PREFIX = "pusher_internal:";
protected static final String SUBSCRIPTION_SUCCESS_EVENT = "pusher_internal:subscription_succeeded";
protected final String name;
Expand Down Expand Up @@ -89,33 +89,29 @@ public boolean isSubscribed() {

/* InternalChannel implementation */

@Override
public PusherEvent prepareEvent(String event, String message) {
return GSON.fromJson(message, PusherEvent.class);
}

@Override
public void onMessage(final String event, final String message) {

if (event.equals(SUBSCRIPTION_SUCCESS_EVENT)) {
updateState(ChannelState.SUBSCRIBED);
}
else {
final Set<SubscriptionEventListener> listeners;
synchronized (lock) {
final Set<SubscriptionEventListener> sharedListeners = eventNameToListenerMap.get(event);
if (sharedListeners != null) {
listeners = new HashSet<SubscriptionEventListener>(sharedListeners);
}
else {
listeners = null;
}
}

} else {
final Set<SubscriptionEventListener> listeners = getInterestedListeners(event);
if (listeners != null) {
for (final SubscriptionEventListener listener : listeners) {
final PusherEvent e = GSON.fromJson(message, PusherEvent.class);
factory.queueOnEventThread(new Runnable() {
@Override
public void run() {
listener.onEvent(e);
}
});
final PusherEvent pusherEvent = prepareEvent(event, message);
if (pusherEvent != null) {
for (final SubscriptionEventListener listener : listeners) {
factory.queueOnEventThread(new Runnable() {
@Override
public void run() {
listener.onEvent(pusherEvent);
}
});
}
}
}
}
Expand Down Expand Up @@ -213,4 +209,18 @@ private void validateArguments(final String eventName, final SubscriptionEventLi
"Cannot bind or unbind to events on a channel that has been unsubscribed. Call Pusher.subscribe() to resubscribe to this channel");
}
}

protected Set<SubscriptionEventListener> getInterestedListeners(String event) {
synchronized (lock) {

final Set<SubscriptionEventListener> sharedListeners =
eventNameToListenerMap.get(event);

if (sharedListeners == null) {
return null;
}

return new HashSet<>(sharedListeners);
}
}
}
13 changes: 11 additions & 2 deletions src/main/java/com/pusher/client/channel/impl/ChannelManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.pusher.client.channel.Channel;
import com.pusher.client.channel.ChannelEventListener;
import com.pusher.client.channel.ChannelState;
import com.pusher.client.channel.PrivateEncryptedChannel;
import com.pusher.client.channel.PresenceChannel;
import com.pusher.client.channel.PrivateChannel;
import com.pusher.client.channel.PrivateChannelEventListener;
Expand Down Expand Up @@ -46,6 +47,14 @@ public PrivateChannel getPrivateChannel(String channelName) throws IllegalArgume
}
}

public PrivateEncryptedChannel getPrivateEncryptedChannel(String channelName) throws IllegalArgumentException{
if (!channelName.startsWith("private-encrypted-")) {
throw new IllegalArgumentException("Encrypted private channels must begin with 'private-encrypted-'");
} else {
return (PrivateEncryptedChannel) findChannelInChannelMap(channelName);
}
}

public PresenceChannel getPresenceChannel(String channelName) throws IllegalArgumentException{
if (!channelName.startsWith("presence-")) {
throw new IllegalArgumentException("Presence channels must begin with 'presence-'");
Expand Down Expand Up @@ -141,7 +150,7 @@ public void run() {
connection.sendMessage(message);
channel.updateState(ChannelState.SUBSCRIBE_SENT);
} catch (final AuthorizationFailureException e) {
clearDownSubscription(channel, e);
handleAuthenticationFailure(channel, e);
}
}
}
Expand All @@ -158,7 +167,7 @@ public void run() {
});
}

private void clearDownSubscription(final InternalChannel channel, final Exception e) {
private void handleAuthenticationFailure(final InternalChannel channel, final Exception e) {

channelNameToChannelMap.remove(channel.getName());
channel.updateState(ChannelState.FAILED);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import com.pusher.client.channel.Channel;
import com.pusher.client.channel.ChannelEventListener;
import com.pusher.client.channel.ChannelState;
import com.pusher.client.channel.PusherEvent;

public interface InternalChannel extends Channel, Comparable<InternalChannel> {

String toSubscribeMessage();

String toUnsubscribeMessage();

PusherEvent prepareEvent(String event, String message);

void onMessage(String event, String message);

void updateState(ChannelState state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ public String toSubscribeMessage() {

@Override
protected String[] getDisallowedNameExpressions() {
return new String[] { "^(?!private-).*" };
return new String[] {
"^(?!private-).*", // double negative, don't not start with private-
"^private-encrypted-.*" // doesn't start with private-encrypted-
};
}

/**
Expand Down
Loading