Trilhos finders dinâmicos baseados em papel
-
07-07-2019 - |
Pergunta
Eu estou procurando a melhor maneira de construir a maneira limpa para papéis de construção localizadores / baseada em authorisaton?
Em meu esquema modelo, um user
pode ter um dos vários papéis (admin-definidos), tais como Administrador, Gerente Regional, Assistente de Vendas:
Exemplo Dado um usuário com uma função Gerente Regional e juntou-se a uma Região A, eu gostaria de ser capaz de consulta que outros usuários que podia ver, por exemplo:
regional_manager_for_region_a.users
=> [...] # Array of users joined to region a
regional_manager_for_region_b.users(:all, conditions => { :active => true })
=> [...] # Array of active users joined to region b
administrator.users
=> [...] # Array of all users in system
Obrigado, muito aprecio qualquer ajuda!
Solução
Eu acho que você precisa colocar no lugar de algum mecanismo de autorização.
A melhor jóia eu sei para isso é declarative_authorization . Eu usei pessoalmente, em um ambiente de produção, e estou satisfeito com ele. Há um railscast sobre isso também.
A idéia é que você declarar em um arquivo específico (config/authorization_rules.rb
) as "funções e permissões". Você diz coisas como "um gerente pode lido apenas os clientes associados com ele" ou "um administrador pode ler e escrever todos os usuários". No seu caso, ele ficaria assim:
authorization do
role :guest do
# actions here can be done by everyone, even not logged in people
end
role :user do
includes :guest
# actions here can be done by logged people
end
role :manager do
includes :user #managers do everything users do, plus:
has_permission_on :sales_region, :to => :read do
if_attribute :id => is_in {user.sales_region_ids}
end
has_permission_on :users, :to => [:update, :read] do
if_attribute :id => is {user.user_ids_by_sales_region} #defined on the model
end
end
role :admin do
includes :user
has_permission_on [:sales_regions, :users], :to :manage
end
end
privileges do
privilege :manage do
includes :create, :read, :update, :delete
end
end
Uma vez que isso for especificado, você tem modificar seus modelos de modo que eles usam declarative_authorization
. Além disso, vamos definir o método user_ids_by_sales_region
class User < ActiveRecord::Base
using_access_control # this enables DA
def users_by_sales_region
sales_regions.collect{ |sr| sr.users }.flatten.uniq
end
def user_ids_by_sales_region
users_by_sales_region.collect{ |u| u.id }
end
end
Você também deve ter um método current_user
, e uma maneira de conseguir o papel (s) do usuário atual. Consulte a seção "Fornecendo Requisitos do plug-in" no readme .
Em seguida, você pode fazer o que quiser com with_permissions_to
:
manager = User.find(...)
manager.users.with_permissions_to(:read) # the users from his region
manager.users.with_permissions_to(:read).find(:all, conditions => { :active => true })
manager.users.with_permissions_to(:write) #returns no users, managers can't edit them
admin = User.find(...)
admin.users.with_permissions_to(:write) #will return all users
Isso significa que um pouco de esforço no início, mas simplifica a aplicação muito mais tarde. Além disso, você tem funcionalidades adicionais, tais como esconder / mostrando peças de pontos de vista, dependendo das permissões do usuário atual, bem como proibindo o acesso a ações de controlador específicos.
Além disso, ele deve funcionar muito bem com paginações, etc.
Há uma outra jóia autorização declarativa chamado cancan . Eu não tenho experiência com este, mas se for feito por Ryan Bates, ele deve ser bom (ele tem um railscast por isso, também). No entanto, eu não acho que ele permite que as extensões do modelo, que é o que você parece precisar agora.
Outras dicas
A minha resposta abaixo é bom para localizadores simples; no entanto, não é muito flexível e não é compatível com o plugin will_paginate
. Alguém sabe de uma maneira melhor de âmbito limpa o @current_user
usuários é capaz de gerir?
Graças
Apenas respondi minha própria pergunta, substituindo a extensão associação padrão como abaixo. Ainda seria ótimo saber comentários ou alternativas embora!
class User < ActiveRecord::Base
has_many :users do
def find(*args)
scope = args.first || :all
options = args.extract_options!
return User.find(args.first, options) if proxy_owner.admin?
users = []
proxy_owner.sales_regions.collect do |sales_region|
users += sales_region.users
end
users.uniq
end
end
end
Apenas para acompanhar meu comentário sobre a resposta de egarcia, eu finalmente a acordo sobre declarando named_scopes
nos modelos restritos. Por exemplo:
# app/models/account.rb
class Account < ActiveRecord::Base
named_scope :visible_to, lambda { |user|
return {} if user.can_see_all_accounts?
{ :conditions => ['sales_area_id IN (?)', user.sales_area_ids] }
}
end
# app/controllers/accounts_controller.rb
class AccountsController < ApplicationController
def index
@accounts = Account.visible_to(@current_user)
...
end
end