DeclarativePolicy:一个声明式授权库该库提供了一个用于编写授权策略的 DSL。
它可以用于将逻辑与权限分离,并已在 GitLab.com 的生产环境中大规模使用。
该库的原始作者是 Jeanine Adkisson,版权归 GitLab 所有。
在应用的 Gemfile 中添加以下内容:
gem 'declarative_policy'
然后执行:
$ bundle install
或者通过以下方式自行安装:
$ 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
该库的核心抽象是 Policy(策略)。策略结合了:
conditions,条件)——关于世界状态的事实rules,规则)该库的存在是为了确定以下形式语句的真值:
用户 谓语 [主体]
关于将 用户 重命名为 行动者、主体 重命名为 资源 的讨论,请参阅此 issue。
例如:
user :is_aliveuser :can_drive caruser :can_sell car它通过允许我们将一个 Policy(一组关于哪些语句为真的规则)与语句的对象关联起来实现这一点。如果没有任何规则 prevents(阻止)它,并且至少有一条规则 enables(启用)它,则认为该语句成立。
例如,假设我们有一个包含车辆和用户的数据模型,我们想知道用户是否可以驾驶一辆车。我们需要一个 VehiclePolicy:
class VehiclePolicy < DeclarativePolicy::Base
# 相关事实
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? }
# 昂贵的规则可以有 'score'。分数越高,计算成本越高
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 }
# 我们可以得出的结论:
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
# 我们可以使用方法抽象公共逻辑
def laws
@subject.registration.country.driving_laws
end
end
需要注意几点:我们可以将其写成一条大规则((owns | has_access_to) & old_enough_to_drive & ~intoxicated & has_driving_license),但我们可以看出声明式策略的一些特性使其适用于大型系统:规则可以拆分成小元素,并组合成更大的规则。可以随时添加新的条件和规则。
不太明显的是,许多性能优化已经为我们透明地处理了:
简而言之,该库旨在声明式:我们声明重要的规则,库来安排如何评估它们。
缓存是策略的一个特别有价值的功能。例如,如果我们在出售车辆时添加新规则:
rule { owns }.enable :sell_vehicle
那么所有权的事实可以在对策略的不同调用之间共享,从而节省数据库调用和其他昂贵的 I/O 操作。
我们可以通过以下方式检查策略的判定:
cache = Session.current_session
policy = DeclarativePolicy.policy_for(user, car, cache: cache)
policy.can?(:drive_vehicle)
有关更多使用细节,请参阅文档。
签出仓库后,运行 bundle install 来安装依赖项。然后运行 rake spec 来运行测试。你也可以运行 bin/console 来获得一个交互式提示符,以便进行实验。
要将此 gem 安装到你的本地机器上,运行 bundle exec rake install。要发布新版本,请更新 version.rb 中的版本号,然后运行 bundle exec rake release,这将为该版本创建一个 git 标签,推送 git 提交和标签,并将 .gem 文件推送到 rubygems.org。
有关策略和自定义角色的更多详细信息,请参阅以下页面:
欢迎在 GitLab 上提交错误报告和合并请求:https://gitlab.com/gitlab-org/ruby/gems/declarative-policy。该项目旨在成为一个安全、欢迎合作的协作空间,贡献者应遵守 GitLab 行为准则。
我们不定期发布 declarative_policy。发布没有规律性,我们只在做出更改时发布——无论更改大小如何。
发布新版本:
Gemfile,将 declarative_policy Ruby gem 升级到新版本。该 gem 以 MIT 许可证 的条款作为开源发布。
所有与 DeclarativePolicy 项目的代码库、问题跟踪器、聊天室和邮件列表互动的人员都应遵守行为准则。