How to define conditional abilities using cancan - ruby

I am using cancan gem for authorization. I have defined ability.rb module to initialize roles and their permissions for different modules.
`def initialize(user)
user ||= User.new # guest user (not logged in)
members = user.members
members.each do |member|
role = member.role
if role.presence
permissions = role.permissions
permissions.each do |permission|
actions = Array.new
actions = permission.activity_name.split(",") if permission.activity_name.presence
app_module = permission.application_module
actions.each do |action|
if app_module.module_name == "Project"
Rails.logger.debug "in if"
can action.downcase.to_sym, app_module.module_name.constantize, :id => member.project_id if action.presence
elsif app_module.module_name == "Sequence"
Rails.logger.debug "in sequence and project id is #{member.project_id} and action is #{action}"
can action.downcase.to_sym, app_module.module_name.constantize, :project_id => 0..1 if action.presence
else
Rails.logger.debug "Module name is #{app_module.module_name}"
can action.downcase.to_sym, app_module.module_name.constantize if action.presence
end
end
end
end
end
end`
Here user has assigned a project and project has many sequences.
If user has assigned a project with id 1 then I have to fetch only those sequences whose project id is 1
In controller I have written -
load_and_authorize_resource
The permissions for project controller is properly fetched but for sequence controller if I pass project id also it was not restricting to other project.
Can anyone please help me

Conditional abilities would be in your ability.rb file.
You would do something like this in your ability.rb file:
can [:<actions>], Sequence do |sequence|
sequence.project.user_id == user.id
end

Related

Factory Girl building and creating 3 related models, association is lost after build stage

I have the following models
class Company
has_many :admins, class_name: 'Profile::CompanyAdmin'
validates :must_have_at_least_one_admin_validation
def must_have_at_least_one_admin_validation
errors.add(:admins, :not_enough) if admins.size.zero?
end
end
class Profile::CompanyAdmin
belongs_to :company
belongs_to :user, inverse_of: :company_admin_profiles
end
class User
has_many :company_admin_profiles, inverse_of: :user
end
I am trying to set up factories so I can easily build coherent data. Especially, I want to be able to create(:company, *traits) and it creates an Admin profile with a user account
factory :company do
transient do
# need one admin to pass validation
admins_count 1 # Admins with a user account
invited_admins_count 0 # Admins without user account
end
after(:build) do |comp, evaluator|
# Creating a company admin with a user
comp.admins += build_list(:company_admin_user,
evaluator.admins_count,
company: comp
).map { |u| u.company_admin_profiles.first }
comp.admins += build_list(:company_admin_profile,
evaluator.invited_admins_count,
company: comp
)
comp.entities = build_list(:entity,
evaluator.entity_count,
company: comp
)
# If I debug here, I have
# comp.admins.first.user # => Exists !
end
after(:create) do |comp, evaluator|
# If I debug here
# comp.admins.first.user # => Gone 😱
# First save users of admin profiles (we need the user ID for the admin profile user foreign key)
comp.admins.map(&:user).each(&:save!)
# Then save admins themselves
comp.admins.each(&:save!)
end
In the example above, when I debug at the end of the company after_build stage, I have successfully built admin profiles with thier users, however after the beginning of the after_create stage, I have lost the associated user in the admin profiles (cf comments)
What's wrong ?
For the reference here are the other factories for Profile/User
factory(:company_admin_user) do
transient do
company { build(:company, admins_count: 0) }
end
after(:build) do |user, evaluator|
user.company_admin_profiles << build(:company_admin_profile,
company: evaluator.company,
user: user,
)
end
after(:create) do |user, evaluator|
user.rh_profiles.each(&:save!)
end
end
factory :company_admin_profile, class: Profile::CompanyAdmin do
company
user nil # By default creating a CompanyAdmin profile does not create associated user
end
EDIT :
A simpler way to see the problem
company = FactoryGirl.build(:company)
company.admins.first.user # => Exists !
company.save # => true
company.admins.first.user # => Nil !
It would seem saving the company model first loses the 2-level deep nested user association. SO instead of
after(:create) do |comp, evaluator|
# First save the users
comp.admins.map(&:user).compact.each(&:save!)
# Then save admins themselves
comp.admins.each(&:save!)
The following does work (still not quite sure why though)
before(:create) do |comp, evaluator|
# Need to save newly created 2-level deep nested users first
comp.admins.map(&:user).compact.each(&:save!)
end
after(:create) do |comp, evaluator|
# Then save admins themselves
comp.admins.each(&:save!)
end

