
Biblioteca declarativa de autorização com uma DSL para escrever regras de política, condições e cache. Permite gerenciamento de permissões escalável e DRY para aplicações Ruby.
DeclarativePolicy: Uma Biblioteca Declarativa de AutorizaçãoEsta biblioteca fornece uma DSL para escrever políticas de autorização.
Ela pode ser usada para separar lógica de permissões e tem sido usada em escala em produção no GitLab.com.
O autor original desta biblioteca é Jeanine Adkisson, e os direitos autorais são do GitLab.
Adicione esta linha ao Gemfile da sua aplicação:
gem 'declarative_policy'
E então execute:
$ bundle install
Ou instale você mesmo como:
$ 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
A abstração central desta biblioteca é uma Policy. As políticas combinam:
conditions) sobre o estado do mundorules)Esta biblioteca existe para determinar o valor verdade de afirmações da forma:
User Predicate [Subject]
Renomear User para Actor e Subject para Resource é discutido em
esta issue.
Por exemplo:
user :is_aliveuser :can_drive caruser :can_sell carEla faz isso permitindo associar uma Policy (um conjunto de regras sobre quais
afirmações são verdadeiras) com os objetos das sentenças. Uma afirmação é
considerada verdadeira se nenhuma regra a previne e pelo menos uma regra a permite.
Por exemplo, imagine que temos um modelo de dados contendo veículos e usuários, e
queremos saber se um usuário pode dirigir um veículo. Precisamos de uma 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
Alguns pontos a notar: poderíamos ter escrito isso como uma grande regra
((owns | has_access_to) & old_enough_to_drive & ~intoxicated & has_driving_license)
mas podemos ver algumas das características que tornam as políticas declarativas escaláveis para
sistemas grandes: as regras podem ser divididas em pequenos elementos e compostas em
regras maiores. Novas condições e regras podem ser adicionadas a qualquer momento.
O que é mais difícil de ver é que muitas otimizações de desempenho são tratadas para nós de forma transparente:
Em resumo, esta biblioteca visa ser declarativa: declaramos as regras que são importantes, e a biblioteca organiza como avaliá-las.
O cache é uma característica particularmente valiosa das políticas. Se adicionarmos novas regras sobre venda de veículos, por exemplo:
rule { owns }.enable :sell_vehicle
Então o fato de propriedade pode ser compartilhado entre diferentes chamadas à política, economizando chamadas ao banco de dados e outras operações de IO caras.
Podemos verificar a determinação de uma política com:
cache = Session.current_session
policy = DeclarativePolicy.policy_for(user, car, cache: cache)
policy.can?(:drive_vehicle)
Para mais detalhes de uso, consulte a documentação.
Após clonar o repositório, execute bundle install para instalar as dependências.
Depois, execute rake spec para rodar os testes. Você também pode executar bin/console para um
prompt interativo que permitirá experimentar.
Para instalar esta gem em sua máquina local, execute bundle exec rake install. Para lançar uma nova versão, atualize o número da versão em version.rb, e então execute bundle exec rake release, que criará uma tag git para a versão, enviará commits e tags git, e enviará o arquivo .gem para rubygems.org.
Mais detalhes sobre políticas e papéis customizados podem ser encontrados nas seguintes páginas:
Relatórios de bug e merge requests são bem-vindos no GitLab em https://gitlab.com/gitlab-org/ruby/gems/declarative-policy. Este projeto pretende ser um espaço seguro e acolhedor para colaboração, e espera-se que os contribuidores sigam o código de conduta do GitLab.
Lançamos o declarative_policy de forma ad-hoc. Não há regularidade sobre quando
lançamos, apenas lançamos quando fazemos uma alteração - independentemente do tamanho da
alteração.
Para lançar uma nova versão:
Gemfile do GitLab para também elevar a gem Ruby declarative_policy para a nova versão.A gem está disponível como código aberto sob os termos da Licença MIT.
Espera-se que todos que interagem no código do projeto DeclarativePolicy, rastreadores de issues,
salas de chat e listas de e-mail sigam
o código de conduta.