Module: Sequel::Plugins::Privacy::ClassMethods

Extended by:
T::Helpers, T::Sig
Defined in:
lib/sequel/plugins/privacy.rb

Instance Method Summary collapse

Instance Method Details

#_inject_privacy_eager_block(opts) ⇒ Object



361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/sequel/plugins/privacy.rb', line 361

def _inject_privacy_eager_block(opts)
  original = opts[:eager_block]
  wrapped = proc do |ds|
    ds = original.call(ds) if original
    vc = Thread.current[DatasetMethods::EAGER_VC_KEY]
    if vc && T.unsafe(ds).model.respond_to?(:privacy_vc_key)
      T.unsafe(ds).for_vc(vc)
    else
      ds
    end
  end
  opts.merge(eager_block: wrapped)
end

#allow_unsafe_access!Object



154
155
156
157
# File 'lib/sequel/plugins/privacy.rb', line 154

def allow_unsafe_access!
  @allow_unsafe_access = T.let(true, T.nilable(T::Boolean))
  Sequel::Privacy.logger&.warn("#{self} allows unsafe access - migrate to use for_vc()")
end

#allow_unsafe_access?Boolean

Returns:

  • (Boolean)


160
161
162
# File 'lib/sequel/plugins/privacy.rb', line 160

def allow_unsafe_access?
  @allow_unsafe_access == true
end

#associate(type, name, opts = {}, &block) ⇒ Object



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/sequel/plugins/privacy.rb', line 340

def associate(type, name, opts = {}, &block)
  opts = _inject_privacy_eager_block(opts)
  result = super

  case type
  when :many_to_one, :one_to_one
    _override_singular_association(name)
    _override_association_dataset(name)
  when :one_to_many, :many_to_many
    _override_plural_association(name)
    _override_association_dataset(name)
    setup_association_privacy(name) if privacy_association_policies[name]
  end

  result
end

#call(values) ⇒ Object



174
175
176
177
178
179
180
181
182
183
# File 'lib/sequel/plugins/privacy.rb', line 174

def call(values)
  vc = Thread.current[privacy_vc_key]

  unless vc || allow_unsafe_access?
    Kernel.raise Sequel::Privacy::MissingViewerContext,
                 "#{self} requires a ViewerContext. Use #{self}.for_vc(vc) or call #{self}.allow_unsafe_access!"
  end

  super
end

#finalize_privacy!Object



314
315
316
# File 'lib/sequel/plugins/privacy.rb', line 314

def finalize_privacy!
  @privacy_finalized = T.let(true, T.nilable(T::Boolean))
end

#for_vc(vc) ⇒ Object



335
336
337
# File 'lib/sequel/plugins/privacy.rb', line 335

def for_vc(vc)
  dataset.for_vc(vc)
end

#policies(action, *policy_chain) ⇒ Object



320
321
322
323
# File 'lib/sequel/plugins/privacy.rb', line 320

def policies(action, *policy_chain)
  Kernel.warn "DEPRECATED: #{self}.policies is deprecated. Use `privacy do; can :#{action}, ...; end` instead"
  register_policies(action, policy_chain)
end

#privacy(&block) ⇒ Object



214
215
216
217
218
219
220
221
# File 'lib/sequel/plugins/privacy.rb', line 214

def privacy(&block)
  if privacy_finalized?
    Kernel.raise Sequel::Privacy::PrivacyAlreadyFinalizedError, "Privacy already finalized for #{self}"
  end

  dsl = PrivacyDSL.new(self)
  dsl.instance_eval(&block)
end

#privacy_association_policiesObject



196
197
198
# File 'lib/sequel/plugins/privacy.rb', line 196

def privacy_association_policies
  @privacy_association_policies ||= T.let({}, T.nilable(T::Hash[Symbol, T::Hash[Symbol, T::Array[T.untyped]]]))
end

#privacy_fieldsObject



191
192
193
# File 'lib/sequel/plugins/privacy.rb', line 191

def privacy_fields
  @privacy_fields ||= T.let({}, T.nilable(T::Hash[Symbol, Symbol]))
end

#privacy_finalized?Boolean

Returns:

  • (Boolean)


201
202
203
# File 'lib/sequel/plugins/privacy.rb', line 201

