pubnub 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71b414f7fd632778c8f35096a7dc3c8fd3c730b827ad2335df6067fd06fc24ac
4
- data.tar.gz: 0d58ad506e63c4979b1c105a14062557ec5a84580ead37882896b6f8636a53e3
3
+ metadata.gz: 376093de161830660661ecd60b0eb9a7f38e06096efad64a5588d5b00aaaa554
4
+ data.tar.gz: 8409b67c533fc6d079d230eb0bea64eddbbdc40ddb48ff95bb48be116aa5be16
5
5
  SHA512:
6
- metadata.gz: eb9cf2ed6c70458c3fc4d5e784273013f362b95d5328e8c98340df39dbb2290f11376e30bce891ef47d5de576a2b9307a3cfde0e76b8bf49558e742353363c83
7
- data.tar.gz: 33dcaca1439a6729e76a41eb2fb12a7ac58dc8acabace463afd035b3112aa5b8766f5a0cdeb5a095ed5d80a46408de0c2d310955506fc5efed7eb7bd3b4609f3
6
+ metadata.gz: 6df8f51d9465cfdf3f9c4cc14ae0afb83e43cc4d83d20c78ae8e9fb7fb9eed99f83f9879e29a334dfbae52d8a39a084c8903b433c4248ae49d51f9cfe58fb766
7
+ data.tar.gz: e6e014c7736f84bcab8eab9b464f1622051372c99097750b5745942e0a153b84dd3fb4a9469516194238d84ae31d03444a9501fc45d2ab61d2573ff40a507377
data/.pubnub.yml CHANGED
@@ -1,6 +1,20 @@
1
1
  ---
2
- version: "6.1.0"
2
+ version: "6.2.0"
3
3
  changelog:
4
+ - date: 2026-10-21
5
+ version: v6.2.0
6
+ changes:
7
+ - type: feature
8
+ text: "Add `categories` support to `grant` and `grant_token` for granting enumeration (`get`) permission on all `channels` and `uuids` metadata."
9
+ - date: 2026-07-21
10
+ version: v6.1.1
11
+ changes:
12
+ - type: bug
13
+ text: "`Pubnub::Cbor` now enforces a recursion-depth cap, a per-container length cap, and a total input size cap. Malformed or attacker-crafted tokens reaching `Pubnub::Client#parse_token` raise `Pubnub::Cbor::DecodeError` instead of hanging the caller or triggering `SystemStackError`."
14
+ - type: bug
15
+ text: "`Pubnub::Constants::DEFAULT_SSL` flipped from `false` to `true`. Integrators must now opt out explicitly to send traffic in cleartext."
16
+ - type: bug
17
+ text: "The Requested URI debug entry in `Pubnub::Event#uri` now masks the `signature` and `pnsig` query parameter values."
4
18
  - date: 2026-06-15
5
19
  version: v6.1.0
6
20
  changes:
@@ -647,7 +661,7 @@ sdks:
647
661
  - x86-64
648
662
  - distribution-type: package
649
663
  distribution-repository: RubyGems
650
- package-name: pubnub-6.1.0.gem
664
+ package-name: pubnub-6.2.0.gem
651
665
  location: https://rubygems.org/gems/pubnub
652
666
  requires:
653
667
  - name: addressable
@@ -752,8 +766,8 @@ sdks:
752
766
  - x86-64
753
767
  - distribution-type: library
754
768
  distribution-repository: GitHub release
755
- package-name: pubnub-6.1.0.gem
756
- location: https://github.com/pubnub/ruby/releases/download/v6.1.0/pubnub-6.1.0.gem
769
+ package-name: pubnub-6.2.0.gem
770
+ location: https://github.com/pubnub/ruby/releases/download/v6.2.0/pubnub-6.2.0.gem
757
771
  requires:
758
772
  - name: addressable
759
773
  min-version: 2.0.0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## v6.2.0
