diff --git a/.pubnub.yml b/.pubnub.yml index e5962fe11..a539941bd 100644 --- a/.pubnub.yml +++ b/.pubnub.yml @@ -1,9 +1,9 @@ name: java -version: 5.0.0 +version: 5.1.0 schema: 1 scm: github.com/pubnub/java files: - - build/libs/pubnub-gson-5.0.0-all.jar + - build/libs/pubnub-gson-5.1.0-all.jar sdks: - type: library @@ -234,6 +234,13 @@ sdks: is-required: Required changelog: + - version: v5.1.0 + date: 2021-05-20 + changes: + - type: feature + text: "Method grantToken has beed added. It allows generation of signed token with permissions for channels and channel groups." + - type: bug + text: "UUID is now exposed as PNMembership field which make is accessible from PNMembershipResult argument of SubscribeCallback.membership() method." - version: v5.0.0 date: 2021-05-12 changes: diff --git a/CHANGELOG.md b/CHANGELOG.md index 84e3d4198..43c0df323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ +## [v5.1.0](https://github.com/pubnub/java/releases/tag/v5.1.0) +May-20-2021 + +[Full Changelog](https://github.com/pubnub/java/compare/v5.0.0...v5.1.0) + +- 🌟️ Method grantToken has beed added. It allows generation of signed token with permissions for channels and channel groups. +- 🐛 UUID is now exposed as PNMembership field which make is accessible from PNMembershipResult argument of SubscribeCallback.membership() method. + ## [v5.0.0](https://github.com/pubnub/java/releases/tag/v5.0.0) May-12-2021 -[Full Changelog](https://github.com/pubnub/java/compare/v4.36.0...v5.0.0) - - 🌟️ Now random initialisation vector used when encryption enabled is now default behaviour. - 🐛 There were some non daemon threads running in background preventing VM from exiting. Now they are daemon threads. diff --git a/README.md b/README.md index 3246be6a6..93dab668e 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ You will need the publish and subscribe keys to authenticate your app. Get your com.pubnub pubnub-gson - 5.0.0 + 5.1.0 ``` * for Gradle, add the following dependency in your `gradle.build`: ```groovy - compile group: 'com.pubnub', name: 'pubnub-gson', version: '5.0.0' + compile group: 'com.pubnub', name: 'pubnub-gson', version: '5.1.0' ``` 2. Configure your keys: diff --git a/build.gradle b/build.gradle index 46f64b819..d43f870d0 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { } group = 'com.pubnub' -version = '5.0.0' +version = '5.1.0' description = """""" diff --git a/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/CustomMetadataInMembershipPropagationIT.java b/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/CustomMetadataInMembershipPropagationIT.java index 40645996b..a3151aa94 100644 --- a/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/CustomMetadataInMembershipPropagationIT.java +++ b/src/integrationTest/java/com/pubnub/api/integration/objects/memberships/CustomMetadataInMembershipPropagationIT.java @@ -1,21 +1,29 @@ package com.pubnub.api.integration.objects.memberships; +import com.pubnub.api.PubNub; import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.managers.subscription.SubscribeCallbackAdapter; import com.pubnub.api.integration.objects.ObjectsApiBaseIT; import com.pubnub.api.models.consumer.objects_api.channel.PNSetChannelMetadataResult; import com.pubnub.api.models.consumer.objects_api.membership.PNChannelMembership; import com.pubnub.api.models.consumer.objects_api.membership.PNGetMembershipsResult; +import com.pubnub.api.models.consumer.objects_api.membership.PNMembershipResult; import com.pubnub.api.models.consumer.objects_api.membership.PNSetMembershipResult; +import org.awaitility.core.ThrowingRunnable; import org.junit.After; +import org.junit.Before; import org.junit.Test; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.TimeUnit; import static com.pubnub.api.endpoints.objects_api.utils.Include.PNChannelDetailsLevel.CHANNEL; import static com.pubnub.api.endpoints.objects_api.utils.Include.PNChannelDetailsLevel.CHANNEL_WITH_CUSTOM; +import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasProperty; @@ -39,6 +47,22 @@ public class CustomMetadataInMembershipPropagationIT extends ObjectsApiBaseIT { private PNSetMembershipResult setMembershipResult; + private CopyOnWriteArrayList pnMembershipResults = new CopyOnWriteArrayList<>(); + + @Before + public void setCallbackListener() { + pubNubUnderTest.addListener(new SubscribeCallbackAdapter() { + @Override + public void membership(final PubNub pubnub, final PNMembershipResult pnMembershipResult) { + pnMembershipResults.add(pnMembershipResult); + } + }); + + pubNubUnderTest.subscribe() + .channels(Collections.singletonList(testChannelMetadataId)) + .execute(); + } + @Test public void setMembershipCustomHappyPath() throws PubNubException { final String testChannelName = "The Name of the Channel"; @@ -86,6 +110,14 @@ public void setMembershipCustomHappyPath() throws PubNubException { hasProperty("name", is(testChannelName)), hasProperty("description", is(testDescription)), hasProperty("custom", nullValue())))))))); + + await().atMost(1, TimeUnit.SECONDS).untilAsserted(new ThrowingRunnable() { + @Override + public void run() throws Throwable { + assertThat(pnMembershipResults, hasItem( + hasProperty("data", hasProperty("uuid", is(pubNubUnderTest.getConfiguration().getUuid()))))); + } + }); } @After diff --git a/src/integrationTest/java/com/pubnub/api/integration/pam/GrantTokenIT.java b/src/integrationTest/java/com/pubnub/api/integration/pam/GrantTokenIT.java new file mode 100644 index 000000000..d730eaa61 --- /dev/null +++ b/src/integrationTest/java/com/pubnub/api/integration/pam/GrantTokenIT.java @@ -0,0 +1,53 @@ +package com.pubnub.api.integration.pam; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.integration.util.BaseIntegrationTest; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant; +import com.pubnub.api.models.consumer.access_manager.v3.PNGrantTokenResult; +import com.pubnub.api.models.consumer.access_manager.v3.PNToken; +import org.junit.Test; + +import java.util.Arrays; +import static org.junit.Assert.assertEquals; + + +public class GrantTokenIT extends BaseIntegrationTest { + private final PubNub pubNubUnderTest = getServer(); + + @Test + public void happyPath() throws PubNubException { + //given + final int expectedTTL = 1337; + final String expectedChannelResourceName = "channelResource"; + final String expectedChannelPattern = "channel.*"; + final String expectedChannelGroupResourceId = "channelGroup"; + final String expectedChannelGroupPattern = "channelGroup.*"; + + //when + final PNGrantTokenResult grantTokenResponse = pubNubUnderTest + .grantToken() + .ttl(expectedTTL) + .channels(Arrays.asList(ChannelGrant.name(expectedChannelResourceName).delete(), + ChannelGrant.pattern(expectedChannelPattern).write())) + .channelGroups(Arrays.asList(ChannelGroupGrant.id(expectedChannelGroupResourceId).read(), + ChannelGroupGrant.pattern(expectedChannelGroupPattern).manage())) + .sync(); + + final PNToken pnToken = pubNubUnderTest.parseToken(grantTokenResponse.getToken()); + + //then + assertEquals(expectedTTL, pnToken.getTtl()); + assertEquals(new PNToken.PNResourcePermissions(false, false, false, false, true), + pnToken.getResources().getChannels().get(expectedChannelResourceName)); + assertEquals(new PNToken.PNResourcePermissions(false, true, false, false, false), + pnToken.getResources().getChannelGroups().get(expectedChannelGroupResourceId)); + assertEquals(new PNToken.PNResourcePermissions(false, false, true, false, false), + pnToken.getPatterns().getChannels().get(expectedChannelPattern)); + assertEquals(new PNToken.PNResourcePermissions(false, false, false, true, false), + pnToken.getPatterns().getChannelGroups().get(expectedChannelGroupPattern)); + } + +} diff --git a/src/main/java/com/pubnub/api/PubNub.java b/src/main/java/com/pubnub/api/PubNub.java index da48acaba..1e3f80e64 100644 --- a/src/main/java/com/pubnub/api/PubNub.java +++ b/src/main/java/com/pubnub/api/PubNub.java @@ -11,6 +11,7 @@ import com.pubnub.api.endpoints.MessageCounts; import com.pubnub.api.endpoints.Time; import com.pubnub.api.endpoints.access.Grant; +import com.pubnub.api.endpoints.access.GrantToken; import com.pubnub.api.endpoints.channel_groups.AddChannelChannelGroup; import com.pubnub.api.endpoints.channel_groups.AllChannelsChannelGroup; import com.pubnub.api.endpoints.channel_groups.DeleteChannelGroup; @@ -62,6 +63,8 @@ import com.pubnub.api.managers.StateManager; import com.pubnub.api.managers.SubscriptionManager; import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.managers.token_manager.TokenParser; +import com.pubnub.api.models.consumer.access_manager.v3.PNToken; import com.pubnub.api.vendor.Crypto; import com.pubnub.api.vendor.FileEncryptionUtil; import lombok.Getter; @@ -94,10 +97,12 @@ public class PubNub { private RetrofitManager retrofitManager; + private final TokenParser tokenParser; + private static final int TIMESTAMP_DIVIDER = 1000; private static final int MAX_SEQUENCE = 65535; - private static final String SDK_VERSION = "5.0.0"; + private static final String SDK_VERSION = "5.1.0"; private final ListenerManager listenerManager; private final StateManager stateManager; @@ -121,6 +126,7 @@ public PubNub(@NotNull PNConfiguration initialConfig) { delayedReconnectionManager, duplicationManager); this.publishSequenceManager = new PublishSequenceManager(MAX_SEQUENCE); + this.tokenParser = new TokenParser(); instanceId = UUID.randomUUID().toString(); } @@ -217,6 +223,11 @@ public Grant grant() { return new Grant(this, this.telemetryManager, this.retrofitManager); } + @NotNull + public GrantToken grantToken() { + return new GrantToken(this, this.telemetryManager, this.retrofitManager); + } + @NotNull public GetState getPresenceState() { return new GetState(this, this.telemetryManager, this.retrofitManager); @@ -575,4 +586,8 @@ public List getSubscribedChannelGroups() { public void unsubscribeAll() { subscriptionManager.unsubscribeAll(); } + + public PNToken parseToken(String token) throws PubNubException { + return tokenParser.unwrapToken(token); + } } diff --git a/src/main/java/com/pubnub/api/PubNubException.java b/src/main/java/com/pubnub/api/PubNubException.java index a3aa267ed..b663e44fc 100644 --- a/src/main/java/com/pubnub/api/PubNubException.java +++ b/src/main/java/com/pubnub/api/PubNubException.java @@ -33,6 +33,12 @@ public PubNubException(final String errormsg, this.affectedCall = affectedCall; } + @Override + @ToString.Include + public Throwable getCause() { + return super.getCause(); + } + @Override public String getMessage() { return errormsg; @@ -42,3 +48,4 @@ public String getMessage() { @ToString.Exclude private Call affectedCall; } + diff --git a/src/main/java/com/pubnub/api/PubNubUtil.java b/src/main/java/com/pubnub/api/PubNubUtil.java index 485d7e114..6bea9355f 100644 --- a/src/main/java/com/pubnub/api/PubNubUtil.java +++ b/src/main/java/com/pubnub/api/PubNubUtil.java @@ -17,6 +17,7 @@ import java.net.URLEncoder; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -285,4 +286,8 @@ public static byte[] readBytes(final InputStream inputStream) throws IOException } } + public static boolean isNullOrEmpty(final Collection collection) { + return collection == null || collection.isEmpty(); + } + } diff --git a/src/main/java/com/pubnub/api/endpoints/access/GrantToken.java b/src/main/java/com/pubnub/api/endpoints/access/GrantToken.java new file mode 100644 index 000000000..77d229509 --- /dev/null +++ b/src/main/java/com/pubnub/api/endpoints/access/GrantToken.java @@ -0,0 +1,120 @@ +package com.pubnub.api.endpoints.access; + +import com.google.gson.JsonObject; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.builder.PubNubErrorBuilder; +import com.pubnub.api.endpoints.Endpoint; +import com.pubnub.api.enums.PNOperationType; +import com.pubnub.api.managers.RetrofitManager; +import com.pubnub.api.managers.TelemetryManager; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant; +import com.pubnub.api.models.consumer.access_manager.v3.PNGrantTokenResult; +import com.pubnub.api.models.server.access_manager.v3.GrantTokenRequestBody; +import lombok.Setter; +import lombok.experimental.Accessors; +import retrofit2.Call; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static com.pubnub.api.PubNubUtil.isNullOrEmpty; + +@Accessors(chain = true, fluent = true) +public class GrantToken extends Endpoint { + + @Setter + private Integer ttl; + @Setter + private Object meta; + @Setter + private List channels = Collections.emptyList(); + @Setter + private List channelGroups = Collections.emptyList(); + + public GrantToken(PubNub pubnub, TelemetryManager telemetryManager, RetrofitManager retrofit) { + super(pubnub, telemetryManager, retrofit); + } + + @Override + protected List getAffectedChannels() { + final ArrayList affectedChannels = new ArrayList<>(); + for (ChannelGrant channelGrant : channels) { + affectedChannels.add(channelGrant.getId()); + } + return affectedChannels; + } + + @Override + protected List getAffectedChannelGroups() { + final ArrayList affectedChannelGroups = new ArrayList<>(); + for (ChannelGroupGrant channelGroupGrant : channelGroups) { + affectedChannelGroups.add(channelGroupGrant.getId()); + } + return affectedChannelGroups; + } + + @Override + protected void validateParams() throws PubNubException { + if (this.getPubnub().getConfiguration().getSecretKey() == null || this.getPubnub() + .getConfiguration() + .getSecretKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SECRET_KEY_MISSING).build(); + } + if (this.getPubnub().getConfiguration().getSubscribeKey() == null || this.getPubnub() + .getConfiguration() + .getSubscribeKey() + .isEmpty()) { + throw PubNubException.builder().pubnubError(PubNubErrorBuilder.PNERROBJ_SUBSCRIBE_KEY_MISSING).build(); + } + if (isNullOrEmpty(channels) + && isNullOrEmpty(channelGroups)) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_RESOURCES_MISSING) + .build(); + } + if (this.ttl == null) { + throw PubNubException.builder() + .pubnubError(PubNubErrorBuilder.PNERROBJ_TTL_MISSING) + .build(); + } + } + + @Override + protected Call doWork(Map queryParams) throws PubNubException { + GrantTokenRequestBody requestBody = GrantTokenRequestBody.builder() + .ttl(ttl) + .channels(channels) + .groups(channelGroups) + .meta(meta) + .build(); + + return this.getRetrofit() + .getAccessManagerService() + .grantToken(this.getPubnub().getConfiguration().getSubscribeKey(), requestBody, queryParams); + } + + @Override + protected PNGrantTokenResult createResponse(Response input) throws PubNubException { + if (input.body() == null) { + return null; + } + + return new PNGrantTokenResult(input.body().getAsJsonObject("data").get("token").getAsString()); + } + + @Override + protected PNOperationType getOperationType() { + return PNOperationType.PNAccessManagerGrantToken; + } + + @Override + protected boolean isAuthRequired() { + return false; + } +} diff --git a/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.java b/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.java new file mode 100644 index 000000000..20a30697c --- /dev/null +++ b/src/main/java/com/pubnub/api/managers/token_manager/TokenParser.java @@ -0,0 +1,27 @@ +package com.pubnub.api.managers.token_manager; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.cbor.CBORFactory; +import com.pubnub.api.PubNubException; +import com.pubnub.api.models.consumer.access_manager.v3.PNToken; +import com.pubnub.api.vendor.Base64; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERROBJ_INVALID_ACCESS_TOKEN; + +public class TokenParser { + private final ObjectMapper mapper = new ObjectMapper(new CBORFactory()); + + public PNToken unwrapToken(String token) throws PubNubException { + try { + byte[] byteArray = Base64.decode(token.getBytes(StandardCharsets.UTF_8), Base64.URL_SAFE); + return mapper.readValue(byteArray, PNToken.class); + } catch (IOException e) { + throw PubNubException.builder() + .pubnubError(PNERROBJ_INVALID_ACCESS_TOKEN) + .build(); + } + } +} diff --git a/src/main/java/com/pubnub/api/models/TokenBitmask.java b/src/main/java/com/pubnub/api/models/TokenBitmask.java new file mode 100644 index 000000000..8f2107a78 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/TokenBitmask.java @@ -0,0 +1,12 @@ +package com.pubnub.api.models; + +public class TokenBitmask { + private TokenBitmask() { + } + + public static final int READ = 1; + public static final int WRITE = 2; + public static final int MANAGE = 4; + public static final int DELETE = 8; + public static final int CREATE = 16; +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/Channel.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/Channel.java deleted file mode 100644 index fc206e7e2..000000000 --- a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/Channel.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.pubnub.api.models.consumer.access_manager.v3; - -public class Channel extends PNResource { - - private Channel() { - - } - - public static Channel name(String channelName) { - Channel channel = new Channel(); - channel.resourceName = channelName; - return channel; - } - - public static Channel pattern(String channelPattern) { - Channel channel = new Channel(); - channel.resourcePattern = channelPattern; - return channel; - } - - @Override - public Channel read() { - return super.read(); - } - - @Override - public Channel delete() { - return super.delete(); - } - - @Override - public Channel write() { - return super.write(); - } -} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGrant.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGrant.java new file mode 100644 index 000000000..153e6e426 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGrant.java @@ -0,0 +1,35 @@ +package com.pubnub.api.models.consumer.access_manager.v3; + +public class ChannelGrant extends PNResource { + + private ChannelGrant() { + + } + + public static ChannelGrant name(String channelName) { + ChannelGrant channelGrant = new ChannelGrant(); + channelGrant.resourceName = channelName; + return channelGrant; + } + + public static ChannelGrant pattern(String channelPattern) { + ChannelGrant channelGrant = new ChannelGrant(); + channelGrant.resourcePattern = channelPattern; + return channelGrant; + } + + @Override + public ChannelGrant read() { + return super.read(); + } + + @Override + public ChannelGrant delete() { + return super.delete(); + } + + @Override + public ChannelGrant write() { + return super.write(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGroupGrant.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGroupGrant.java new file mode 100644 index 000000000..edde89f27 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/ChannelGroupGrant.java @@ -0,0 +1,29 @@ +package com.pubnub.api.models.consumer.access_manager.v3; + +public class ChannelGroupGrant extends PNResource { + + private ChannelGroupGrant() { + } + + public static ChannelGroupGrant id(String groupName) { + ChannelGroupGrant channelGroupGrant = new ChannelGroupGrant(); + channelGroupGrant.resourceName = groupName; + return channelGroupGrant; + } + + public static ChannelGroupGrant pattern(String groupPattern) { + ChannelGroupGrant channelGroupGrant = new ChannelGroupGrant(); + channelGroupGrant.resourcePattern = groupPattern; + return channelGroupGrant; + } + + @Override + public ChannelGroupGrant read() { + return super.read(); + } + + @Override + public ChannelGroupGrant manage() { + return super.manage(); + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/Group.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/Group.java deleted file mode 100644 index 08459a8b1..000000000 --- a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/Group.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.pubnub.api.models.consumer.access_manager.v3; - -public class Group extends PNResource { - - private Group() { - - } - - public static Group id(String groupName) { - Group group = new Group(); - group.resourceName = groupName; - return group; - } - - public static Group pattern(String groupPattern) { - Group group = new Group(); - group.resourcePattern = groupPattern; - return group; - } - - @Override - public Group read() { - return super.read(); - } - - @Override - public Group manage() { - return super.manage(); - } -} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNGrantTokenResult.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNGrantTokenResult.java index ee9aea960..ee079bc90 100644 --- a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNGrantTokenResult.java +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNGrantTokenResult.java @@ -1,13 +1,10 @@ package com.pubnub.api.models.consumer.access_manager.v3; -import lombok.Builder; -import lombok.Getter; -import lombok.ToString; +import lombok.Data; +import lombok.NonNull; -@Builder -@ToString -@Getter +@Data public class PNGrantTokenResult { - - private String token; + @NonNull + private final String token; } diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNResource.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNResource.java index 274e8f294..31af4e4a6 100644 --- a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNResource.java +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNResource.java @@ -3,7 +3,7 @@ import lombok.AccessLevel; import lombok.Getter; -@Getter() +@Getter public abstract class PNResource { @Getter(AccessLevel.NONE) diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java new file mode 100644 index 000000000..9ee565d71 --- /dev/null +++ b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/PNToken.java @@ -0,0 +1,71 @@ +package com.pubnub.api.models.consumer.access_manager.v3; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.pubnub.api.models.TokenBitmask; +import lombok.Data; +import lombok.NonNull; + +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class PNToken { + private final int version; + private final long timestamp; + private final long ttl; + @NonNull + private final PNTokenResources resources; + @NonNull + private final PNTokenResources patterns; + + @JsonCreator + public static PNToken of( + @JsonProperty("v") + final int v, + @JsonProperty("t") + final long t, + @JsonProperty("ttl") + final long ttl, + @JsonProperty("res") + final PNTokenResources res, + @JsonProperty("pat") + final PNTokenResources pat) { + return new PNToken(v, t, ttl, res, pat); + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class PNTokenResources { + @NonNull + private final Map channels; + @NonNull + private final Map channelGroups; + + @JsonCreator + public static PNTokenResources of(@JsonProperty("chan") final Map chan, + @JsonProperty("grp") final Map grp) { + + return new PNTokenResources(chan, grp); + } + } + + @Data + public static class PNResourcePermissions { + private final boolean create; + private final boolean read; + private final boolean write; + private final boolean manage; + private final boolean delete; + + @JsonCreator + public static PNResourcePermissions of(int grant) { + return new PNResourcePermissions((grant & TokenBitmask.CREATE) != 0, + (grant & TokenBitmask.READ) != 0, + (grant & TokenBitmask.WRITE) != 0, + (grant & TokenBitmask.MANAGE) != 0, + (grant & TokenBitmask.DELETE) != 0); + } + } +} diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/Space.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/Space.java deleted file mode 100644 index ea076b9ed..000000000 --- a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/Space.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.pubnub.api.models.consumer.access_manager.v3; - -public class Space extends PNResource { - - private Space() { - - } - - public static Space id(String spaceId) { - Space space = new Space(); - space.resourceName = spaceId; - return space; - } - - public static Space pattern(String spacePattern) { - Space space = new Space(); - space.resourcePattern = spacePattern; - return space; - } - - @Override - public Space read() { - return super.read(); - } - - @Override - public Space delete() { - return super.delete(); - } - - @Override - public Space write() { - return super.write(); - } - - @Override - public Space manage() { - return super.manage(); - } - - @Override - public Space create() { - return super.create(); - } -} \ No newline at end of file diff --git a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/User.java b/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/User.java deleted file mode 100644 index 998071b0c..000000000 --- a/src/main/java/com/pubnub/api/models/consumer/access_manager/v3/User.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.pubnub.api.models.consumer.access_manager.v3; - -public class User extends PNResource { - - private User() { - - } - - public static User id(String userId) { - User user = new User(); - user.resourceName = userId; - return user; - } - - public static User pattern(String userPattern) { - User user = new User(); - user.resourcePattern = userPattern; - return user; - } - - @Override - public User read() { - return super.read(); - } - - @Override - public User delete() { - return super.delete(); - } - - @Override - public User write() { - return super.write(); - } - - @Override - public User manage() { - return super.manage(); - } - - @Override - public User create() { - return super.create(); - } -} diff --git a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembership.java b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembership.java index 8b02370fa..35ca8af8d 100644 --- a/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembership.java +++ b/src/main/java/com/pubnub/api/models/consumer/objects_api/membership/PNMembership.java @@ -3,13 +3,12 @@ import com.google.gson.annotations.JsonAdapter; import com.pubnub.api.models.consumer.objects_api.channel.PNChannelMetadata; import com.pubnub.api.models.consumer.objects_api.util.CustomPayloadJsonInterceptor; +import com.pubnub.api.utils.UnwrapSingleField; import lombok.*; import lombok.experimental.Accessors; -@Getter +@Data @Accessors(chain = true) -@EqualsAndHashCode -@ToString @RequiredArgsConstructor public class PNMembership { @NonNull @@ -18,6 +17,9 @@ public class PNMembership { @JsonAdapter(CustomPayloadJsonInterceptor.class) protected Object custom; + @JsonAdapter(UnwrapSingleField.class) + protected String uuid; + protected String updated; protected String eTag; } diff --git a/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.java b/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.java index 13e76b9bc..c5bb3d5c6 100644 --- a/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.java +++ b/src/main/java/com/pubnub/api/models/consumer/pubsub/objects/ObjectResult.java @@ -9,6 +9,7 @@ public abstract class ObjectResult extends BasePubSubResult { @Getter protected String event; + @Getter protected T data; public ObjectResult(BasePubSubResult result, String event, T data) { diff --git a/src/main/java/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.java b/src/main/java/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.java index 9197aca1a..9c8acc9f7 100644 --- a/src/main/java/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.java +++ b/src/main/java/com/pubnub/api/models/server/access_manager/v3/GrantTokenRequestBody.java @@ -1,121 +1,90 @@ package com.pubnub.api.models.server.access_manager.v3; -import com.google.gson.JsonObject; -import com.pubnub.api.PubNub; import com.pubnub.api.PubNubException; import com.pubnub.api.builder.PubNubErrorBuilder; -import com.pubnub.api.models.consumer.access_manager.v3.Channel; -import com.pubnub.api.models.consumer.access_manager.v3.Group; +import com.pubnub.api.models.TokenBitmask; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant; import com.pubnub.api.models.consumer.access_manager.v3.PNResource; -import com.pubnub.api.models.consumer.access_manager.v3.Space; -import com.pubnub.api.models.consumer.access_manager.v3.User; import lombok.Builder; -import lombok.extern.java.Log; +import lombok.Data; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; -@Log -@Builder +@Data public class GrantTokenRequestBody { + private final Integer ttl; + private final GrantTokenPermissions permissions; + + @Data + private static class GrantTokenPermissions { + private final GrantTokenPermission resources; + private final GrantTokenPermission patterns; + private final Object meta; + } - private static final int READ = 1; - private static final int WRITE = 2; - private static final int MANAGE = 4; - private static final int DELETE = 8; - private static final int CREATE = 16; - - private Integer ttl; - private List channels; - private List groups; - private List users; - private List spaces; - private Object meta; - private PubNub pubNub; - - public JsonObject assemble() throws PubNubException { - JsonObject payload = new JsonObject(); - - payload.addProperty("ttl", this.ttl); - - JsonObject permissions = new JsonObject(); - - JsonObject resources = new JsonObject(); - JsonObject patterns = new JsonObject(); - - parse(channels, "channels", resources, patterns); - parse(groups, "groups", resources, patterns); - parse(users, "users", resources, patterns); - parse(spaces, "spaces", resources, patterns); + @Data + public static class GrantTokenPermission { + private final Map channels; + private final Map groups; + private final Map spaces = Collections.emptyMap(); + private final Map users = Collections.emptyMap(); + } - permissions.add("resources", resources); - permissions.add("patterns", patterns); + @Builder + public static GrantTokenRequestBody of(Integer ttl, + List channels, + List groups, + Object meta) throws PubNubException { + + GrantTokenPermission resources = new GrantTokenPermission(getResources(channels), + getResources(groups)); + GrantTokenPermission patterns = new GrantTokenPermission(getPatterns(channels), + getPatterns(groups)); + GrantTokenPermissions permissions = new GrantTokenPermissions(resources, patterns, meta == null ? Collections.emptyMap() : meta); + return new GrantTokenRequestBody(ttl, permissions); + } - if (this.meta != null) { - try { - permissions.add("meta", pubNub.getMapper().convertValue(this.meta, JsonObject.class)); - } catch (PubNubException e) { - throw PubNubException.builder() - .pubnubError(PubNubErrorBuilder.PNERROBJ_INVALID_META) - .cause(e) - .build(); + private static > Map getResources(List resources) throws PubNubException { + final Map result = new HashMap<>(); + for (T resource : resources) { + if (!resource.isPatternResource()) { + result.put(resource.getId(), calculateBitmask(resource)); } - } else { - permissions.add("meta", new JsonObject()); } - - payload.add("permissions", permissions); - - return payload; + return result; } - private void parse(List list, String resourceSetName, JsonObject resources, - JsonObject patterns) throws PubNubException { - if (list != null) { - for (PNResource pnResource : list) { - JsonObject resourceObject = new JsonObject(); - - JsonObject determinedObject; - - if (pnResource.isPatternResource()) { - determinedObject = patterns; - } else { - determinedObject = resources; - } - - if (determinedObject.has(resourceSetName)) { - determinedObject.get(resourceSetName).getAsJsonObject() - .addProperty(pnResource.getId(), calculateBitmask(pnResource)); - } else { - resourceObject.addProperty(pnResource.getId(), calculateBitmask(pnResource)); - determinedObject.add(resourceSetName, resourceObject); - } + private static > Map getPatterns(List resources) throws PubNubException { + final Map result = new HashMap<>(); + for (T resource : resources) { + if (resource.isPatternResource()) { + result.put(resource.getId(), calculateBitmask(resource)); } } + return result; - if (!resources.has(resourceSetName)) { - resources.add(resourceSetName, new JsonObject()); - } - if (!patterns.has(resourceSetName)) { - patterns.add(resourceSetName, new JsonObject()); - } } - private int calculateBitmask(PNResource resource) throws PubNubException { + private static int calculateBitmask(PNResource resource) throws PubNubException { int sum = 0; if (resource.isRead()) { - sum += READ; + sum += TokenBitmask.READ; } if (resource.isWrite()) { - sum += WRITE; + sum += TokenBitmask.WRITE; } if (resource.isManage()) { - sum += MANAGE; + sum += TokenBitmask.MANAGE; } if (resource.isDelete()) { - sum += DELETE; + sum += TokenBitmask.DELETE; } if (resource.isCreate()) { - sum += CREATE; + sum += TokenBitmask.CREATE; } if (sum == 0) { throw PubNubException.builder() diff --git a/src/main/java/com/pubnub/api/utils/UnwrapSingleField.java b/src/main/java/com/pubnub/api/utils/UnwrapSingleField.java new file mode 100644 index 000000000..5f26177b5 --- /dev/null +++ b/src/main/java/com/pubnub/api/utils/UnwrapSingleField.java @@ -0,0 +1,22 @@ +package com.pubnub.api.utils; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; + +import java.lang.reflect.Type; + +public class UnwrapSingleField implements JsonDeserializer { + @Override + public T deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException { + final JsonObject jsonObject = json.getAsJsonObject(); + if (jsonObject.keySet().size() != 1) { + throw new IllegalStateException("Couldn't unwrap field for object containing more than 1 field. Actual number of fields: " + jsonObject.keySet().size()); + } + final String key = jsonObject.keySet().toArray(new String[]{})[0]; + final JsonElement element = jsonObject.get(key); + return context.deserialize(element, typeOfT); + } +} diff --git a/src/test/java/com/pubnub/api/PubNubTest.java b/src/test/java/com/pubnub/api/PubNubTest.java index 933c7e465..fefb2830b 100644 --- a/src/test/java/com/pubnub/api/PubNubTest.java +++ b/src/test/java/com/pubnub/api/PubNubTest.java @@ -99,7 +99,7 @@ public void getVersionAndTimeStamp() { pubnub = new PubNub(pnConfiguration); String version = pubnub.getVersion(); int timeStamp = pubnub.getTimestamp(); - Assert.assertEquals("5.0.0", version); + Assert.assertEquals("5.1.0", version); Assert.assertTrue(timeStamp > 0); } diff --git a/src/test/java/com/pubnub/api/endpoints/access/GrantTokenEndpointTest.java b/src/test/java/com/pubnub/api/endpoints/access/GrantTokenEndpointTest.java new file mode 100644 index 000000000..8eb8db550 --- /dev/null +++ b/src/test/java/com/pubnub/api/endpoints/access/GrantTokenEndpointTest.java @@ -0,0 +1,100 @@ +package com.pubnub.api.endpoints.access; + +import com.pubnub.api.PNConfiguration; +import com.pubnub.api.PubNub; +import com.pubnub.api.PubNubException; +import com.pubnub.api.endpoints.TestHarness; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGrant; +import com.pubnub.api.models.consumer.access_manager.v3.ChannelGroupGrant; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Collections; + +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_PERMISSION_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_RESOURCES_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_SECRET_KEY_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_SUBSCRIBE_KEY_MISSING; +import static com.pubnub.api.builder.PubNubErrorBuilder.PNERR_TTL_MISSING; +import static org.junit.Assert.assertEquals; + +public class GrantTokenEndpointTest extends TestHarness { + + private final PubNub pubnub = this.createPubNubInstance(); + + @Before + public void beforeEach() throws IOException { + pubnub.getConfiguration().setSecretKey("secretKey").setIncludeInstanceIdentifier(true); + } + + @Test + public void validate_NoResourceSet() { + try { + pubnub.grantToken() + .ttl(1) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_RESOURCES_MISSING, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void validate_NoTTLSet() { + try { + pubnub.grantToken() + .channels(Collections.singletonList(ChannelGrant.name("test").read())) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_TTL_MISSING, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void validate_ChannelResourceMissingAnyPermissions() { + try { + pubnub.grantToken() + .ttl(1) + .channels(Collections.singletonList(ChannelGrant.name("test"))) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_PERMISSION_MISSING, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void validate_ChannelGroupPatterMissingAnyPermissions() { + try { + pubnub.grantToken() + .ttl(1) + .channelGroups(Collections.singletonList(ChannelGroupGrant.id("test"))) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_PERMISSION_MISSING, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void validate_SecretKeyMissing() { + try { + createPubNubInstance().grantToken() + .ttl(1) + .channelGroups(Collections.singletonList(ChannelGroupGrant.id("test").read())) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_SECRET_KEY_MISSING, e.getPubnubError().getErrorCode()); + } + } + + @Test + public void validate_SubscribeKeyMissing() { + try { + new PubNub(new PNConfiguration().setSecretKey("secret")).grantToken() + .ttl(1) + .channelGroups(Collections.singletonList(ChannelGroupGrant.id("test").read())) + .sync(); + } catch (PubNubException e) { + assertEquals(PNERR_SUBSCRIBE_KEY_MISSING, e.getPubnubError().getErrorCode()); + } + } +} diff --git a/src/test/java/com/pubnub/api/managers/token_manager/TokenParserTest.java b/src/test/java/com/pubnub/api/managers/token_manager/TokenParserTest.java new file mode 100644 index 000000000..526a47b41 --- /dev/null +++ b/src/test/java/com/pubnub/api/managers/token_manager/TokenParserTest.java @@ -0,0 +1,33 @@ +package com.pubnub.api.managers.token_manager; + +import com.pubnub.api.PubNubException; +import com.pubnub.api.models.consumer.access_manager.v3.PNToken; +import org.junit.Test; + +import java.util.Collections; + +import static java.util.Collections.emptyMap; +import static org.junit.Assert.assertEquals; + +public class TokenParserTest { + + private final TokenParser tokenParser = new TokenParser(); + + @Test + public void stringTokenIsProperlyDeserialized() throws PubNubException { + //given + String stringToken = "p0F2AkF0Gl043rhDdHRsCkNyZXOkRGNoYW6hZnNlY3JldAFDZ3JwoEN1c3KgQ3NwY6BDcGF0pERjaGFuoENncnCgQ3VzcqBDc3BjoERtZXRhoENzaWdYIGOAeTyWGJI-blahPGD9TuKlaW1YQgiB4uR_edmfq-61"; + PNToken expectedToken = new PNToken(2, 1564008120, 10, + new PNToken.PNTokenResources( + Collections.singletonMap("secret", + new PNToken.PNResourcePermissions(false, true, false, false, false)), + emptyMap()), + new PNToken.PNTokenResources(emptyMap(), emptyMap())); + + //when + PNToken token = tokenParser.unwrapToken(stringToken); + + //then + assertEquals(expectedToken, token); + } +}