def privacy_finalized?
  @privacy_finalized == true
end

#privacy_policiesObject



186
187
188
# File 'lib/sequel/plugins/privacy.rb', line 186

def privacy_policies
  @privacy_policies ||= T.let({}, T.nilable(T::Hash[Symbol, T::Array[T.untyped]]))
end

#privacy_vc_keyObject



167
168
169
# File 'lib/sequel/plugins/privacy.rb', line 167

def privacy_vc_key
  :"#{self}_privacy_vc"
end

#protect_field(field, policy: nil) ⇒ Object



327
328
329
330
331
# File 'lib/sequel/plugins/privacy.rb', line 327

def protect_field(field, policy: nil)
  Kernel.warn "DEPRECATED: #{self}.protect_field is deprecated. Use `privacy do; field :#{field}, ...; end` instead"
  policy_name = policy || :"view_#{field}"
  register_protected_field(field, policy_name)
end

#register_association_policies(assoc_name, action, policies) ⇒ Object



265
266
267
268
269
270
271
272
# File 'lib/sequel/plugins/privacy.rb', line 265

def register_association_policies(assoc_name, action, policies)
  Kernel.raise "Privacy policies have been finalized for #{self}" if privacy_finalized?

  privacy_association_policies[assoc_name] ||= {}
  assoc_hash = T.must(privacy_association_policies[assoc_name])
  assoc_hash[action] ||= []
  T.must(assoc_hash[action]).concat(policies)
end

#register_policies(action, policies) ⇒ Object



224
225
226
227
228
229
230
231
# File 'lib/sequel/plugins/privacy.rb', line 224

def register_policies(action, policies)
  if privacy_finalized?
    Kernel.raise Sequel::Privacy::PrivacyAlreadyFinalizedError, "Privacy already finalized for #{self}"
  end

  privacy_policies[action] ||= []
  T.must(privacy_policies[action]).concat(policies)
end

#register_protected_field(field, policy_name) ⇒ Object



234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/sequel/plugins/privacy.rb', line 234

def register_protected_field(field, policy_name)
  if privacy_finalized?
    Kernel.raise Sequel::Privacy::PrivacyAlreadyFinalizedError, "Privacy already finalized for #{self}"
  end

  privacy_fields[field] = policy_name

  original_method = instance_method(field)

  define_method(field) do
    return original_method.bind(self).() if Sequel::Privacy::Enforcer.in_policy_eval?

    vc = instance_variable_get(:@viewer_context)

    unless vc
      return original_method.bind(self).() if T.unsafe(self.class).allow_unsafe_access?

      Kernel.raise Sequel::Privacy::MissingViewerContext,
                   "#{self.class}##{field} requires a ViewerContext"
    end

    value = original_method.bind(self).()
    return unless T.cast(self, InstanceMethods).allow?(vc, policy_name)

    value
  end
end

#setup_association_privacy(assoc_name) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/sequel/plugins/privacy.rb', line 277

def setup_association_privacy(assoc_name)
  assoc_policies = privacy_association_policies[assoc_name]
  return unless assoc_policies

  reflection = association_reflection(assoc_name)
  return unless reflection

  @_wrapped_associations ||= T.let({}, T.nilable(T::Hash[Symbol, T::Boolean]))
  return if @_wrapped_associations[assoc_name]

  @_wrapped_associations[assoc_name] = true

  # Sequel derives mutator names by stripping a trailing 's' from
  # the association name: many_to_many :members → add_member,
  # one_to_many :memberships → add_membership.
  #
  # TODO: I'm not sure if this will break sometimes.
  singular_name = reflection[:name].to_s.chomp('s').to_sym

  add_policies = assoc_policies[:add]
  if add_policies && method_defined?(:"add_#{singular_name}")
    _wrap_association_add(assoc_name, singular_name, add_policies)
  end

  remove_policies = assoc_policies[:remove]
  if remove_policies && method_defined?(:"remove_#{singular_name}")
    _wrap_association_remove(assoc_name, singular_name, remove_policies)
  end

  remove_all_policies = assoc_policies[:remove_all]
  return unless remove_all_policies && method_defined?(:"remove_all_#{reflection[:name]}")

  _wrap_association_remove_all(assoc_name, reflection[:name], remove_all_policies)
end