Cancan : User can see its backoffice show view - ruby-on-rails

(Using rails 4, Cancan 1.6.10)
Hello all,
I have two UsersController: one for the front, the other under /backoffice for the back, and both are using the same User model.
I defined abilities for a "normal user" like this :
can(:manage, User) do |u|
u == user
end
whereas an admin user will be able to manage all users:
can :manage, User
I also have two "show" views, one in the front: /users/:id and one in the backoffice: /backoffice/users/:id
My problem is that a limited user can today see its profile through the backoffice URL, as he "can Manage this user". (And of course this is not acceptable)
I know this should not be too difficult to correct, but what solution would you use?

There seems to be a way to namespace the abilities. See the Wiki entry of the continued project CanCanCan here: https://github.com/CanCanCommunity/cancancan/wiki/Authorization-for-Namespaced-Controllers
What it basically does is override the Ability class to pass a namespace that is determined by the ApplicationController. The Ability class has two different rulesets depending on the namespace it is initialized with.

Related

How to create a rule-based access control unit for different user types

I'm trying to implement a Role-based Access Control Unit for my app but don't know how to do it given my constraints.
I use Devise for my sign in/up operations. I have only one model User but every user has a different user_type. I have eight unique user types with different authentication levels (integer).
These are the constraints on how users interact with the site:
Users with higher authentication levels can perform their related user types with lower authentication levels functions, i.e., access to their related controllers and do stuff.
Not every user type with the same authentication levels can access the same controller: user1 with auth_level = 2 can access to controller C1 but user2 with the same auth_level = 2 can access controller C2. Other way to say it is that they are doing different jobs at the same level.
Is there any gem to implement such things? I've looked into CanCan it does not satisfy my needs.
If there is no gem, what approach/design do you suggest to implement such access control unit?
I would suggest you use Route Constraints (http://guides.rubyonrails.org/routing.html#advanced-constraints) for implementing the controller access.
Using request.env["warden"].authenticate you can the authenticate the current user for the request inside the matches? method in your constraint class. If you use request.env["warden"].authenticate! you will even redirect the user to the login page, or display an appropriate error message (depending on your setup).
Since I asked this question a couple of days ago, for some time I wonder around and evaluating available gems, suitable for my purpose but I couldn't find any simple, easy-to-use and lightweight enough to import it into my application; So I accepted the fight and wrote my own gem named ACU (Access Control Unit), which satisfies my constraints in the question + it's very lightweight and easy-to-use and general for any purposes.
In this gem, every request is either gets passed through or not, and the access rules can cover any aspect of the rails app. the following is an example of the rules set, for more details you can see the documentation provided at here.
# config/initializers/acu_rules.rb
Acu::Rules.define do
# anyone makes a request could be count as everyone!
whois :everyone { true }
whois :admin, args: [:user] { |c| c and c.user_type == :ADMIN.to_s }
whois :client, args: [:user] { |c| c and c.user_type == :CLIENT.to_s }
# any request that doesn't match with a rule will be denied by default;
# but this can be configured to not to!
# the default namespace
namespace do
controller :home, except: [:some_secret_action] do
allow :everyone
end
controller :home do
allow [:admin, :client], on: [:some_secret_action]
end
end
# the admin namespace
namespace :admin do
allow :admin
controller :contact, only: [:send_message] do
allow :everyone
end
controller :contact do
action :support {
allow :client
}
end
end
end

Modelling multiple types of users with Devise and Rails 4

I have 3 types of "users"
Shop
Provider
Customer
The last 2 users will have similar attributes such as
First Name
Last Name
and so on
And the Shop user type will have the most contrast between types.
As far as behaviours they will all be quite different, although Provider and Shop will inherent many of customer behaviours.
It seems the behaviours can be dealt with CanCan as I've researched.
I'm now attempting to how I should authenticate these types.
I have looked at the STI model but I couldn't grasp where I would these extra attributes.
My mental model is as follower:
User is a table and model
The types are abstract models that inherit from this.
So I'm wondering, how do I add attributes such as Business address for just the Shop type?
Or is it that the User Table has a column called Type and that type is associated with these type tables? And within the type tables are the extra attributes?
Don't even bother bringing Devise into this; Devise is for authentication not authorization. Authentication is determining whether or not someone who visits your site is who you think they are, like logging in. Authorization is deciding whether or not a user is allowed to perform some sort of action, like creating a new post.
What you want to do is have some sort of system that assigns a normal user your three different types; CanCan will do something like that. One way to do this on your own is using a permissions number based system. Let's say normal users have permissions level at 100, shop has a level at 50, and provider at 25. Using this system you can determine what actions a user can perform without having to make separate models, which will make your schema unnecessarily complicated. Here's an example of how this would work with say the UserController:
def show
if current_user.permissions == 100
render "customer_show"
elsif current_user.permissions == 50
render "shop_show"
else
render "provider_show"
end
end
The easiest way to do this is to add a column to the user's table called permissions that defaults to say 100 when a new row is created. Here's what that migration would look like:
def change
add_column :users, :permissions, :integer, default: 100
end
As for authenticating, don't worry about it. Let devise do it's thing; every user, no matter what type, will login and sign up in the same way, maybe just having separate forms for each that has a hidden field to set the permissions level for that specific kind of user.
I know I'm late to the party but I'm putting this out for future SO searchers. You CAN authorize actions with Devise. If you have devise models for 'buyer' & 'seller' you can add 'buyer_signed_in?' for whatever action you only want buyers to be able to do. You can also do more specific role-based authorizations as well - check out Devise' page
All in all, Tsiege's solution sounds pretty interesting. Let us know if you had any success with it!

What is the purpose of Rolify?

Hi I'm using rolify and have just realized that I'm not actually taking advantage of it's full potential.
At present I am doing things in my controller like re-routing users if current_user.has_role? :whatever_role, and allowing users if they have whatever other role...
Someone asked a question on stackoverflow about rolify and when I got to trying to answer it, I realized that I'm doing it wrong.
Now, here is where my confusion starts... Inside of ability.rb I have:
user ||= User.new # guest user (not logged in)
if user.has_role? :consumer
can :manage, Review
else
can :read, Review
end
Now let's say I add the consumer role to a user:
x=User.last
x.add_role :consumer
# => #<Role id: 10, name: "consumer", resource_id: nil, resource_type: nil, created_at: "2013-04-18 23:00:46", updated_at: "2013-04-18 23:00:46">
Right, so the role is created. I can check this by doing:
x.has_role? :consumer
=> true
Now I would expect this to give management ability for reviews...
x.has_role? :consumer, Review
=> true
but not for other models... here I try products
x.has_role? :consumer, Product
=> true
Further, when I look at "resource roles querying" and try to query the applied roles for reviews I find no applied roles:
Review.first.applied_roles
=> []
Can someone please explain rolify to me. Thanks
My answer, garnishing the question from this reddit post:
Authentication is establishing a User is who they claim to be.
Authorization is establishing that a User can perform a given action, be it reading or writing, after they've established their identity.
Roles are just common patterns of authorization across users: this User can be authorized as such, that User can be authorized like this instead.
The ingredient you're missing here is Permissions: a relationship between an established Role and some controller action.
Roles themselves make no promises about what action a User can perform. And remember--authorization is all about actions. Roles generalize what kind of User you're dealing with. They exist to keep you from having to query every User for a giant laundry list of Permissions. They declare: this User is a Role! Of course they have Permission to do that!
There are many types of Permission. You can store them in a database if you want your sufficiently authorized Users to be able to edit them, along with your Roles if those too ought to be configurable. Or, if your User's Roles are sufficiently static, you can manage Permissions in advance with Ruby code:
When I want to have configurable Roles and Permissions, i.e. for a client application you're handing off to someone at completion of contract, I implement a User :has_many Roles and a Role :has_many Permissions with my own custom models, and then add a before_filter :authorize hook into my ApplicationController, and write an authorize method on it that knows how to martial these expectations, or render a 403 page for those people who insist upon manually entering urls to things they hope expose actions to things they oughtn't have access to.
When I want to just have configurable Roles, I use Ryan Bates' CanCan gem.
When I want to have predetermined Roles and Permissions, I use Rolify in conjunction with Nathan Long's Authority, to get delightfully flexible Class-based Permissions via Authorizer classes.
Both Roles and Permissions can be either class-based or instance-based, depending on your use-case. You can, say, with the abilities of rolify you've just discovered, decide that Users may only act as a Role in certain, instance-based circumstances. Or, general Roles of User may only be able to execute an action given the object they are trying to action is of a certain type.
To explore the permutation of these, assuming a blog application, following the formula
a User who is a/an Role class/instance can action a/an/all/any/that (class/instance) Permission:
Role class and Permission class:
A User who is an Admin can delete any Post.
Role class and Permission instance:
A User who is an Admin can edit all Posts that they approved to be published
This would be easier if published posts had an approved_by field pointing to a User id. (Use a state machine gem for this sort of situation.
Role instance and Permission class:
A User who is an Author of a Post can comment on any Post
Note that this sort of situation is rare, which is why there are no gems I've mentioned above to handle this situation, except for perhaps the ability to manage predetermined circumstances like Rolify and Authority in conjunction; or, if you must pass this decision on to your client, your own custom solution.
Role instance and Permission instance:
A User who is an Author of a Post can edit that Post.
TL;DR:
Rolify is just for roles: grouping Users by Permission: access to a controller action. You have yet to decide how you are going to manage Permissions.
I hope this helps your understanding of Rolify's position in the grand scheme of authentication and authorization!

Rails + Devise: How to restrict a user from editing records that do not belong to him

I'm using Rails, Devise and Mongoid.
A user can only have one project (has_one :profile) but all users (and non authenticated users) can see a list of projects (I have this working). When a user is logged in they see "edit and delete" buttons next to the projects (via wrapping those buttons in <% if user_signed_in? %>). However, a signed in user sees these buttons next to all project and can edit or delete them all.
How do I restrict a logged on user to only be able to edit only his project?
As a bonus, is it possible to show specific content or html around the project that belongs to the signed in user (such as text that says "this is your project" or an additional class around the project's html)?
CanCan is great when you have multiple users being able to modify the same resource, and you need to keep track of who has which permissions. But if it's strictly the case that only the user who owns the project can modify it, CanCan is probably overkill.
Hide the links in the view as suggested by iouri, and then in your controller do something like:
def edit
if current_user != #project.user
# Redirect them to an error page
else
# Render the view
end
end
Better yet, create a method like:
def user_owns_project?
#project.user == current_user
end
Then set up a before filter for edit, update and destroy to redirect to the error page if the user doesn't own the project.
EDIT: Your before filter will also ned to find the project and set #project. CanCan takes care of this for you too with load_and_authorize_resource, but I'd still avoid using it unless you need, or expect to need, fine-grained permissions control.
Devise is to control "authentication", this should not to be your responsibility.
You want to control "authorizations", for that CanCan is better.

Have different view based on type of user

I have a user model that has a few different types, i.e. there is a user attribute that is set as either a usertype1, usertype2, or usertype3. How can I route the user to a different page based on the user type? I am using devise for authentication.
If you want to redirect the user after logging in then check this devise wiki. The specific code is
# app/controllers/application_controller.rb
def after_sign_in_path_for(resource)
if usertype1?
some_url
elsif usertype2?
another_url
elsif usertype3?
some_other_url
end
end
But if what you're looking for is more generic then that you probably want to define roles and give users access to certain parts of the site based on their roles - also known as authorization. For that I'd recommend using cancan. You can also watch the railscast episode if you want more info.
Edit:
See also this so question

Resources