activity_notification 2.7.0 → 2.7.1

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: bafe2d6977f08838087612f2d58947783a2e6add85633d85abe024cbe3a8041b
4
- data.tar.gz: '095b581d3e475f27bfd186265083ea42f953b9b7c992c963fbb5fdd40cedd96e'
3
+ metadata.gz: 56122565c61de552c48285f449429a7d64616f5c13f04f0e023a68b33273db6f
4
+ data.tar.gz: 6462762a8f2d8b7b68b82be0632c1dd3d89379aa7fe904c6f4492053be2633fb
5
5
  SHA512:
6
- metadata.gz: f6c687e0c9df39d2950bd2f2b2f2da4e10b95404315e09a9c91b8002cbaf98140a33877700c5c5b8a36b46f3387658caba039c722a91664b52d56ec29eac47d8
7
- data.tar.gz: a9ebe7817f6c78fceb53e5ce0adee2b20e7b964e97c5ad812933a2d097d42f3d1617a29d26b0b07225309d56ebb2168fdcf64bd26c4d3478523ed6d1354de0ff
6
+ metadata.gz: fd7b507f00663ebcf26694a0c8c98a0051f51f7bf35e67687a77e668fb61f500f1cf84e2fa433ecbc39bf59357449fcd0cfd0303b107b7e16ea9838d39e3848f
7
+ data.tar.gz: 12cd4b4e589aa66e4ccb6108beb5624d5b13f3d327234491520551c5d9134afabe718367ee3d5193a92e9a1fd2491c9bf5d59250e8e2bb8588c3bcdfaf8565b7
@@ -4,6 +4,8 @@ class ActivityNotification::NotificationApiChannel < ActivityNotification::Notif
4
4
  # ActionCable::Channel::Base#subscribed
5
5
  # @see https://api.rubyonrails.org/classes/ActionCable/Channel/Base.html#method-i-subscribed
6
6
  def subscribed
7
+ # before_subscribe callbacks do not halt the chain on reject
8
+ return if subscription_rejected?
7
9
  stream_from "#{ActivityNotification.config.notification_api_channel_prefix}_#{@target.to_class_name}#{ActivityNotification.config.composite_key_delimiter}#{@target.id}"
8
10
  rescue
9
11
  reject
@@ -7,6 +7,8 @@ if defined?(ActionCable)
7
7
  # ActionCable::Channel::Base#subscribed
8
8
  # @see https://api.rubyonrails.org/classes/ActionCable/Channel/Base.html#method-i-subscribed
9
9
  def subscribed
10
+ # before_subscribe callbacks do not halt the chain on reject
11
+ return if subscription_rejected?
10
12
  stream_from "#{ActivityNotification.config.notification_channel_prefix}_#{@target.to_class_name}#{ActivityNotification.config.composite_key_delimiter}#{@target.id}"
11
13
  rescue
12
14
  reject
@@ -5,7 +5,6 @@ module ActivityNotification
5
5
  include Swagger::NotificationsApi
6
6
  # Include CommonApiController to select target and define common methods
7
7
  include CommonApiController
8
- protect_from_forgery except: [:open_all]
9
8
  rescue_from ActivityNotification::NotifiableNotFoundError, with: :render_notifiable_not_found
10
9
 
11
10
  # Returns notification index of the target.
@@ -5,7 +5,6 @@ module ActivityNotification
5
5
  include Swagger::SubscriptionsApi
6
6
  # Include CommonApiController to select target and define common methods
7
7
  include CommonApiController
8
- protect_from_forgery except: [:create]
9
8
  before_action :set_subscription, except: [:index, :create, :find, :optional_target_names]
10
9
  before_action ->{ validate_param(:key) }, only: [:find, :optional_target_names]
11
10
 
@@ -40,7 +40,7 @@
40
40
  </style>
41
41
 
42
42
  <% if @target.notification_action_cable_allowed? %>
43
- <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/push.js/1.0.9/push.min.js"></script>
43
+ <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/push.js/1.0.9/push.min.js" integrity="sha512-eiqtDDb4GUVCSqOSOTz/s/eiU4B31GrdSb17aPAA4Lv/Cjc8o+hnDvuNkgXhSI5yHuDvYkuojMaQmrB5JB31XQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
44
44
  <script>
