this idea ... user on his page can see all of his data, while other users only specific attribute (example: email).
I use "gem cancan" Where necessary to prescribe the "select"? rails ought to show certain field.
The quickest solution I can think of off the top of my head would be to to divide up the user#show page into partials. Then, you could run can? :read, :attribute for each individual partial, and setup abilities for each :read, :attribute either for all users or for just #user == user in ability.rb.
Let me know if this doesn't make sense, and I'll try to pseudo-code an example
Related
I have two models: User and Dream. User has_many :dreams and Dream belongs_to :user. I have two controllers for users and dreams as well. I want to create new Dream with form_for with a reference to a particular User. But it shouldn't be specified by me, it should somehow (and this is where I'm stuck) track on which User's page I pressed "Create new dream", and create it for this User.
I managed to do this with using only Users Controller (for managing Dreams as well), with passing user_id parameter in URL, and with hidden field. But I realize this is not a great way to do it, because anybody can edit URL or hidden field's value and create new Dream for any User.
I'm looking for a way to make two controllers communicate with each other under the hood. Or maybe this is not what I need and I miss some conceptual nuance of MVC or whatever. I need someone to push me in the right direction.
If you have login functionality, then you can define a method in ApplicationController that returns the user that matches the session token provided by the request, and then do current_user.dreams.create(params) which will create the dream with reference to whichever user is returned by current_user and never put any information about the user into the client's view.
If you don't have login functionality, then I wouldn't worry about the fact that it can be edited, since at that point anyone would be able to create a dream for any other user anyway just by visiting that page.
edit: e.g. I've often used something like this:
def current_user
#current_user ||= User.find_by(session_token: session[:session_token])
end
You can add an optional user_id param to the new action in the DreamsController. It that param build the new dream on that User.
def new
user_id = params[:user_id]
#dream = if params[:id].present?
User.find(params[:id]).dreams.build
else
Dream.new
end
end
Then your link will be something like
<%= link_to "Create New Dream", {:controller => "dream", :action => "new", user_id: user[:id]}%>
I think that is the way things are done of such kind.
For:
anybody can edit URL or hidden field's value and create new Dream for any User
If you have single user through out the app, he can do anything ; he is superAdmin
If multiple users
they must have different roles like; moderator, superadmin, admin
in such case; authentication alone cannot do all the things you have to use some method of authorizationmechanism like cancan, cancancan, pundit,etc.
these basically prevents any user to do something nasty they are unauthorized.
If any user modifies the hidden field value, you have to check if he is authorized to do so or not.
I am writing a rails application for an organization. Every user may have 1 or more roles and can only access certain controller actions depending on those roles.
For example, only admins can create, destroy and update certain fields of Users. Also, there are Teams which each have a team leader, and only the team leader can update certain information about the Team (like the member list, for example). However, Admins are the one who assign the team leader in the first place.
The specific details of my scenario are not important, I merely hope I described the situation where there are many different roles and permissions.
My question is: what gem to use? My first thought was CanCan, but the last commit was almost a year ago and there is no mention of Rails 4 compatibility. Is there a currently maintained alternative?
Your first guess was right, use cancancan and you'll be good with it.
EDIT Jul 24, 2015
I've been using cancancan for a long time now and it was always working great. I've recently started working on a project where Pundit is used for authorization.
It is awesome. It prompts you to define the policy for each resource and it feels more natural than one bloated Ability class.
For bigger projects, I would definitely recommend Pundit.
To control access to actions I'd recommend Action Access, it boils down to this:
class UsersController < ApplicationController
let :admin, :all
let :user, [:index, :show]
# ...
end
This will automatically lock the controller, allowing admins to access every action, users only to show or index users and anyone else will be rejected and redirected with an alert.
If you need more control, you can use not_authorized! inside actions to check and reject access.
It's completely independent of the authentication system and it can work without User models or predefined roles. All you need is to set the clearance level for the current request:
class ApplicationController < ActionController::Base
def current_clearance_level
session[:role] || :guest
end
end
You can return whatever you app needs here, like current_user.role for example.
Although it isn't required, it bundles a set of handy model additions that allow to do things like:
<% if current_user.can? :edit, :team %>
<%= link_to 'Edit team', edit_team_path(#team) %>
<% end %>
Here :team refers to TeamsController, so the link will only be displayed if the current user is authorized to access the edit action in TeamsController. It also supports namespaces.
You can lock controllers by default, customize the redirection path and the alert message, etc.
It's very straightforward and easy, I hope you find it useful.
Something that was suggested to me that we are now using is the petergate gem. Easy to use and very clean looking with a great rails feel.
Works well with devise.
Here is some examples from the readme.
If you're using devise you're in luck, otherwise you'll have to add following methods to your project:
user_signed_in?
current_user
after_sign_in_path_for(current_user)
authenticate_user!
This comes in your User.rb. Adding more roles is as easy as adding them to the array.
petergate(roles: [:admin, :editor], multiple: false)
Instance Methods
user.role => :editor
user.roles => [:editor, :user]
user.roles=(v) #sets roles
user.available_roles => [:admin, :editor]
user.has_roles?(:admin, :editors) # returns true if user is any of roles passed in as params.
Controller access syntax.
access all: [:show, :index], user: {except: [:destroy]}, company_admin: :all
I have a table output from entries using the rails generated scaffold: CRUD ops.
If I want to make another action on the table like the default "Show, Edit, Destory" like a library book "check in", that will update the status to "checked in"...
What would be the proper way to use the model and controller to update? (Using mongodb)
Better stated: What's the best way to have many custom actions? Think of it like many multi purpose "Facebook Likes".
On the table, list of actions "Punch this", "Check out this"...
There are lots of ways to handle this, but I typically like to isolate actions like this in their own controller action with it's own route.
Model
To keep things tidy I recommend adding a method to the model that updates the attribute you are concerned about. If you aren't concerned with validation you can use update_attribute. This method skips validations and saves to the database
class LibraryBook < ActiveRecord::Base
def check_in!
self.update_attribute(:checked_in, true)
end
end
View
You'll need to update the index.html.erb view to add the link to update the individual record. This will also require adding a route. Since you are updating the record you will want to use the PUT HTTP verb.
routes.rb
resources :library_books do
match :check_in, on: :member, via: :put # creates a route called check_in_library_book
end
index.html.erb
Add the link
link_to check_in_library_book_path(library_book), method: :put
Controller
Now you need to add the action within the controller that calls the #check_in! method.
class LibraryBooksController < ApplicationController
def check_in
#library_book = LibraryBook.find(params[:id])
if #library_book.check_in!
# Handle the success
else
# Handle the Failure
end
end
end
In my opinion, the best way to handle status workflows like this is to think about it in terms of events, and then just think of status as most recent event. I usually create an event_type table with a name and code (so, e.g. Check In and CHECK_IN for name and code, respectively), and then an event table with an event_type_id, timestamp, and usually some kind of user id, or IP address, or both.
Then you could say something like this:
class Thing < ActiveRecord::Base
has_many :events
def status
events.order("created_at DESC").first.event_type.name
end
end
There are also "audit trail" gems out there, but in my (limited) experience they aren't very good.
This doesn't speak to MongoDB, and may in fact be incompatible with Mongo, but hopefully it at least points you in the right direction or gives you some ideas.
I've modified my devise table, User, to have a clearance column. This column is a number (1-3) that represents a user's permissions (read, read/write, full control). Unlike all the examples I'm reading, my Clearance (Role in the examples) is not a separate table with a relationship but is its own column in the Devise table (User). Its default value is 1.
My ability.rb looks like this:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.clearance.equal?("2")
can :create, Post
can :manage, Post, :user_id => user.id
else
can :read, :all
end
end
end
I've written this off of other examples I've seen (I'm really new to Rails but trying to not ask for help unless absolutely necessary) with the intent that users with a clearance value of 2 can create posts and manage only their posts. I've also included that, because I haven't written the code for 1 and 3 yet, that all other clearance numbers can read everything.
Apperantly, however, Cancan thinks that my user (confirmed clearance level of 2) falls under the "else" provision and therefore I can only read posts. I get a You are not authorized to access this page message if I try to make a new one.
I'm lost. Help?
May this be that the clearance column is an integer? in such a case you should write user.clearance == 2 and not as you wrote.
I've been reading up on rails security concerns and the one that makes me the most concerned is mass assignment. My application is making use of attr_accessible, however I'm not sure if I quite know what the best way to handle the exposed relationships is. Let's assume that we have a basic content creation/ownership website. A user can have create blog posts, and have one category associated with that blog post.
So I have three models:
user
post: belongs to a user and a category
category: belongs to user
I allow mass-assignment on the category_id, so the user could nil it out, change it to one of their categories, or through mass-assignment, I suppose they could change it to someone else's category. That is where I'm kind of unsure about what the best way to proceed would be.
The resources I have investigated (particularly railscast #178 and a resource that was provided from that railscast), both mention that the association should not be mass-assignable, which makes sense. I'm just not sure how else to allow the user to change what the category of the post would be in a railsy way.
Any ideas on how best to solve this? Am I looking at it the wrong way?
UPDATE: Hopefully clarifying my concern a bit more.
Let's say I'm in Post, do I need something like the following:
def create
#post = Post.new(params[:category])
#post.user_id = current_user.id
# CHECK HERE IF REQUESTED CATEGORY_ID IS OWNED BY USER
# continue on as normal here
end
That seems like a lot of work? I would need to check that on every controller in both the update and create action. Keep in mind that there is more than just one belongs_to relationship.
Your user can change it through an edit form of some kind, i presume.
Based on that, Mass Assignment is really for nefarious types who seek to mess with your app through things like curl. I call them curl kiddies.
All that to say, if you use attr_protected - (here you put the fields you Do Not want them to change) or the kid's favourite attr_accessible(the fields that are OK to change).
You'll hear arguments for both, but if you use attr_protected :user_id in your model, and then in your CategoryController#create action you can do something like
def create
#category = Category.new(params[:category])
#category.user_id = current_user.id
respond_to do |format|
....#continue on as normal here
end
OK, so searched around a bit, and finally came up with something workable for me. I like keeping logic out of the controllers where possible, so this solution is a model-based solution:
# Post.rb
validates_each :asset_category_id do |record, attr, value|
self.validates_associated_permission(record, attr, value)
end
# This can obviously be put in a base class/utility class of some sort.
def self.validates_associated_permission(record, attr, value)
return if value.blank?
class_string = attr.to_s.gsub(/_id$/, '')
klass = class_string.camelize.constantize
# Check here that the associated record is the users
# I'm leaving this part as pseudo code as everyone's auth code is
# unique.
if klass.find_by_id(value).can_write(current_user)
record.errors.add attr, 'cannot be found.'
end
end
I also found that rails 3.0 will have a better way to specify this instead of the 3 lines required for the ultra generic validates_each.
http://ryandaigle.com/articles/2009/8/11/what-s-new-in-edge-rails-independent-model-validators