
Bibliothèque d'autorisation déclarative avec un DSL pour écrire des règles de politique, des conditions et de la mise en cache. Permet une gestion des permissions évolutive et DRY pour les applications Ruby.
DeclarativePolicy : Une bibliothèque d'autorisation déclarativeCette bibliothèque fournit un DSL pour écrire des politiques d'autorisation.
Elle peut être utilisée pour séparer la logique des permissions, et a été utilisée à grande échelle en production sur GitLab.com.
L'auteur original de cette bibliothèque est Jeanine Adkisson, et les droits d'auteur sont détenus par GitLab.
Ajoutez cette ligne au Gemfile de votre application :
gem 'declarative_policy'
Et ensuite exécutez :
$ bundle install
Ou installez-la vous-même avec :
$ gem install declarative_policy
require 'declarative_policy'
class User
attr_reader :name
def initialize(name:)
@name = name
end
end
class Vehicle
def initialize(owner:, trusted: [])
@owner = owner
@trusted = trusted
end
def owner?(user)
@owner.name == user.name
end
def trusted?(user)
@owner.name == user.name || @trusted.detect { |t| t.name == user.name }
end
end
class VehiclePolicy < DeclarativePolicy::Base
condition(:owns) { @subject.owner?(@user) }
condition(:trusted) { @subject.trusted?(@user) }
rule { owns }.enable :sell_vehicle
rule { trusted }.enable :drive_vehicle
end
jack = User.new(name: 'jack')
jill = User.new(name: 'jill')
jacks_vehicle = Vehicle.new(owner: jack, trusted: [jill])
jills_vehicle = Vehicle.new(owner: jill, trusted: [jack])
puts "Jack can drive Jack's vehicle? -> #{DeclarativePolicy.policy_for(jack, jacks_vehicle).can?(:drive_vehicle)}"
puts "Jack can drive Jill's vehicle? -> #{DeclarativePolicy.policy_for(jack, jills_vehicle).can?(:drive_vehicle)}"
puts "Jack can sell Jack's vehicle? -> #{DeclarativePolicy.policy_for(jack, jacks_vehicle).can?(:sell_vehicle)}"
puts "Jack can sell Jill's vehicle? -> #{DeclarativePolicy.policy_for(jack, jills_vehicle).can?(:sell_vehicle)}"
$ ruby example.rb
Jack can drive Jack's vehicle? -> true
Jack can drive Jill's vehicle? -> true
Jack can sell Jack's vehicle? -> true
Jack can sell Jill's vehicle? -> false
L'abstraction centrale de cette bibliothèque est une Policy. Les politiques combinent :
conditions) sur l'état du monderules)Cette bibliothèque existe pour déterminer la valeur de vérité d'énoncés de la forme :
User Predicate [Subject]
Le renommage de User en Actor et Subject en Resource est discuté dans ce ticket.
Par exemple :
user :is_aliveuser :can_drive caruser :can_sell carIl le fait en nous permettant d'associer une Policy (un ensemble de règles sur les énoncés vrais) aux objets des phrases. Un énoncé est considéré comme vrai si aucune règle ne le previent (empêche), et qu'au moins une règle l'enable (autorise).
Par exemple, imaginons que nous avons un modèle de données contenant des véhicules et des utilisateurs, et que nous voulons savoir si un utilisateur peut conduire un véhicule. Nous avons besoin d'une VehiclePolicy :
class VehiclePolicy < DeclarativePolicy::Base
# relevant facts
condition(:owns) { @subject.owner == @user }
condition(:has_access_to) { @subject.owner.trusts?(@user) }
condition(:old_enough_to_drive) { @user.age >= laws.minimum_age }
condition(:has_driving_license) { @user.driving_license&.valid? }
# expensive rules can have 'score'. Higher scores are 'more expensive' to calculate
condition(:owns, score: 0) { @subject.owner == @user }
condition(:has_access_to, score: 3) { @subject.owner.trusts?(@user) }
condition(:intoxicated, score: 5) { @user.blood_alcohol > laws.max_blood_alcohol }
# conclusions we can draw:
rule { owns }.enable :drive_vehicle
rule { has_access_to }.enable :drive_vehicle
rule { ~old_enough_to_drive }.prevent :drive_vehicle
rule { intoxicated }.prevent :drive_vehicle
rule { ~has_driving_license }.prevent :drive_vehicle
# we can use methods to abstract common logic
def laws
@subject.registration.country.driving_laws
end
end
Quelques points à noter : nous aurions pu écrire cela comme une seule grande règle ((owns | has_access_to) & old_enough_to_drive & ~intoxicated & has_driving_license) mais nous pouvons voir certaines des fonctionnalités qui rendent les politiques déclaratives évolutives pour les grands systèmes : les règles peuvent être décomposées en petits éléments, et composées en règles plus grandes. De nouvelles conditions et règles peuvent être ajoutées à tout moment.
Ce qui est plus difficile à voir, c'est que de nombreuses optimisations de performance sont gérées pour nous de manière transparente :
En bref, cette bibliothèque vise à être déclarative : nous déclarons les règles importantes, et la bibliothèque organise la façon de les évaluer.
La mise en cache est une fonctionnalité particulièrement précieuse des politiques. Si nous ajoutons de nouvelles règles concernant la vente d'un véhicule, par exemple :
rule { owns }.enable :sell_vehicle
Alors le fait de propriété peut être partagé entre différents appels à la politique, économisant des appels de base de données et d'autres opérations d'E/S coûteuses.
Nous pouvons vérifier la détermination d'une politique avec :
cache = Session.current_session
policy = DeclarativePolicy.policy_for(user, car, cache: cache)
policy.can?(:drive_vehicle)
Pour plus de détails sur l'utilisation, consultez la documentation.
Après avoir cloné le dépôt, exécutez bundle install pour installer les dépendances. Ensuite, exécutez rake spec pour lancer les tests. Vous pouvez également exécuter bin/console pour une invite interactive qui vous permettra d'expérimenter.
Pour installer cette gem sur votre machine locale, exécutez bundle exec rake install. Pour publier une nouvelle version, mettez à jour le numéro de version dans version.rb, puis exécutez bundle exec rake release, ce qui créera un tag git pour la version, poussera les commits et tags git, et poussera le fichier .gem vers rubygems.org.
Plus de détails sur les politiques et les rôles personnalisés peuvent être trouvés dans les pages suivantes :
Les signalements de bugs et les demandes de fusion sont les bienvenus sur GitLab à l'adresse https://gitlab.com/gitlab-org/ruby/gems/declarative-policy. Ce projet se veut un espace sûr et accueillant pour la collaboration, et les contributeurs sont tenus de respecter le code de conduite GitLab.
Nous publions declarative_policy de manière ad hoc. Il n'y a pas de régularité dans les publications, nous publions simplement lorsque nous apportons un changement - quelle que soit la taille du changement.
Pour publier une nouvelle version :
declarative_policy à la nouvelle version.La gem est disponible en open source sous les termes de la licence MIT.
Toute personne interagissant dans la base de code, les trackers de tickets, les salons de discussion et les listes de diffusion du projet DeclarativePolicy est tenue de suivre le code de conduite.