45
45
  App.activity_notification = App.cable.subscriptions.create(
46
46
  {
data/docs/Functions.md CHANGED
@@ -336,6 +336,8 @@ You can also add conditions to filter notifications, like this:
336
336
  User.send_batch_unopened_notification_email(batch_key: 'batch.comment.post', filtered_by_key: 'comment.post', custom_filter: ["created_at >= ?", time.hour.ago])
337
337
  ```
338
338
 
339
+ *custom_filter* accepts an array of a SQL fragment with bind parameters or a hash of conditions. Write it in your application code and never build it from request parameters, since it is passed to the ORM query layer as is. A raw SQL string, an array without bind parameters and MongoDB JavaScript operators (`$where`, `$function` and `$accumulator`) are rejected with `ArgumentError`.
340
+
339
341
  #### Batch sender configuration
340
342
 
341
343
  *activity_notification* uses same sender configuration of real-time email notification as batch email sender.
@@ -1007,7 +1009,7 @@ Then, *activity_notification* will broadcast configured notifications to target
1007
1009
  You can simply create subscriptions for the specified target in your view like this:
1008
1010
 
1009
1011
  ```js
1010
- <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/push.js/1.0.9/push.min.js"></script>
1012
+ <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/push.js/1.0.9/push.min.js" integrity="sha512-eiqtDDb4GUVCSqOSOTz/s/eiU4B31GrdSb17aPAA4Lv/Cjc8o+hnDvuNkgXhSI5yHuDvYkuojMaQmrB5JB31XQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
1011
1013
  <script>
1012
1014
  App.activity_notification = App.cable.subscriptions.create(
1013
1015
  {
@@ -1045,7 +1047,7 @@ You can simply create subscriptions for the specified target in your view like t
1045
1047
  or create subscriptions in your single page application with API channels like this:
1046
1048
 
1047
1049
  ```js
1048
- // Vue.js implementation with actioncable-vue
1050
+ // Vue.js implementation with a small Action Cable plugin (see spec/rails_app/app/javascript/plugins/action-cable.js)
1049
1051
  export default {
1050
1052
  // ...
1051
1053
  mounted () {
@@ -47,24 +47,51 @@ module ActivityNotification
47
47
  end
48
48
  end
49
49
 
50
+ # MongoDB query operators which execute JavaScript on the database server.
51
+ JAVASCRIPT_QUERY_OPERATORS = %w[$where $function $accumulator].freeze
52
+
50
53
  # Validates a :custom_filter value before it is passed to the ORM query layer.
51
- # A String is rejected because ActiveRecord's where treats a String as a raw
52
- # SQL fragment, which allows SQL injection when the value originates from
53
- # untrusted input. The documented and supported forms are an Array of a SQL
54
- # fragment with bind parameters (e.g. ["created_at >= ?", time]) or a Hash of
55
- # conditions, both of which are safe.
54
+ # custom_filter is a trusted input written by the application developer and
55
+ # must not be built from request parameters. This validation rejects the forms
56
+ # that run raw SQL or server-side JavaScript:
57
+ # - A String, since ActiveRecord's where treats it as a raw SQL fragment
58
+ # - An Array without bind parameters (e.g. ["1=1"]), which is also a raw SQL fragment
59
+ # - A Hash containing MongoDB JavaScript operators ($where, $function and $accumulator)
60
+ # The supported forms are an Array of a SQL fragment with bind parameters
61
+ # (e.g. ["created_at >= ?", time]) or a Hash of conditions.
56
62
  # @param [Array, Hash] custom_filter Custom filter value
57
63
  # @return [Array, Hash] The same value when it is safe to use
58
- # @raise [ArgumentError] When custom_filter is a raw SQL String
64
+ # @raise [ArgumentError] When custom_filter is not in the supported forms
59
65
  def self.validate_custom_filter(custom_filter)
60
- if custom_filter.is_a?(String)
66
+ valid =
67
+ case custom_filter
68
+ when Array then custom_filter.size >= 2 && custom_filter.first.is_a?(String)
69
+ when Hash then !contains_javascript_query_operator?(custom_filter)
70
+ else false
71
+ end
72
+ unless valid
61
73
  raise ArgumentError,
62
74
  ":custom_filter must be an array of a SQL fragment and bind parameters " \
63
- "(e.g. [\"created_at >= ?\", time]) or a hash of conditions, not a raw SQL string"
75
+ "(e.g. [\"created_at >= ?\", time]) or a hash of conditions without JavaScript operators"
64
76
  end
65
77
  custom_filter
66
78
  end
67
79
 
80
+ # Returns whether the value contains MongoDB query operators which execute JavaScript.
81
+ # @param [Object] value Value to check
82
+ # @return [Boolean] Whether the value contains JavaScript query operators
83
+ def self.contains_javascript_query_operator?(value)
84
+ case value
85
+ when Hash
86
+ value.any? { |k, v| JAVASCRIPT_QUERY_OPERATORS.include?(k.to_s) || contains_javascript_query_operator?(v) }
87
+ when Array
88
+ value.any? { |v| contains_javascript_query_operator?(v) }
89
+ else
90
+ false
91
+ end
92
+ end
93
+ private_class_method :contains_javascript_query_operator?
94
+
68
95
  # Casts to indifferent hash
69
96
  # @param [ActionController::Parameters, Hash] hash
70
97
  # @return [HashWithIndifferentAccess] Converted indifferent hash
@@ -21,7 +21,7 @@ module ActivityNotification
21
21
  notifications.render self, options
22
22
  elsif notifications.respond_to?(:map)
23
23
  return nil if (notifications.respond_to?(:empty?) ? notifications.empty? : notifications.to_a.empty?)
24
- notifications.map {|notification| notification.render self, options.dup }.join.html_safe
24
+ safe_join(notifications.map {|notification| notification.render self, options.dup })
25
25
  end
26
26
  end
27
27
  alias_method :render_notifications, :render_notification
@@ -1,3 +1,3 @@
1
1
  module ActivityNotification
2
- VERSION = "2.7.0"
2
+ VERSION = "2.7.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activity_notification
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shota Yamazaki