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



370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/sequel/plugins/privacy.rb', line 370

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!(except: []) ⇒ Object



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

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

#allow_unsafe_access?(name = nil) ⇒ Boolean

Returns:

  • (Boolean)


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

def allow_unsafe_access?(name = nil)
  return false unless @allow_unsafe_access == true
  return true if name.nil?

  !(@unsafe_access_except || []).include?(name)
end

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



349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/sequel/plugins/privacy.rb', line 349

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



183
184
185
186
187
188
189
190
191
192
# File 'lib/sequel/plugins/privacy.rb', line 183

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



323
324
325
# File 'lib/sequel/plugins/privacy.rb', line 323

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

#for_vc(vc) ⇒ Object



344
345
346
# File 'lib/sequel/plugins/privacy.rb', line 344

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

#policies(action, *policy_chain) ⇒ Object



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

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



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

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



205
206
207
# File 'lib/sequel/plugins/privacy.rb', line 205

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

#privacy_fieldsObject



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

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

#privacy_finalized?Boolean

Returns:

  • (Boolean)


210
211
212
# File 'lib/sequel/plugins/privacy.rb', line 210

def privacy_finalized?
  @privacy_finalized == true
end

#privacy_policiesObject



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

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

#privacy_vc_keyObject



176
177
178
# File 'lib/sequel/plugins/privacy.rb', line 176

def privacy_vc_key
  :"#{self}_privacy_vc"
end

#protect_field(field, policy: nil) ⇒ Object



336
337
338
339
340
# File 'lib/sequel/plugins/privacy.rb', line 336

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



274
275
276
277
278
279
280
281
# File 'lib/sequel/plugins/privacy.rb', line 274

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



233
234
235
236
237
238
239
240
# File 'lib/sequel/plugins/privacy.rb', line 233

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



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/sequel/plugins/privacy.rb', line 243

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?(field)

      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



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
311
312
313
314
315
316
317
318
319
# File 'lib/sequel/plugins/privacy.rb', line 286

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