CanCan not authorizing nested resource - ruby-on-rails-3

I have a Ticket model and a TicketReply model:
class Ticket < ActiveRecord::Base
has_many :replies, :class_name => "TicketReply"
end
class TicketReply < ActiveRecord::Base
belongs_to :ticket, :class_name => "Ticket"
end
Here is my list of abilities:
class Ability
include CanCan::Ability
def initialize(user)
can :manage, Ticket, :userid => user.id
can :manage, TicketReply, :ticket => { :userid => user.id }
end
end
And finally my TicketRepliesController:
class TicketRepliesController < AuthorizedController
load_and_authorize_resource :ticket
load_and_authorize_resource :ticket_reply, :through => :ticket
def create
if #ticket_reply.valid?
# ...
else
# ...
end
end
end
However, every time I try to create a ticket reply I get an unauthorized message: "You are not authorized to access this page.".
EDIT: I can access the Ticket itself fine through a TicketsController.
Any idea what I am missing?

In the controller new and create actions, you need to wire your new ticket record with the current_user
class TicketRepliesController < AuthorizedController
... whatever code you have
protected
# override to do the wireing
def build_resource
resource = super
resource.userid = current_user.id # wired!
resource
end
end
Now CanCan will see the link and allow!

Related

Ember: how to create a join table model using Rails API