2
+ October 21 2026
3
+
4
+ #### Added
5
+ - Add `categories` support to `grant` and `grant_token` for granting enumeration (`get`) permission on all `channels` and `uuids` metadata.
6
+
7
+ ## v6.1.1
8
+ July 21 2026
9
+
10
+ #### Fixed
11
+ - `Pubnub::Cbor` now enforces a recursion-depth cap, a per-container length cap, and a total input size cap. Malformed or attacker-crafted tokens reaching `Pubnub::Client#parse_token` raise `Pubnub::Cbor::DecodeError` instead of hanging the caller or triggering `SystemStackError`.
12
+ - `Pubnub::Constants::DEFAULT_SSL` flipped from `false` to `true`. Integrators must now opt out explicitly to send traffic in cleartext.
13
+ - The Requested URI debug entry in `Pubnub::Event#uri` now masks the `signature` and `pnsig` query parameter values, per the SDK log practices ADR. `auth` and `token` remain visible at DEBUG since they are permitted URL parameters.
14
+
1
15
  ## v6.1.0
2
16
  June 15 2026
3
17
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pubnub (6.1.0)
4
+ pubnub (6.2.0)
5
5
  addressable (>= 2.0.0)
6
6
  base64
7
7
  concurrent-ruby (~> 1.3.4)
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ <img width="1920" height="600" alt="ruby-header" src="https://github.com/user-attachments/assets/ddf7134b-804c-48d6-aa74-cc67ee5aab94" />
2
+
1
3
  # PubNub Ruby SDK
2
4
 
3
5
  ![Tests Status](https://github.com/pubnub/ruby/actions/workflows/run-tests.yml/badge.svg)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 6.1.0
1
+ 6.2.0
@@ -12,7 +12,7 @@ Before do |scenario|
12
12
  expect(ENV['SERVER_PORT']).not_to be_nil
13
13
  @pn_configuration = {
14
14
  origin: ENV['SERVER_HOST'] + ":" + ENV['SERVER_PORT'],
15
- isSecure: false,
15
+ ssl: false,
16
16
  }
17
17
  }
18
18
 
data/lib/pubnub/cbor.rb CHANGED
@@ -1,5 +1,10 @@
1
1
  module Pubnub
2
2
  class Cbor
3
+ class DecodeError < StandardError; end
4
+
5
+ MAX_DEPTH = 32
6
+ MAX_CONTAINER_LENGTH = 65_536
7
+ MAX_INPUT_SIZE = 1_048_576
3
8
 
4
9
  private
5
10
 
@@ -43,9 +48,14 @@ module Pubnub
43
48
  end
44
49
  end
45
50
 
51
+ def take_bytes(data, count)
52
+ raise DecodeError, "Truncated CBOR input" if data.size < count
53
+ data.shift(count)
54
+ end
55
+
46
56
  def decode_integer(data, additional)
47
57
  if ADDITIONAL_LENGTH_BYTES.member?(additional)
48
- bytearray_to_i(data.shift(ADDITIONAL_LENGTH_BYTES[additional]))
58
+ bytearray_to_i(take_bytes(data, ADDITIONAL_LENGTH_BYTES[additional]))
49
59
  else
50
60
  additional
51
61
  end
@@ -55,7 +65,10 @@ module Pubnub
55
65
  if additional <= 23
56
66
  additional
57
67
  else
58
- bytes = bytearray_to_i(data.shift(ADDITIONAL_LENGTH_BYTES[additional]))
68
+ raise DecodeError, "Invalid float additional value" unless ADDITIONAL_LENGTH_BYTES.member?(additional)
69
+
70
+ bytes = bytearray_to_i(take_bytes(data, ADDITIONAL_LENGTH_BYTES[additional]))
71
+
59
72
  case (additional)
60
73
  when ADDITIONAL_LENGTH_1B
61
74
  bytes
@@ -98,44 +111,57 @@ module Pubnub
98
111
  result = []
99
112
 
100
113
  loop do
114
+ raise DecodeError, "Truncated CBOR input" if data.empty?
101
115
  byte = data.shift
102
116
  break if byte == INDEFINITE_BREAK
103
117
  result.append(byte)
104
- break if data.empty?
105
118
  end
106
119
  result
107
120
  end
108
121
 
109
- def compute_length(data, additional)
110
- if ADDITIONAL_LENGTH_BYTES.member?(additional)
111
- bytearray_to_i(data.shift(ADDITIONAL_LENGTH_BYTES[additional]))
112
- else
113
- additional
122
+ def compute_container_length(data, additional, min_element_bytes)
123
+ length = if ADDITIONAL_LENGTH_BYTES.member?(additional)
124
+ bytearray_to_i(take_bytes(data, ADDITIONAL_LENGTH_BYTES[additional]))
125
+ else
126
+ additional
127
+ end
128
+
129
+ if length > MAX_CONTAINER_LENGTH
130
+ raise DecodeError, "CBOR container length #{length} exceeds maximum #{MAX_CONTAINER_LENGTH}"
131
+ end
132
+
133
+ if length * min_element_bytes > data.size
134
+ raise DecodeError, "CBOR container length #{length} exceeds remaining input"
114
135
  end
