I have some records in my database which only the owning users, or administrators are allowed to view/edit etc.
What is the best way to secure these records on an application level so that only these people can see these records? I could do this manually with conditions, but I would need to ensure I use the same conditions on every single find().
Any ideas?
Scoping a finder to the current_user is useful in most instances, but does not account for a user being an administrator and having access to objects to which it is not directly associated.
Create a named scope within the model to restrict the selection to records owned by the user or any if the specified user is an administrator. The User model must implement a method called "is_admin?" that returns true if the User is considered an admin.
This way you can call:
my_widgets = Widget.accessible_by(user).find(:all, :conditions => ["created_at > ?", 1.day.ago.to_s(:db)])
class Widget
named_scope :accessible_by, lambda {|u|
conditions = nil
unless u.is_admin?
conditions = ["widgets.user_id = :user_id", {:user_id => u.id}]
end
return {:conditions => conditions}
}
end
I find the best way is to avoid finders on the actual model like this...
SecretRecord.find(:id, :conditions => 'user = ?', current_user.id)
and instead use the collections off the user object
class User
has_many :secret_records
end
current_user.secret_records.find(id)
This automatically scopes the select to those secret records that belong to the current user.
I'm assuming that you have a variable called current_user of type User provided by your authentication system (such as restful_authentication)
Put a filter in front of all controllers that can be used to access this information
current_user.admin? or params[:person_id].to_i == current_user.person.id
If this condition is not met redirect them somewhere else
Related
I am creating a messaging system in which a particular user may be a member of one or more organizations. Therefore, if they are signed in to an organization they should only be able to see conversations with users from the same organization but I can't seem to find a way to figure out how to specify that in the query. For example:
recipients = current_org.users
#conversations = current_user.mailbox.inbox.conversations.where(participants.include?(recipients))
..or something along those lines.
I didn't find a great way to do this, but this is what I did for future reference. I added a class_eval in an initializer file on Mailboxer's Receipts model, creating a slightly modified version of their .recipients scope. It checks to see if there is more than one in the collection and calls .id on it, which was an issue before. It also gets the base_class of the first of the collection since they will all be the same.
Mailboxer::Receipt.class_eval do
scope :recipients, lambda { |recipient|
if recipient.is_a?(ActiveRecord::Associations::CollectionProxy)
where(:receiver_id => recipient.collect {|x| x.id },:receiver_type => recipient.first.class.base_class.to_s)
else
where(:receiver_id => recipient.id,:receiver_type => recipient.class.base_class.to_s)
end
}
end
So for some reason, my client will not drop inactive users from their database. Is there a way to globally exclude all inactive users for all ActiveRecord calls to the users table?
EX: User.where("status != 'Inactive'")
I want that to be global so I don't have to include that in EVERY user statement.
Yes, you can set a default scope:
class User < ActiveRecord::Base
default_scope where("status != 'Inactive'")
end
User.all # select * from users where status != 'Inactive'
... but you shouldn't.
It will only lead to trouble down the road when you inevitably forget that there is a default scope, and are confused by why you can't find your records.
It will also play havoc with associations, as any records belonging to a user not within your default scope will suddenly appear to belong to no user.
If you had a simple setup with posts and users, and users had a default scope, you'd wind up with something like this:
# we find a post called 1
p = Post.first # <#post id=1>
# It belongs to user 2
p.user_id # 2
# What's this? Error! Undefined method 'firstname' for `nil`!
p.user.first_name
# Can't find user 2, that's impossible! My validations prevent this,
# and my associations destroy dependent records. Can't be!
User.find(2) # nil
# Oh, there he is.
User.unscoped.find(2) <#user id=2 status="inactive">
In practice, this will come up all the time. It's very common to find a record by it's ID, and then try to find the associated record that owns it to verify permissions, etc. Your logic will likely be written to assume the associated record exists, because validation should prevent it from not existing. Suddenly you'll find yourself encountering many "undefined method blank on nil class" errors.
It's much better to be explicit with your scope. Define one called active, and use User.active to explicitly select your active users:
class User < ActiveRecord::Base
scope :active, -> where("status != 'Inactive'")
end
User.active.all # select * from users where status != 'Inactive'
I would only ever recommend using a default_scope to apply an order(:id) to your records, which helps .first and .last act more sanely. I would never recommend using it to exclude records by default, that has bitten me too many times.
Sure, in your model define a default scope
see here for more info
eg
class Article < ActiveRecord::Base
default_scope where(:published => true)
end
Article.all # => SELECT * FROM articles WHERE published = true
As an alternative to #meagar's suggestion, you could create a new table with the same structure as the Users table, called InactiveUsers, and move people into here, deleting them from Users when you do so. That way you still have them on your database, and can restore them back into Users if need be.
I have users in my system that can elect to 'hibernate', at which point they can remove themselves and all of their associated records entirely from the system. I have queries all over my site that search within the User table and its associated tables (separated by as many as 5 intermediate tables), and none explicitly test whether the user is hibernating or not.
Is there a way to redefine the User set to non-hibernating users only, so all my current queries will work without being changed individually?
How can I most elegantly accomplish what I'm trying to do?
This is typically done with default scopes. Read all about them
Code from Ryan's site:
class User < ActiveRecord::Base
default_scope :hibernate => false
end
# get all non-hibernating users
#users = User.all
# get all users, not just non-hibernating (break out of default scope)
#users = User.with_exclusive_scope { find(:all) } #=> "SELECT * FROM `users`
I have a model called Stem. I need a 'thumbs up' feature, so I have created a second model called Thumb, which consists of stem_id and user_id.
I'm also using the restful authentication plugin for user credentials.
I have the 'thumbs up' button working, which adds a row to the thumbs table fine, but I'd like to be able to check if the currently logged in user has already given a thumbs up to this particular stem.
I tried adding this to the Stem model:
def thumbed
Thumb.count_by_sql ["SELECT COUNT(*) FROM thumbs WHERE user_id = ? AND stem_id = ?", current_user.id, self.id ]
end
The problem here is that the stem model has no access to the current_user variable the the controllers have.
Is there a way I can get access to this property, or alternatively, is there another way I could go about checking this? I was hoping to get this as a property in the model because the stems are passed over to a Flex app using RubyAMF.
Thanks!
Your controller knows the current_user, and your Stem model probably shouldn't. You can, however clean up your code and avoid hard-wiring SQL with a named_scope and pass the user into that.
#thumb.rb
named_scope :for_user_id, lambda {|id| {:conditions => {:user_id => id}}}
#stem.rb
def thumbed_by_user(user)
self.thumbs.for_user_id(user.id).count > 0
end
# some controller
stem = some_method_that_gets_a_stem
is_already_thumbed = stem.thumbed_by_user(current_user)
Can you pass the current user to thumbed? Where are you calling it from?
BTW, you could try to simplify all of this using associations. I'm not 100% sure I understand what you're trying to do, but it sounds like you have the following...
class Stem
has_many :thumbs
end
class User
has_many :thumbs
end
class Thumb
belongs_to :user
belongs_to :stem
end
Then you can use find though associations to get at your thumbs without resorting to direct SQL. Check out this RailsCast: http://railscasts.com/episodes/3-find-through-association
What ended up working for me first was something like this:
I added these methods to my stem model:
def thumbed_by? usr_id
Thumb.count(:conditions => {:user_id => usr_id, :stem_id => self.id}) > 0
end
def owned_by? usr_id
self.id == usr_id
end
I also added this:
attr_accessor :thumbed, owned
Then in my show action where these were needed, I added the following lines:
#stem.thumbed = #stem.thumbed_by? current_user.id
#stem.owned = #stem.owned_by? current_user.id
This works exactly how I would like (the Flex app is already interpreting it as properly), but is there a way I could shorten this?
I have a web application with users and their documents. Each user can have many documents:
user.rb:
has_many :documents
document.rb:
belongs_to :user
document_controller.rb:
def index
#documents = Document.find(:all)
end
I am using the restful_authentication plugin. Here is my question: How do I get the controller to only show documents that belongs to each user? Right now it shows all the documents for all the users.
I am using the latest version of Rails.
You set a relationship in your User class to your Document class. This will automatically add a method to your User objects that returns a list of all documents related to a particular user:
def index
#documents = #current_user.documents
end
See the documentation for other automatically added methods.
Try this:
def index
#documents = Document.where(:user_id => current_user.id)
end
def index
#documents = Document.find(:all, :conditions => {:user_id => session[:user_id]})
end
Take a look here in the rails API in the Association Join Models section.
However be aware Restful authentication won't control access in order to limit the users to only their own records particularly with restful routes. They can still view other users' records by entering values in the urls once they are logged in.
For that you might want to look into Restful ACL