activity_notification 2.6.1 → 2.7.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: fa4bf56cbe8f0a7b85c9d582615af447e15acc1193c94b9e12bdb0a1e2dcbfe9
4
- data.tar.gz: 7e5db0f7477270c39626132b255b7a21d11c290a9f7aee9dc18d1042f2969df5
3
+ metadata.gz: bafe2d6977f08838087612f2d58947783a2e6add85633d85abe024cbe3a8041b
4
+ data.tar.gz: '095b581d3e475f27bfd186265083ea42f953b9b7c992c963fbb5fdd40cedd96e'
5
5
  SHA512:
6
- metadata.gz: 4c9ca166727a66e201f9b1af1588a262223a69331999b272a24c1d7508fcad7a1ffa47e5f9b4bb1eae8d93e1e062cdb84ba4fd0c6bee892dc17e2dce87eb2ee0
7
- data.tar.gz: 927fbf635366912c5d8c1f4f1fe8d48d638978dc37d1c6d8192226d00aea7db163f300ea29722cec1aa32643b3021aa4070aafc9284bf5b5634be00934c2cb42
6
+ metadata.gz: f6c687e0c9df39d2950bd2f2b2f2da4e10b95404315e09a9c91b8002cbaf98140a33877700c5c5b8a36b46f3387658caba039c722a91664b52d56ec29eac47d8
7
+ data.tar.gz: a9ebe7817f6c78fceb53e5ce0adee2b20e7b964e97c5ad812933a2d097d42f3d1617a29d26b0b07225309d56ebb2168fdcf64bd26c4d3478523ed6d1354de0ff
data/README.md CHANGED
@@ -1,12 +1,10 @@
1
1
  # ActivityNotification
2
2
 