136
+
137
+ length
115
138
  end
116
139
 
117
140
  def decode_string(data, additional)
118
141
  if additional == ADDITIONAL_TYPE_INDEFINITE
119
142
  indefinite_data(data).pack('C*').force_encoding('UTF-8')
120
143
  else
121
- length = compute_length(data, additional)
122
- data.shift(length).pack('C*').force_encoding('UTF-8')
144
+ length = compute_container_length(data, additional, 1)
145
+ take_bytes(data, length).pack('C*').force_encoding('UTF-8')
123
146
  end
124
147
  end
125
148
 
126
- def decode_map(data, additional)
127
- length = compute_length(data, additional)
149
+ def decode_map(data, additional, depth)
150
+ length = compute_container_length(data, additional, 2)
128
151
  result = Hash.new
129
- (1..length).each { result.store(parse_data(data), parse_data(data)) }
152
+ length.times { result.store(parse_data(data, depth), parse_data(data, depth)) }
130
153
  result
131
154
  end
132
155
 
133
- def decode_array(data, additional)
134
- length = compute_length(data, additional)
135
- (1..length).map { parse_data(data) }
156
+ def decode_array(data, additional, depth)
157
+ length = compute_container_length(data, additional, 1)
158
+ Array.new(length) { parse_data(data, depth) }
136
159
  end
137
160
 
138
- def parse_data(data)
161
+ def parse_data(data, depth = 0)
162
+ raise DecodeError, "CBOR nesting depth exceeds #{MAX_DEPTH}" if depth > MAX_DEPTH
163
+ raise DecodeError, "Truncated CBOR input" if data.empty?
164
+
139
165
  byte = data.shift
140
166
 
141
167
  case (byte)
@@ -163,9 +189,9 @@ module Pubnub
163
189
  when TYPE_TEXT_STRING
164
190
  decode_string(data, additional)
165
191
  when TYPE_ARRAY
166
- decode_array(data, additional)
192
+ decode_array(data, additional, depth + 1)
167
193
  when TYPE_HASHMAP
168
- decode_map(data, additional)
194
+ decode_map(data, additional, depth + 1)
169
195
  else
170
196
  nil
171
197
  end
@@ -175,7 +201,11 @@ module Pubnub
175
201
  public
176
202
 
177
203
  def decode(value)
178
- parse_data(value)
204
+ if value.size > MAX_INPUT_SIZE
205
+ raise DecodeError, "CBOR input size #{value.size} exceeds maximum #{MAX_INPUT_SIZE}"
206
+ end
207
+
208
+ parse_data(value.dup)
179
209
  end
180
210
  end
181
211
 
@@ -20,7 +20,7 @@ module Pubnub
20
20
  DEFAULT_TTL = 1440
21
21
  DEFAULT_REGION = '0'.freeze
22
22
  DEFAULT_USE_RANDOM_IV = true
23
- DEFAULT_SSL = false
23
+ DEFAULT_SSL = true
24
24
  REQUEST_MESSAGE_COUNT_THRESHOLD = 0
25
25
  MAXIMUM_HERE_NOW_COUNT = 1000
26
26
 
data/lib/pubnub/event.rb CHANGED
@@ -88,7 +88,7 @@ module Pubnub
88
88
  uri += path
89
89
  uri += '?' + Formatter.params_hash_to_url_params(parameters)
90
90
  uri += "&signature=#{sa_signature}" if sa_signature
91
- Pubnub.logger.debug('Pubnub::Event') { "Requested URI: #{uri}" }
91
+ Pubnub.logger.debug('Pubnub::Event') { "Requested URI: #{uri.gsub(/([?&](?:signature|pnsig)=)[^&#]*/i, '\1***')}" }
92
92
  URI uri
93
93
  end
94
94
 
@@ -7,6 +7,7 @@ module Pubnub
7
7
 
8
8
  def initialize(options, app)
9
9
  @event = current_operation
10
+ @categories = format_categories(options[:categories])
10
11
  super
11
12
  @uuids = Formatter::format_uuid(options[:uuids]) unless options[:uuids].nil?
12
13
  @ttl ||= Pubnub::Constants::DEFAULT_TTL