Warming Up Cache Digests Overnight

We have a Rails 3.2 website which is fairly large with thousands of URLs. We implemented Cache_Digests gem for Russian Doll caching. It is working well. We want to further optimize by warming up the cache overnight so that user gets a better experience during the day. I have seen answer to this question: Rails: Scheduled task to warm up the cache?
Could it be modified for warming up large number of URLs?
To trigger cache hits for many pages with expensive load times, just create a rake task to iteratively send web requests to all record/url combinations within your site. (Here is one implementation)
Iteratively Net::HTTP request all site URL/records:
To only visit every page, you can run a nightly Rake task to make sure that early morning users still have a snappy page with refreshed content.
lib/tasks/visit_every_page.rake:
namespace :visit_every_page do
include Net
include Rails.application.routes.url_helpers
task :specializations => :environment do
puts "Visiting specializations..."
Specialization.all.sort{ |a,b| a.id <=> b.id }.each do |s|
begin
puts "Specialization #{s.id}"
City.all.sort{ |a,b| a.id <=> b.id }.each do |c|
puts "Specialization City #{c.id}"
Net::HTTP.get( URI("http://#{APP_CONFIG[:domain]}/specialties/#{s.id}/#{s.token}/refresh_city_cache/#{c.id}.js") )
end
Division.all.sort{ |a,b| a.id <=> b.id }.each do |d|
puts "Specialization Division #{d.id}"
Net::HTTP.get( URI("http://#{APP_CONFIG[:domain]}/specialties/#{s.id}/#{s.token}/refresh_division_cache/#{d.id}.js") )
end
end
end
end
# The following methods are defined to fake out the ActionController
# requirements of the Rails cache
def cache_store
ActionController::Base.cache_store
end
def self.benchmark( *params )
yield
end
def cache_configured?
true
end
end
(If you want to directly include cache expiration/recaching into this task, check out this implementation.)
via a Custom Controller Action:
If you need to bypass user authentication restrictions to get to your pages, and/or you don't want to screw up (too badly) your website's tracking analytics, you can create a custom controller action for hitting cache digests that use tokens to bypass authentication:
app/controllers/specializations.rb:
class SpecializationsController < ApplicationController
...
before_filter :check_token, :only => [:refresh_cache, :refresh_city_cache, :refresh_division_cache]
skip_authorization_check :only => [:refresh_cache, :refresh_city_cache, :refresh_division_cache]
...
def refresh_cache
#specialization = Specialization.find(params[:id])
#feedback = FeedbackItem.new
render :show, :layout => 'ajax'
end
def refresh_city_cache
#specialization = Specialization.find(params[:id])
#city = City.find(params[:city_id])
render 'refresh_city.js'
end
def refresh_division_cache
#specialization = Specialization.find(params[:id])
#division = Division.find(params[:division_id])
render 'refresh_division.js'
end
end
Our custom controller action renders the views of other expensive to load pages, causing cache hits to those pages. E.g. refresh_cache renders the same view page & data as controller#show, so requests to refresh_cache will warm up the same cache digests as controller#show for those records.
Security Note:
For security reasons, I recommend before providing access to any custom refresh_cache controller request that you pass in a token and check it to make sure that it corresponds with a unique token for that record. Matching URL tokens to database records before providing access (as seen above) is trivial because your Rake task has access to the unique tokens of each record -- just pass the record's token in with each request.
tl;dr:
To trigger thousands of site URL's/cache digests, create a rake task to iteratively request every record/url combination in your site. You can bypass your app's user authentication restrictions for this task by creating a a custom controller action that authenticates access via tokens instead.
I realize this question is about a year old, but I just worked out my own answer, after scouring a bunch of partial & incorrect solutions.
Hopefully this will help the next person...
Per my own utility class, which can be found here:
https://raw.githubusercontent.com/JayTeeSF/cmd_notes/master/automated_action_runner.rb
You can simply run this (per it's .help method) and pre-cache your pages, without tying-up your own web-server, in the process.
class AutomatedActionRunner
class StatusObject
def initialize(is_valid, error_obj)
#is_valid = !! is_valid
#error_obj = error_obj
end
def valid?
#is_valid
end
def error
#error_obj
end
end
def self.help
puts <<-EOH
Instead tying-up the frontend of your production site with:
`curl http://your_production_site.com/some_controller/some_action/1234`
`curl http://your_production_site.com/some_controller/some_action/4567`
Try:
`rails r 'AutomatedActionRunner.run(SomeController, "some_action", [{id: "1234"}, {id: "4567"}])'`
EOH
end
def self.common_env
{"rack.input" => "", "SCRIPT_NAME" => "", "HTTP_HOST" => "localhost:3000" }
end
REQUEST_ENV = common_env.freeze
def self.run(controller, controller_action, params_ary=[], user_obj=nil)
success_objects = []
error_objects = []
autorunner = new(controller, controller_action, user_obj)
Rails.logger.warn %Q|[AutomatedAction Kickoff]: Preheating cache for #{params_ary.size} #{autorunner.controller.name}##{controller_action} pages.|
params_ary.each do |params_hash|
status = autorunner.run(params_hash)
if status.valid?
success_objects << params_hash
else
error_objects << status.error
end
end
return process_results(success_objects, error_objects, user_obj.try(:id), autorunner.controller.name, controller_action)
end
def self.process_results(success_objects=[], error_objects=[], user_id, controller_name, controller_action)
message = %Q|AutomatedAction Summary|
backtrace = (error_objects.first.try(:backtrace)||[]).join("\n\t").inspect
num_errors = error_objects.size
num_successes = success_objects.size
log_message = %Q|[#{message}]: Generated #{num_successes} #{controller_name}##{controller_action}, pages; Failed #{num_errors} times; 1st Fail: #{backtrace}|
Rails.logger.warn log_message
# all the local-variables above, are because I typically call Sentry or something with extra parameters!
end
attr_reader :controller
def initialize(controller, controller_action, user_obj)
#controller = controller
#controller = controller.constantize unless controller.respond_to?(:name)
#controller_instance = #controller.new
#controller_action = controller_action
#env_obj = REQUEST_ENV.dup
#user_obj = user_obj
end
def run(params_hash)
Rails.logger.warn %Q|[AutomatedAction]: #{#controller.name}##{#controller_action}(#{params_hash.inspect})|
extend_with_autorun unless #controller_instance.respond_to?(:autorun)
#controller_instance.autorun(#controller_action, params_hash, #env_obj, #user_obj)
end
private
def extend_with_autorun
def #controller_instance.autorun(action_name, action_params, action_env, current_user_value=nil)
self.params = action_params # suppress strong parameters exception
self.request = ActionDispatch::Request.new(action_env)
self.response = ActionDispatch::Response.new
define_singleton_method(:current_user, -> { current_user_value })
send(action_name) # do it
return StatusObject.new(true, nil)
rescue Exception => e
return StatusObject.new(false, e)
end
end
end

Hide Products from list based on authorization using cancan in rails_admin

I have a Product model.
Ability class is as follows:
class Ability
include CanCan::Ability
def initialize(user)
if user.has_role? :super_admin
can :manage, :all
else
can :read, Product, Product.all.limit(10) if user.has_role? :brand_manager
can :access, :rails_admin # grant access to rails_admin
can :dashboard # grant access to the dashboard
end
end
end
Here is the code for rails_admin:
RailsAdmin.config do |config|
config.authorize_with :cancan
end
I want to hide all the products from list which don't have particular name? Some how rails_admin does not support it. Can anyone help me to resolve this issue?
You can do something like this.
1. Create a new field in model like admin_user_id
2. While creating/updating product from save current admin user details in product data
and use it ability class
class Ability
include CanCan::Ability
def initialize(user)
if user.has_role? :super_admin
can :manage, :all
elsif user.has_role? :brand_manager
can :manage,Product, :admin_user_id=> user.id
can :access, :rails_admin # grant access to rails_admin
can :dashboard # grant access to the dashboard
end
end
end
What do you mean with hide all the producs from the list ?
If you want that users can't read it, you can try:
can :read, Product do |product|
[name1, name2].include?( product.name )
end
If you want don't return these products, you can use in your controller:
Product.where( :name.in => [name1, name2] )
I hope I've helped

Rails, CanCan manage all & other roles

class Ability
include CanCan::Ability
def initialize(user)
#user = user || User.new
can :manage, :all
can :custom_action, User, role: 'admin'
end
end
and in view
if can? :custom_action, #user
SHOW SOMETHING
this if always show "SHOW SOMETHING", don't understood why it's happend.
Well, that's because in your ability class, you give every user all rights.
You are probably looking for something like this:
def initialize(user)
#user = user || User.new
can :manage, :all
# When user is an admin, grant her extra privileges
if #user.is_admin?
can :custom_action
end
end
This way, you define the abilities (by using can) conditionally
Solution is:
class Ability
include CanCan::Ability
def initialize(user)
#user = user || User.new
can :manage, :all
cannot :custom_action, User, role: 'admin'
end
end
In view:
if can? :custom_action, #user
return
user = true
admin = false
This is not perfect solution but it's works

Generating JSON for Sinatra

I'm having an issue with passing the generated JSON notation of my object to my Sinatra application. The problem I have is twofold:
I have 2 classes that are mapped to a database using the Sequel gem. When they generate JSON it is ok and properly implemented.
I have a custom class called registration that maps one of the classes with an additional field. The goal is to generate JSON out of this and pass that JSON to the application using cucumber (test purpose)
The application code responsible for handling the request has the following function defined:
post '/users' do
begin
hash = JSON.parse(self.request.body.read)
registration = Registration.new.from_json(#request.body.read)
registration.user.country = Database::Alaplaya.get_country_by_iso_code(registration.user.country.iso_code)
return 400 unless(registration.is_valid?)
id = Database::Alaplaya.create_user(registration.user)
# If the registration failed in our system, return a page 400.
return 400 if id < 1
end
problem 1: I cannot use the params hash. It exists but is just an empty hash. Why?
problem 2: I cannot deserialize the JSON generated by the class itself. Why?
The registration class looks like this:
require 'json'
class Registration
attr_accessor :user, :project_id
def to_json(*a)
{
'json_class' => self.class.name,
'data' => [#user.to_json(*a), #project_id]
}.to_json(*a)
end
def self.json_create(o)
new(*o['data'])
end
# Creates a new instance of the class using the information provided in the
# hash. If a field is missing in the hash, nil will be assigned to that field
# instead.
def initialize(params = {})
#user = params[:user]
#project_id = params[:project_id]
end
# Returns a string representing the entire Registration.
def inspect
"#{#user.inspect} - #{#user.country.inspect} - #{#project_id}"
end
# Returns a boolean valid representing whether the Registration instance is
# considered valid for the API or not. True if the instance is considered
# valid; otherwise false.
def is_valid?
return false if #user.nil? || #project_id.nil?
return false if !#user.is_a?(User) || !#project_id.is_a?(Fixnum)
return false if !#user.is_valid?
true
end
end
I had to implement the methods to generate the JSON output correctly. When I run this in console I get the following output generated:
irb(main):004:0> r = Registration.new(:user => u, :project_id => 1)
=> new_login - nil - 1
irb(main):005:0> r.to_json
=> "{\"json_class\":\"Registration\",\"data\":[\"{\\\"json_class\\\":\\\"User\\\
",\\\"login\\\":\\\"new_login\\\"}\",1]}"
Which looks like valid JSON to me. However when I POST this to the application server and try to parse this, JSON complains that at least 2 octets are needed and refuses to deserialize the object.
If you're using Sequel as your ORM, try something like this:
In your model:
class Registration < Sequel::Model
many_to_one :user
many_to_one :project
plugin :json_serializer
end
The server:
before do
#data = JSON.parse(request.body.read) rescue {}
end
post '/users' do
#registration = Registration.new #data
if #registration.valid?
#registration.save
#registration.to_json #return a JSON representation of the resource
else
status 422 #proper status code for invalid input
#registration.errors.to_json
end
end
I think you may be overcomplicating your registration process. If the HTTP action is POST /users then why not create a user? Seems like creating a registration is overly complex. Unless your user already exists, in which case POST /users would be incorrect. If what you're really intending to do is add a user to to a project, then you should PUT /projects/:project_id/users/:user_id and the action would look something like this:
class User < Sequel::Model
many_to_many :projects
end
class Project < Sequel::Model
many_to_many :users
end
#make sure your db schema has a table called users_projects or projects_users
put '/projects/:project_id/users/:user_id' do
#find the project
#project = Project.find params[:project_id]
raise Sinatra::NotFound unless #project
#find the user
#user = Project.find params[:project_id]
raise Sinatra::NotFound unless #user
#add user to project's users collection
#project.add_user #user
#send a new representation of the parent resource back to the client
#i like to include the child resources as well
#json might look something like this
#{ 'name' : 'a project name', 'users' : ['/users/:user_id', '/users/:another_user_id'] }
#project.to_json
end

Resources