I'm using Rails5 API and Ember 3.
On the Rails side the model I'd like to save is defined as follows:
class ShopLanguage < ApplicationRecord
belongs_to :shop
belongs_to :language
end
Here is the Shop model:
class Shop < ApplicationRecord
has_many :shop_languages, inverse_of: :shop, dependent: :destroy
has_many :languages, through: :shop_languages
end
Here is the ShopLanguage serializer:
class ShopLanguageSerializer < ActiveModel::Serializer
attributes :id, :shop_id, :language_id, :modified_by
belongs_to :shop
belongs_to :language
end
The problem is that when I'm trying to create a new shop_languagefor a specified shop from Ember:
# controllers/shop-languages.js
actions: {
saveLanguage(aLanguage) {
let shopLanguage = this.store.createRecord('shop-language', {
language: aLanguage,
shop: this.get('currentShop.shop'),
modifiedBy: this.get('currentUser.user').get('username')
});
shopLanguage.save().then(function() {
this.get('flashMessages').info('New language added with success');
});
the language_id is not passed in in params hash in the ShopLanguagesController on the Rails side:
# shop_langauges_controller.rb
class ShopLanguagesController < ApplicationController
before_action :find_shop
before_action :find_language, only: [:create, :destroy]
def create
#shop.languages << #language
json_response(#shop.languages, :created)
end
private
def language_params
params.permit(:language_id, :shop_id, :id, :modified_by)
end
def find_shop
#shop = Shop.find(params[:shop_id])
end
def find_language
#language = Language.find_by!(id: params[:language_id])
end
end
When I check the URL hit by Ember app, it seems to be OK:
POST http://localhost:4200/shops/613/languages
The error comes from find_language method because language_id is null.
Why so ? What is wrong with that ? Thank you

How can I default an ancestral relationship with cancan to an internal node of the tree?

I am using cancan to authorize my controller actions. One of classes where access is authorized by cancan is a tree, implemented with acts_as_ancestry. I'm having problems using load_and_authorize_resource when the user is not permitted to access the root level, but rather is allowed access starting at an interior node.
Here are some relavant class definitions:
class User < ActiveRecord::Base
belongs_to :organization, :inverse_of => :users
end
class Post < ActiveRecord::Base
belongs_to :organization, :inverse_of => :posts
end
class Organization < ActiveRecord::Base
has_ancestry :cache_depth => true
has_many :users, :inverse_of => :organization
has_many :posts, :inverse_of => :organization
end
The rules for managing posts are "You can manage posts in any organization below yours". My cancan abilities definition is this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
# subtree_ids is added by acts_as_ancestry
can :manage, Post, {:organization_id => user.organization.subtree_ids}
end
end
In the controller, I have this (other actions omitted)
class PostsController < ApplicationController
load_and_authorize_resource :post
def index
end
def new
end
end
Everything works fine when the authorized user belongs to the root organization. However, when I login as a user authorized at an internal node, the index action works fine, but when the new action is invoked, I get a can-can authorization error.
Here is what I see in the log:
Access denied on new #<Post id: nil, organization_id: 1>
The organization_id 1 (the root) is coming from the schema:
create_table "posts", :force => true do |t|
t.integer "organization_id", :default => 1
end
With cancan, the new action will build a new Post and assign it to #post. When it does this, it will initialize all the attributes with values taken from the can definition in Abilities.rb. However, it will not do anything if those attributes are Arrays, Hashes or Ranges and the default value ends up coming from the schema.
How can I authorize users to manage posts in their subtree, but when they create a new post, default it to their organization?
In cancan, if the #post variable is already initialized by you, it will not call load_resource on it, only do the authorize part. See this part of the docs: https://github.com/ryanb/cancan/wiki/Authorizing-controller-actions, "Override loading".
So the simplest solution is to take control of the initialization yourself and make it what you need, like here:
class PostsController < ApplicationController
before_filter :initialize_post, :only => [:new, :create]
def initialize_post
#post = current_user.organization.posts.build(params[:post]||{:name=>'Smashing Kittens'})
end
load_and_authorize_resource :post
def index
end
def new
end
def create
end
end
You can see it working in this test project that I created from your post: https://github.com/robmathews/cancan_test.
I had a similar issue and ended up writing ancestry related permissions in blocks like so:
can :manage, Post do |post|
post.organization.subtree_ids.include?(user.organization_id)
end

Rspec controller error? expecting <"index"> but rendering with <""> or it's working?

I'm new with rspec test and maybe there are something that I dont undertand.
if can any help me, I really appreciate some help.
File Structure:
app/models/booking.rb
app/models/user.rb
app/models/role.rb
app/models/ability.rb
app/controllers/bookings_controller.rb
app/views/bookings/index.html.erb
app/views/dashboard/index.html.erb
app/spec/controllers/bookings_controller_spec.rb
I read this link with a similar problem but it isn't solved
Rspec controller error expecting <"index"> but rendering with <"">
is similar, because if I change this line:
it 'should not render index template from bookings' do
get :index
=> response.should_not render_template(:index)
end
for this other:
it 'should not render index template from bookings' do
get :index
=> response.should render_template(:index)
end
I get the same mistake that in the link
expecting <"index"> but rendering with <"">
and I don't know why?
Here's my Code:
My Spec:
describe BookingsController do
context 'as guest' do
before(:each) do
#user = User.new(:email => 'mail_admin#test.com',
:username => 'admin',
:password => 'password_admin',
:password_confirmation => 'password_admin')
#user.save
#when i save, with gem CanCan i assign a default role to #user
#with the default role the user only can see the views/dashboard/index.html.erb
end
it 'should not render index template from bookings' do
get :index
response.should_not render_template(:index)
end
end
end
Controller:
class BookingsController < ApplicationController
load_and_authorize_resource
def index
...
end
def show
...
end
end
My model:
class Booking < Activerecord::Base
paginates_per 20
def
...
end
def
...
end
end
User:
Class User < ActiveRecord::Base
after_save :set_default_role
rolify
.
.
.
.
def set_default_role
self.add_role :default
end
end
Role:
class Role < ActiveRecord::Base
ROLES = {"admin" => "Admin", "default" => "Default"}
.
.
.
.
scopify
end
Ability:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.has_role? :admin
can :manage, :all
elsif user.has_role? :data_consistency
can :read, Booking
end
end
end
CanCan authorizes model access not controller actions. For most other actions these two are more or less the same thing, but not for the index. On the index action CanCan adds a scope to the query for records that includes your authorization restrictions.
What this means is that your guest user will simply not be able to see any records, but the view will still render.
What you want is authentication (ie Devise) and use it from a before_filter in each controller that requires an authenticated user to access.
class BookingsController < ApplicationController
load_and_authorize_resource # Handles authorization
before_filter !authenticate_user # Handles authentication (included with Devise)
...
end
In my case, the problem was solved in before(:each) block!
My code works like this:
before :each do
#user = User.new(:email => 'mail_admin#test.com',
:username => 'admin',
:password => 'password_admin',
:password_confirmation => 'password_admin')
#user.confirm!
sign_in #user
end

Rspec controller error: No route matches {:controller=>"bookings", :action=>"/dashboard/index"}

I'm getting an error when I try to test mi controller, I'm new with rspec test and maybe there are something that I dont undertand.
$rspec spec/controllers/booking_controller_spec.rb
I get the following error:
No route matches {:controller=>"bookings", :action=>"/dashboard/index"}
and I don't understand why? so if can any help me, I really appreciate some help.
File Structure:
app/models/booking.rb
app/models/user.rb
app/models/role.rb
app/models/ability.rb
app/controllers/bookings_controller.rb
app/views/bookings/index.html.erb
app/views/dashboard/index.html.erb
app/spec/controllers/bookings_controller_spec.rb
My fail Spec:
describe BookingsController do
context 'as guest' do
before(:each) do
#user = User.new(:email => 'mail_admin#test.com',
:username => 'admin',
:password => 'password_admin',
:password_confirmation => 'password_admin')
#user.save
#when i save, with gem CanCan i assign a default role to #user
#with the default role the user only can see the views/dashboard/index.html.erb
end
it 'should not render index template from bookings' do
get :index
response.should_not render_template(:index)
end
it 'should render index template from dashboard' do
get '/dashboard/index'
response.should render_template('index')
end
end
end
Controller:
class BookingsController < ApplicationController
load_and_authorize_resource
def index
...
end
def show
...
end
end
My model:
class Booking < Activerecord::Base
paginates_per 20
def
...
end
def
...
end
end
User:
Class User < ActiveRecord::Base
after_save :set_default_role
rolify
.
.
.
.
def set_default_role
self.add_role :default
end
end
Role:
class Role < ActiveRecord::Base
ROLES = {"admin" => "Admin", "default" => "Default"}
.
.
.
.
scopify
end
Ability:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
if user.has_role? :admin
can :manage, :all
elsif user.has_role? :data_consistency
can :read, Booking
end
end
end

Issue with setting values to a model in rails

I have a model named User . I am using Devise for authentication.
The 'User' has many 'Profiles' and one default profile . So I have added a column called 'default' to the User model. I want to store the id of the default profile.
However the code fails at the current_user.default = statement.
Error - undefined method `default=' for #User:0x402c480
class User < ActiveRecord::Base
..........
has_many :profiles
...........
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :default
end
.......
class ProfilesController < ApplicationController
before_filter :authenticate_user! ,:except => [:show]
def create
#profile = current_user.profiles.new(params[:profile])
#profile.user = current_user
respond_to do |format|
if #profile.save
#current_user.default= #profile.id
............
end
How do I go about this ? Adding 'default' to the User model doesnt solve the problem.
I suggest you to use STI, it means add "type" column into 'profiles' table
class User < ActiveRecord::Base
has_many :profiles
has_one :profile_default
after_create do
create_profile_default
end
end
class Profile < ActiveRecord::Base
end
class ProfileDefault < Profile
end
def create
#profile = current_user.profiles.new(params[:profile])
#profile.user = current_user
respond_to do |format|
if #profile.save
...
end