@@ -18,10 +19,22 @@ module Pubnub
18
19
  Pubnub::Constants::OPERATION_GRANT
19
20
  end
20
21
 
22
+ def format_categories(categories)
23
+ formatted = case categories
24
+ when NilClass then return nil
25
+ when Array then categories.map { |category| category.to_s.strip }
26
+ when String, Symbol then categories.to_s.split(',').map(&:strip)
27
+ else return categories
28
+ end
29
+
30
+ formatted.empty? ? nil : formatted
31
+ end
32
+
21
33
  def parameters(signature = false)
22
- write = [0, '0', false].include?(@write) ? 0 : 1
23
- read = [0, '0', false].include?(@read) ? 0 : 1
24
- manage = [0, '0', false].include?(@manage) ? 0 : 1 # unless @group.blank?
34
+ category_grant = !@categories.nil?
35
+ write = (category_grant && @write.nil?) || [0, '0', false].include?(@write) ? 0 : 1
36
+ read = (category_grant && @read.nil?) || [0, '0', false].include?(@read) ? 0 : 1
37
+ manage = (category_grant && @manage.nil?) || [0, '0', false].include?(@manage) ? 0 : 1 # unless @group.blank?
25
38
  delete = [0, '0', false].include?(@delete) ? 0 : 1 unless @delete.nil?
26
39
  get = [0, '0', false].include?(@get) ? 0 : 1 unless @get.nil?
27
40
  update = [0, '0', false].include?(@update) ? 0 : 1 unless @update.nil?
@@ -35,7 +48,8 @@ module Pubnub
35
48
  g: get,
36
49
  u: update,
37
50
  j: join,
38
- ttl: @ttl }.delete_if { |_k, v| v.nil? }.merge(super(signature))
51
+ ttl: @ttl,
52
+ category: (@categories.join(',') unless @categories.nil?) }.delete_if { |_k, v| v.nil? }.merge(super(signature))
39
53
  end
40
54
 
41
55
  def path
@@ -12,6 +12,7 @@ module Pubnub
12
12
  options[:channel_groups] = options[:channel_groups] || {}
13
13
  @spaces_permissions = options[:spaces_permissions] || {}
14
14
  @users_permissions = options[:users_permissions] || {}
15
+ @categories = options[:categories] || {}
15
16
  super
16
17
  end
17
18
 
@@ -31,7 +32,8 @@ module Pubnub
31
32
  meta: @meta,
32
33
  uuid: uuid,
33
34
  resources: prepare_permissions(:resource, @channels, @channel_groups, @uuids, @spaces_permissions, @users_permissions),
34
- patterns: prepare_permissions(:pattern, @channels, @channel_groups, @uuids, @spaces_permissions, @users_permissions)
35
+ patterns: prepare_permissions(:pattern, @channels, @channel_groups, @uuids, @spaces_permissions, @users_permissions),
36
+ categories: prepare_categories(@categories)
35
37
  }.select { |_, v| v }
36
38
  }
37
39
  body = Formatter.format_message(raw_body, nil, false)
@@ -70,6 +72,12 @@ module Pubnub
70
72
  .to_h { |k, v| [k, v.calculate_bitmask] }
71
73
  end
72
74
 
75
+ def prepare_categories(categories)
76
+ return nil if categories.empty?
77
+
78
+ categories.to_h { |k, v| [k.to_s, v.calculate_bitmask] }
79
+ end
80
+
73
81
  def valid_envelope(parsed_response, req_res_objects)
74
82
  Pubnub::Envelope.new(
75
83
  event: @event,
@@ -146,6 +154,13 @@ module Pubnub
146
154
  join: join
147
155
  )
148
156
  end
157
+
158
+ def cat(get: false)
159
+ Permissions.new(
160
+ type: :category,
161
+ get: get
162
+ )
163
+ end
149
164
  end
150
165
 
151
166
  def initialize(options)
@@ -12,6 +12,7 @@ module Pubnub
12
12
  validate_keys!
13
13
  validate_ttl!
14
14
  validate_uuids!
15
+ validate_categories!
15
16
  end
16
17
 
17
18
  private
@@ -37,6 +38,27 @@ module Pubnub
37
38
  )
38
39
  end
39
40
 