3
- [![Build Status](https://github.com/simukappu/activity_notification/actions/workflows/build.yml/badge.svg)](https://github.com/simukappu/activity_notification/actions/workflows/build.yml)
4
- [![Coverage Status](https://coveralls.io/repos/github/simukappu/activity_notification/badge.svg?branch=master)](https://coveralls.io/github/simukappu/activity_notification?branch=master)
5
- [![Dependency](https://img.shields.io/depfu/simukappu/activity_notification.svg)](https://depfu.com/repos/simukappu/activity_notification)
6
- [![Inline docs](http://inch-ci.org/github/simukappu/activity_notification.svg?branch=master)](http://inch-ci.org/github/simukappu/activity_notification)
7
3
  [![Gem Version](https://badge.fury.io/rb/activity_notification.svg)](https://rubygems.org/gems/activity_notification)
8
4
  [![Gem Downloads](https://img.shields.io/gem/dt/activity_notification.svg)](https://rubygems.org/gems/activity_notification)
9
- [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](MIT-LICENSE)
5
+ [![Build Status](https://github.com/simukappu/activity_notification/actions/workflows/build.yml/badge.svg)](https://github.com/simukappu/activity_notification/actions/workflows/build.yml)
6
+ [![Coverage Status](https://coveralls.io/repos/github/simukappu/activity_notification/badge.svg?branch=master)](https://coveralls.io/github/simukappu/activity_notification?branch=master)
7
+ [![License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](MIT-LICENSE)
10
8
 
11
9
  *activity_notification* provides integrated user activity notifications for [Ruby on Rails](https://rubyonrails.org). You can easily use it to configure multiple notification targets and make activity notifications with notifiable models, like adding comments, responding etc.
12
10
 
@@ -135,14 +135,26 @@ module ActivityNotification
135
135
  # Returns options for notification JSON
136
136
  # @api protected
137
137
  def notification_json_options
138
+ include_options =
139
+ if ActivityNotification.config.restrict_api_response_fields
140
+ {
141
+ target: { only: [:id], methods: [:printable_type, :printable_target_name] },
142
+ notifiable: { only: [:id], methods: [:printable_type] },
143
+ group: { only: [:id], methods: [:printable_type, :printable_group_name] },
144
+ notifier: { only: [:id], methods: [:printable_type, :printable_notifier_name] },
145
+ group_members: { only: [:id] }
146
+ }
147
+ else
148
+ {
149
+ target: { methods: [:printable_type, :printable_target_name] },
150
+ notifiable: { methods: [:printable_type] },
151
+ group: { methods: [:printable_type, :printable_group_name] },
152
+ notifier: { methods: [:printable_type, :printable_notifier_name] },
153
+ group_members: {}
154
+ }
155
+ end
138
156
  {
139
- include: {
140
- target: { methods: [:printable_type, :printable_target_name] },
141
- notifiable: { methods: [:printable_type] },
142
- group: { methods: [:printable_type, :printable_group_name] },
143
- notifier: { methods: [:printable_type, :printable_notifier_name] },
144
- group_members: {}
145
- },
157
+ include: include_options,
146
158
  methods: [:notifiable_path, :printable_notifiable_name, :group_member_notifier_count, :group_notification_count]
147
159
  }
148
160
  end
@@ -47,7 +47,7 @@ module ActivityNotification
47
47
  # @option params [String] :reload ('true') Whether notification index will be reloaded
48
48
  # @return [Response] JavaScript view for ajax request or redirects to back as default
49
49
  def open_all
50
- @opened_notifications = @target.open_all_notifications(params)
50
+ @opened_notifications = @target.open_all_notifications(bulk_action_options)
51
51
  return_back_or_ajax
52
52
  end
53
53
 
@@ -70,7 +70,7 @@ module ActivityNotification
70
70
  # @option params [String] :reload ('true') Whether notification index will be reloaded
71
71
  # @return [Response] JavaScript view for ajax request or redirects to back as default
72
72
  def destroy_all
73
- @destroyed_notifications = @target.destroy_all_notifications(params)
73
+ @destroyed_notifications = @target.destroy_all_notifications(bulk_action_options)
74
74
  set_index_options
75
75
  load_index if params[:reload].to_s.to_boolean(true)
76
76
  return_back_or_ajax
@@ -152,6 +152,21 @@ module ActivityNotification
152
152
  validate_target(@notification = Notification.with_target.find(params[:id]))
153
153
  end
154
154
 
155
+ # Builds a permitted options hash for bulk actions (open_all / destroy_all)
156
+ # from request parameters.
157
+ # Only known-safe filter keys are permitted here. Request parameters must
158
+ # never be forwarded to the model layer as-is, because :custom_filter is
159
+ # passed straight to ActiveRecord's where as a raw SQL fragment and would
160
+ # allow SQL injection from untrusted input.
161
+ # @api protected
162
+ # @return [Hash] permitted options for bulk actions
163
+ def bulk_action_options
164
+ params.permit(:filter, :limit, :reverse, :without_grouping, :with_group_members,
165
+ :filtered_by_type, :filtered_by_group_type, :filtered_by_group_id, :filtered_by_key,
166
+ :later_than, :earlier_than, :routing_scope, :devise_default_routes, ids: [])
167
+ .to_h.symbolize_keys
168
+ end
169
+
155
170
  # Sets options to load notification index from request parameters.
156
171
  # @api protected
157
172
  # @return [Hash] options to load notification index
@@ -171,7 +171,11 @@ module ActivityNotification
171
171
  # Returns include option for subscription JSON
172
172
  # @api protected
173
173
  def subscription_json_include_option
174
- [:target].freeze
174
+ if ActivityNotification.config.restrict_api_response_fields
175
+ { target: { only: [:id], methods: [:printable_type, :printable_target_name] } }.freeze
176
+ else
177
+ [:target].freeze
178
+ end
175
179
  end
176
180
 
177
181
  # Returns methods option for subscription JSON
data/docs/Functions.md CHANGED
@@ -709,6 +709,22 @@ When you want to use REST API backend integrated with Devise authentication, see
709
709
 
710
710
  You can see [sample single page application](/spec/rails_app/app/javascript/) using [Vue.js](https://vuejs.org) as a part of example Rails application. This sample application works with *activity_notification* REST API backend.
711
711
 
712
+ #### Securing the REST API
713
+
714
+ The REST API endpoints do not authenticate requests on their own. Authenticating requests and authorizing access to a target's notifications and subscriptions is the responsibility of your application. The recommended way is to use the *:with_devise* option so that only the authenticated resource can access its own records. See [REST API backend with Devise Token Auth](#rest-api-backend-with-devise-token-auth) for the setup. The controllers shipped for *:with_devise* return HTTP 403 for a target that does not belong to the authenticated resource.
715
+
716
+ By default, notification and subscription responses embed the associated records (target, notifiable, notifier, group, group members and subscription target) with all of their database columns. When those associated records are your user model, the response can include columns you may not want to expose to the frontend, such as authentication identifiers. Two options are available to reduce this exposure.
717
+
718
+ The simplest option is to enable *config.restrict_api_response_fields*, which restricts every embedded associated record to its *id* and *printable_\** method values instead of all columns. It is disabled by default to preserve the existing response shape:
719
+
720
+ ```ruby
721
+ ActivityNotification.configure do |config|
722
+ config.restrict_api_response_fields = true
723
+ end
724
+ ```
725
+
726
+ If you need finer control over the response shape, override the serialization options in your own API controllers instead. *ActivityNotification::NotificationsApiController#notification_json_options* and *ActivityNotification::SubscriptionsApiController#subscription_json_include_option* are protected methods that you can override to return only the fields your frontend consumes.
727
+
712
728
  #### API reference as OpenAPI Specification
713
729
 
714
730
  *activity_notification* provides API reference as [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification).
@@ -135,7 +135,7 @@ module ActivityNotification
135
135
  filtered_notifications = filtered_notifications.earlier_than(Time.iso8601(options[:earlier_than]))
136
136
  end
137
137
  if options.has_key?(:custom_filter)
138
- filtered_notifications = filtered_notifications.where(options[:custom_filter])
138
+ filtered_notifications = filtered_notifications.where(ActivityNotification.validate_custom_filter(options[:custom_filter]))
139
139
  end
140
140
  filtered_notifications
141
141
  }
@@ -31,7 +31,7 @@ module ActivityNotification
31
31
  filtered_subscriptions = filtered_subscriptions.filtered_by_key(options[:filtered_by_key])
32
32
  end
33
33
  if options.has_key?(:custom_filter)
34
- filtered_subscriptions = filtered_subscriptions.where(options[:custom_filter])
34
+ filtered_subscriptions = filtered_subscriptions.where(ActivityNotification.validate_custom_filter(options[:custom_filter]))
35
35
  end
36
36
  filtered_subscriptions
37
37
  }
@@ -47,6 +47,24 @@ module ActivityNotification
47
47
  end
48
48
  end
49
49
 
50
+ # 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.
56
+ # @param [Array, Hash] custom_filter Custom filter value
57
+ # @return [Array, Hash] The same value when it is safe to use
58
+ # @raise [ArgumentError] When custom_filter is a raw SQL String
59
+ def self.validate_custom_filter(custom_filter)
60
+ if custom_filter.is_a?(String)
61
+ raise ArgumentError,
62
+ ":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"
64
+ end
65
+ custom_filter
66
+ end
67
+
50
68
  # Casts to indifferent hash
51
69
  # @param [ActionController::Parameters, Hash] hash
52
70
  # @return [HashWithIndifferentAccess] Converted indifferent hash
@@ -240,6 +240,15 @@ module ActivityNotification
240
240
  # @return [Boolean] Whether activity_notification internally rescues optional target errors.
241
241
  attr_accessor :rescue_optional_target_errors
242
242
 
243
+ # @overload restrict_api_response_fields
244
+ # Returns whether the REST API restricts embedded associated records to their id and printable_* method values
245
+ # @return [Boolean] Whether the REST API restricts embedded associated records to their id and printable_* method values.
246
+ # @overload restrict_api_response_fields=(value)
247
+ # Sets whether the REST API restricts embedded associated records (target, notifiable, notifier, group, group members and subscription target) in JSON responses to their id and printable_* method values instead of all database columns. This is an opt-in hardening option to avoid exposing columns the frontend does not need (e.g. authentication identifiers on an associated user record). It is disabled by default to preserve the existing response shape.
248
+ # @param [Boolean] restrict_api_response_fields The new restrict_api_response_fields
249
+ # @return [Boolean] Whether the REST API restricts embedded associated records to their id and printable_* method values.
250
+ attr_accessor :restrict_api_response_fields
251
+
243
252
  # Initialize configuration for ActivityNotification.
244
253
  # These configuration can be overridden in initializer.
245
254
  # @return [Config] A new instance of Config
@@ -272,6 +281,7 @@ module ActivityNotification
272
281
  @notification_channel_prefix = 'activity_notification_channel'
273
282
  @notification_api_channel_prefix = 'activity_notification_api_channel'
274
283
  @rescue_optional_target_errors = true
284
+ @restrict_api_response_fields = false
275
285
  end
276
286
 
277
287
  # Sets ORM name for ActivityNotification (:active_record, :mongoid or :dynamodb)
@@ -332,7 +332,7 @@ module Dynamoid # :nodoc: all
332
332
  filtered_notifications = filtered_notifications.earlier_than(Time.iso8601(options[:earlier_than]))
333
333
  end
334
334
  if options.has_key?(:custom_filter)
335
- filtered_notifications = filtered_notifications.where(options[:custom_filter])
335
+ filtered_notifications = filtered_notifications.where(ActivityNotification.validate_custom_filter(options[:custom_filter]))
336
336
  end
337
337
  filtered_notifications
338
338
  end
@@ -1,3 +1,3 @@
1
1
  module ActivityNotification
2
- VERSION = "2.6.1"
2
+ VERSION = "2.7.0"
3
3
  end
@@ -121,4 +121,12 @@ ActivityNotification.configure do |config|
121
121
  # See https://github.com/simukappu/activity_notification/issues/155 for more details.
122
122
  config.rescue_optional_target_errors = true
123
123
 
124
+ # Configure if the REST API restricts embedded associated records in JSON responses.
125
+ # When set true, associated records (target, notifiable, notifier, group, group members
126
+ # and subscription target) are serialized with only their id and printable_* method values
127
+ # instead of all database columns, to avoid exposing columns the frontend does not need.
128
+ # Note that authentication and authorization of the REST API is still the responsibility of
129
+ # your application (see the :with_devise routing option). Default value is false.
130
+ # config.restrict_api_response_fields = false
131
+
124
132
  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.6.1
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shota Yamazaki
@@ -311,7 +311,7 @@ description: Integrated user activity notifications for Ruby on Rails. Provides
311
311
  to configure multiple notification targets and make activity notifications with
312
312
  notifiable models, like adding comments, responding etc.
313
313
  email:
314
- - shota.yamazaki.8@gmail.com
314
+ - shota.yamazaki.work@gmail.com
315
315
  executables: []
316
316
  extensions: []
317
317
  extra_rdoc_files: []