
Biblioteca de autorización declarativa con un DSL para escribir reglas de políticas, condiciones y almacenamiento en caché. Permite una gestión de permisos escalable y DRY para aplicaciones Ruby.
DeclarativePolicy: Una biblioteca de autorización declarativaEsta biblioteca proporciona un DSL para escribir políticas de autorización.
Se puede utilizar para separar la lógica de los permisos y se ha utilizado a gran escala en producción en GitLab.com.
El autor original de esta biblioteca es Jeanine Adkisson, y los derechos de autor pertenecen a GitLab.
Agrega esta línea al Gemfile de tu aplicación:
gem 'declarative_policy'
Y luego ejecuta:
$ bundle install
O instálalo tú mismo con:
$ 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
La abstracción central de esta biblioteca es una Policy. Las políticas combinan:
conditions) sobre el estado del mundorules)Esta biblioteca existe para determinar el valor de verdad de afirmaciones de la forma:
User Predicate [Subject]
Renombrar User a Actor y Subject a Resource se discute en este issue.
Por ejemplo:
user :is_aliveuser :can_drive caruser :can_sell carHace esto permitiéndonos asociar una Policy (un conjunto de reglas sobre qué afirmaciones son verdaderas) con los objetos de las oraciones. Se considera que una afirmación se cumple si ninguna regla la previene, y al menos una regla la habilita.
Por ejemplo, imagina que tenemos un modelo de datos que contiene vehículos y usuarios, y queremos saber si un usuario puede conducir un vehículo. Necesitamos una 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
Algunos puntos a destacar: podríamos haber escrito esto como una gran regla ((owns | has_access_to) & old_enough_to_drive & ~intoxicated & has_driving_license) pero podemos ver algunas de las características que hacen que las políticas declarativas sean escalables para sistemas grandes: las reglas se pueden dividir en elementos pequeños y componer en reglas más grandes. Se pueden agregar nuevas condiciones y reglas en cualquier momento.
Lo que es más difícil de ver es que muchas optimizaciones de rendimiento se manejan de forma transparente para nosotros:
En resumen, esta biblioteca aspira a ser declarativa: declaramos las reglas que son importantes, y la biblioteca organiza cómo evaluarlas.
El almacenamiento en caché es una característica particularmente valiosa de las políticas. Si agregamos nuevas reglas sobre la venta de un vehículo, por ejemplo:
rule { owns }.enable :sell_vehicle
Entonces, el hecho de propiedad se puede compartir entre diferentes llamadas a la política, ahorrando llamadas a la base de datos y otras operaciones costosas de E/S.
Podemos verificar la determinación de una política con:
cache = Session.current_session
policy = DeclarativePolicy.policy_for(user, car, cache: cache)
policy.can?(:drive_vehicle)
Para más detalles de uso, consulta la documentación.
Después de clonar el repositorio, ejecuta bundle install para instalar las dependencias. Luego, ejecuta rake spec para ejecutar las pruebas. También puedes ejecutar bin/console para un prompt interactivo que te permitirá experimentar.
Para instalar esta gema en tu máquina local, ejecuta bundle exec rake install. Para lanzar una nueva versión, actualiza el número de versión en version.rb, y luego ejecuta bundle exec rake release, lo que creará una etiqueta git para la versión, enviará los commits y etiquetas, y subirá el archivo .gem a rubygems.org.
Más detalles sobre políticas y roles personalizados se pueden encontrar en las siguientes páginas:
Los informes de errores y las solicitudes de fusión son bienvenidos en GitLab en https://gitlab.com/gitlab-org/ruby/gems/declarative-policy. Este proyecto pretende ser un espacio seguro y acogedor para la colaboración, y se espera que los colaboradores se adhieran al código de conducta de GitLab.
Lanzamos declarative_policy de forma ad-hoc. No hay regularidad en cuándo lanzamos,
simplemente lanzamos cuando hacemos un cambio, sin importar el tamaño del cambio.
Para lanzar una nueva versión:
declarative_policy a la nueva versión.La gema está disponible como código abierto bajo los términos de la Licencia MIT.
Se espera que todos los que interactúan en el código base del proyecto DeclarativePolicy, los rastreadores de incidencias, las salas de chat y las listas de correo sigan
el código de conducta.