Skip to content

Commit

Permalink
Extend ability DSL for parents inheriting children's permissions
Browse files Browse the repository at this point in the history
This allows us to easily define abilities which parents can inherit from
their children. I.e. if the child is allowed to do it, the parent is
also allowed to do it.

Refs hitobito/hitobito#1967
  • Loading branch information
carlobeltrame committed Apr 14, 2023
1 parent bca91f8 commit a41015e
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 12 deletions.
35 changes: 35 additions & 0 deletions app/abilities/youth/ability.rb
@@ -0,0 +1,35 @@
# frozen_string_literal: true

# Copyright (c) 2023, Pfadibewegung Schweiz. This file is part of
# hitobito_youth and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https ://github.com/hitobito/hitobito_youth.

# Adds to the core the option to specify ability conditions like this:
# on(Event) do
# for_self_or_manageds do
# permission(:foo).may(:bar).some_condition
# end
# end
# The condition will then grant permission when either the logged in user or one
# of their manageds is granted permission. I.e. the logged in user inherits the
# permissions of his manageds.
# Technically, this is implemented by normally generating the "can :foo, :bar"
# statements from the stored ability configs, and then for each of the manageds
# generating additional "can :foo, :bar" statements (but only the ones which
# originate inside a for_self_or_manageds block in the ability DSL).
module Youth::Ability

private

def define_user_abilities(current_store, current_user_context)
super

user.manageds.each do |managed|
managed_user_context = AbilityDsl::UserContext.new(managed)
managed_store = current_store.only_manager_inheritable

super(managed_store, managed_user_context)
end
end
end
19 changes: 19 additions & 0 deletions app/abilities/youth/ability_dsl/config.rb
@@ -0,0 +1,19 @@
# frozen_string_literal: true

# Copyright (c) 2023, Pfadibewegung Schweiz. This file is part of
# hitobito_youth and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https ://github.com/hitobito/hitobito_youth.

# Extends the ability configs from core with the possibility
# to add options.
module Youth::AbilityDsl::Config

attr_reader :options

def initialize(permission, subject_class, action, ability_class, constraint, options = {})
super(permission, subject_class, action, ability_class, constraint)
@options = options
end

end
43 changes: 43 additions & 0 deletions app/abilities/youth/ability_dsl/recorder.rb
@@ -0,0 +1,43 @@
# frozen_string_literal: true

# Copyright (c) 2023, Pfadibewegung Schweiz. This file is part of
# hitobito_youth and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https ://github.com/hitobito/hitobito_youth.

# Extends the ability DSL with the for_self_or_manageds syntax.
# All ability conditions in such a block are granted if either the current user
# or one of their manageds (children) individually is granted the ability.
# In other words, if the abilities defined in a for_self_or_manageds block are granted
# to my child, I automatically get the same abilities.
module Youth::AbilityDsl::Recorder

def for_self_or_manageds
return unless block_given?

AbilityDsl::Recorder::Base.include_manageds = true
yield
AbilityDsl::Recorder::Base.include_manageds = false
end

# I found no way to add a class_attribute using the .prepend mechanism.
# Therefore, this module is .include'd instead of .prepend'd into its core counterpart.
module Base
extend ActiveSupport::Concern

included do
class_attribute :include_manageds
self.include_manageds = false

def add_config(permission, action, constraint)
@store.add(AbilityDsl::Config.new(permission,
@subject_class,
action,
@ability_class,
constraint,
{ include_manageds: self.include_manageds }))
end
end
end

end
21 changes: 21 additions & 0 deletions app/abilities/youth/ability_dsl/store.rb
@@ -0,0 +1,21 @@
# frozen_string_literal: true

# Copyright (c) 2023, Pfadibewegung Schweiz. This file is part of
# hitobito_youth and licensed under the Affero General Public License version 3
# or later. See the COPYING file at the top-level directory or at
# https ://github.com/hitobito/hitobito_youth.

# Extends the ability store from the core with the possibility to filter the
# stored ability configs and keep only the ones which a manager can inherit from their
# manageds (children).
module Youth::AbilityDsl::Store

def only_manager_inheritable
filtered_configs = configs.select { |_, config| config.options[:include_manageds] }
AbilityDsl::Store.new.tap do |clone|
clone.instance_variable_set(:@ability_classes, ability_classes)
clone.instance_variable_set(:@configs, filtered_configs)
end
end

end
18 changes: 6 additions & 12 deletions app/abilities/youth/person_ability.rb
Expand Up @@ -13,10 +13,12 @@ module Youth::PersonAbility
included do
on(Person) do
# Managers have almost all base permissions on the managed person
permission(:any).
may(:show, :show_details, :show_full, :history, :update, :update_email, :primary_group,
:log, :totp_reset).
herself_or_manager
for_self_or_manageds do
permission(:any).
may(:show, :show_details, :show_full, :history, :update, :update_email, :primary_group,
:log, :totp_reset).
herself
end

# People with update permission on a managed person also have the permission to update the
# managers of that managed person
Expand All @@ -31,14 +33,6 @@ module Youth::PersonAbility
end
end

def herself_or_manager
herself || manager
end

def manager
contains_any?([user.id], person.managers.pluck(:id))
end

def non_restricted_in_same_group_except_self
non_restricted_in_same_group && !herself
end
Expand Down
5 changes: 5 additions & 0 deletions lib/hitobito_youth/wagon.rb
Expand Up @@ -45,6 +45,11 @@ class Wagon < Rails::Engine
People::Merger.prepend Youth::People::Merger

# ability
Ability.prepend Youth::Ability
AbilityDsl::Recorder::Base.include Youth::AbilityDsl::Recorder::Base
AbilityDsl::Recorder.include Youth::AbilityDsl::Recorder
AbilityDsl::Config.prepend Youth::AbilityDsl::Config
AbilityDsl::Store.prepend Youth::AbilityDsl::Store
GroupAbility.include Youth::GroupAbility
EventAbility.include Youth::EventAbility
PersonAbility.include Youth::PersonAbility
Expand Down

0 comments on commit a41015e

Please sign in to comment.