41
+ def validate_categories!
42
+ return if @categories.nil?
43
+
44
+ raise(
45
+ ArgumentError.new(object: self, message: ':categories has to be kind of String, Symbol or Array for grant event.'),
46
+ ':categories has to be kind of String, Symbol or Array for grant event.'
47
+ ) unless @categories.is_a?(Array)
48
+
49
+ raise(
50
+ ArgumentError.new(object: self, message: ':categories supports only channels and uuids for grant event.'),
51
+ ':categories supports only channels and uuids for grant event.'
52
+ ) unless (@categories - %w[channels uuids]).empty?
53
+
54
+ # Use the per-call option: PAM#initialize resets @auth_key to options[:auth_key], so a client-level
55
+ # auth_key is never sent as the grant target `auth`.
56
+ raise(
57
+ ArgumentError.new(object: self, message: ':auth_key is required for grant event on categories.'),
58
+ ':auth_key is required for grant event on categories.'
59
+ ) if @given_options[:auth_key].nil? || @given_options[:auth_key].to_s.empty?
60
+ end
61
+
40
62
  def validate_uuids!
41
63
  return if @uuids.nil?
42
64
 
@@ -12,6 +12,7 @@ module Pubnub
12
12
  validate_permissions!(@uuids, ":uuids")
13
13
  validate_permissions!(@channels, ":channels")
14
14
  validate_permissions!(@channel_groups, ":uuids")
15
+ validate_categories!
15
16
  validate_objects_entities_separation!
16
17
  end
17
18
 
@@ -47,6 +48,27 @@ module Pubnub
47
48
  ) unless arg.is_a?(Hash)
48
49
  end
49
50
 
51
+ def validate_categories!
52
+ return if @categories.nil?
53
+
54
+ raise(
55
+ ArgumentError.new(object: self, message: ':categories has to be kind of Hash for grant token event.'),
56
+ ':categories has to be kind of Hash for grant token event.'
57
+ ) unless @categories.is_a?(Hash)
58
+
59
+ @categories.each do |key, value|
60
+ raise(
61
+ ArgumentError.new(object: self, message: ':categories supports only :channels and :uuids keys for grant token event.'),
62
+ ':categories supports only :channels and :uuids keys for grant token event.'
63
+ ) unless %w[channels uuids].include?(key.to_s)
64
+
65
+ raise(
66
+ ArgumentError.new(object: self, message: ':categories values have to be created with Pubnub::Permissions.cat for grant token event.'),
67
+ ':categories values have to be created with Pubnub::Permissions.cat for grant token event.'
68
+ ) unless value.is_a?(Pubnub::Permissions) && value.type == :category
69
+ end
70
+ end
71
+
50
72
  def validate_objects_entities_separation!
51
73
  entities_set = !@spaces_permissions.empty? ||
52
74
  !@users_permissions.empty?
@@ -1,4 +1,4 @@
1
1
  # Toplevel Pubnub module.
2
2
  module Pubnub
3
- VERSION = '6.1.0'.freeze
3
+ VERSION = '6.2.0'.freeze
4
4
  end
data/pubnub.gemspec CHANGED
@@ -11,6 +11,10 @@ Gem::Specification.new do |spec|
11
11
  spec.description = 'Ruby anywhere in the world in 250ms with PubNub!'
12
12
  spec.homepage = 'https://github.com/pubnub/ruby'
13
13
  spec.licenses = ['LicenseRef-LICENSE.txt']
14
+ spec.metadata = {
15
+ "changelog_uri" => "https://www.pubnub.com/docs/sdks/ruby/changelog",
16
+ "documentation_uri" => "https://www.pubnub.com/docs/sdks/ruby",
17
+ }
14
18
 
15
19
  spec.files = `git ls-files -z`.split("\x0").grep_v(/^(test|spec|fixtures)/)
16
20
  spec.executables = spec.files.grep(%r{^bin\/}) { |f| File.basename(f) }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pubnub
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.0
4
+ version: 6.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - PubNub
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-15 00:00:00.000000000 Z
11
+ date: 2026-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -332,7 +332,9 @@ files:
332
332
  homepage: https://github.com/pubnub/ruby
333
333
  licenses:
334
334
  - LicenseRef-LICENSE.txt
335
- metadata: {}
335
+ metadata:
336
+ changelog_uri: https://www.pubnub.com/docs/sdks/ruby/changelog
337
+ documentation_uri: https://www.pubnub.com/docs/sdks/ruby
336
338
  post_install_message:
337
339
  rdoc_options: []
338
340
  require_paths: