I have a rails app that is multi tenant. It has resources for Account, Client, and Transactions. An Account has many Clients has many Transactions. I want to make sure I can never accidentally do Client.find, Transaction.find, etc (everything must go through the_account.clients.find, or client.transactions.find, etc). I want to do this so I don't accidentally show the wrong users things from the wrong account by forgetting to first select on the Account.
Is there a way to disable the Client.find (Client.find_by_name, Client.find_by_etc) but still allow the_account.clients.find?
I don't know about completely disabling Client.find, but I'd probably put a method into the bottom of each of my Client and Transaction controllers to handle lookups. Here's what the Client code would look like:
def collection
if current_user.admin?
Client.all
else
current_user.account.clients
end
end
Then, everywhere in my controller I'd ordinarily use Client.find, I'd substitute collection instead. Gets you all the right records without risk of over-exposure.
Related
I've got a number of security concerns about my current application and wondering if I am leaving myself open to abuse, in the following arenas.
a) .My main access control method is by maining a current_user, current_company current_project method in my application controller. These methods return object based on stored session keys established when a user logs in and cleared when they log out. I.e if I want to know something about the current user, I can call "current_user.role" or if I want see whether the account a user is trying to change belongs to him, I check whether the associated account id which is requested in the url actually belongs to that user, essentially as follows
in Account controller
def account_info
redirect_to login_path if !user.logged_in
account_id=params[:account_id]
#account = Account.find(account_id)
unless account_belongs_to_user(account_id)
redirect_to unauthorized_path
end
end
In my application controller, when a user is initially authenticated, I do something like this:
session[:current_user_id] = user.id
and clear that session key when the user logs out.
Then when account is requested, and account_belongs_to_user is called, the application controller processes it, more or less like this:
def account_belongs_to_user(account_id)
account = Account.find(account_id)
return account.user_id==session[:current_user_id]
end
So I guess my security scheme ultimately relies on whether the session data is secure and not trivially spoofable.
b) When I render pages I sometimes pass objects which have senstive data to my erb pages to generate the page text.
For example, I might pass a "company" object (ActiveRecord) to the view to generate an invoice screen. But the company object, passed as #company, has a lot of sensitive data like access keys and the like. Not really being fully aware of the the internals, if I don't specifically include something like:
<%= #company.access_token %>
on my web page, can I be confident that the attributes of #company won't somehow be passed into the browser unless I specifically ask for them to be rendered on the page?
This is obviously an issue when using rails to serve data for say, AngularJS single page applications, as everything I pass for Angular to render the page I assume is probably accessible to an evil-doer even if not on the page itself, but I'm hoping that's not the case with pages generated server side by rails.
This may be a naive question, but thanks as I just want to be certain what I am doing before start spilling secrets all over the place.
put an authentication for the token using active_record callback
https://guides.rubyonrails.org/active_record_callbacks.html
The logic of the application that I currently work on demands a Payment mustn't be editable if its status is open. I see two ways of implementing this:
1 A routing constraint like:
constraint: lambda { |req| Payment.find(req.id).status != 'open' }
2 A simple condition in PaymentsController#edit:
if #payment.status == 'open'
redirect_to payments_path
end
What option should I go for? Which is more suitable and clean, Rails-ish? Is there any other option? If I go with the first option and have a resources :payments, how can I add the constraint only for the edit route?
As to the Rails way of solving this it is actually none of your suggestions.
Routing - You routes should just simply declaratively state the RESTful interface of your application. They should not be aware of the current request unless absolutely necessary.
Controller - Adding business logic in the controller will bloat your controllers and violates DRY.
In MVC the model is in charge of enforcing the business logic. You could handle this through a custom validation for example:
class Payment
validates :cannot_be_edited_when_open, on: :update
def cannot_be_edited_when_open
errors.add(:status, 'is open. Cannot edit this record.') if self.open?
end
end
This will cause any call to .update to fail - which means that you probably will not need to change anything in your controller.
Edited
Another place to handle this would be on the authorization layer - the key difference here is how the feedback should be handled.
A validation failing will just re-render the form (422 Unprocessable Entity for an API) while an authorization error should clearly tell the user "no you don't have permission to do that - and changing the input won't change that" (403 Forbidden).
To setup the rule in CanCan you would do:
can [:edit, :update], Payment do |payment|
payment.status !== 'open'
end
You could also possibly set this up with hash condition instead of a block if your business logic allows it:
can [:edit, :update], Payment, status: 'not-open'
If the rule you specified is a business logic, i.e. no Payment object should be updated with the status open, then, the correct way would be to add that logic to your model. You can use before_validation or before_update callbacks. Also, don't display any means to edit (link, button, etc.) your Payments that have status open. If a user somehow gets to your form, display it, but then validation in your model will not let them save it. I think, in this case, everyone is responsible for their own responsibilities.
But, if you have to choose from the 2 options you provided, I would go with the second one. Your first option, having the business logic in routes, doesn't seem not right. It does not belong there, IMO. The second option is better, but the drawback is that you and your team members will still have to remember that they have to check the Payment object's status attribute does not have some value before touching it. Obviously, someone will forget to do that at some point. So, in the long run, your system (models in your system) will have a corrupted state.
On my ruby on Rails app using ActiveAdmin, I wish to delete not only the 30 Users displayed but all the 456 users (that's an example of course).
When I select "select all' and then confirm deletion, it only deletes the 30 users visible on the current screen page.
I want to select ALL users (across all view pages, not only the one I currently see), and then manually deselect the first 4 users (or any I would manually pick on the current view page). So not really deleting ALL users. that's my problem.
How to customize ActiveAdmin to be able to do this ?
Maybe something like this would work:
https://github.com/activeadmin-plugins/active_admin_scoped_collection_actions
Plugin for ActiveAdmin. Provides batch Update and Delete for scoped_collection (Filters + Scope) across all pages.
If you want to delete some users from a list of all of them, I suggest you to write a custom active admin action. Minimize your markup, make it easy to render for browser and prepare for the worst. If you have 1 million records, there is no way it will work properly, there is no solution for that.
I suggest you to accept the fact that user will delete records by using search, probably and if you literally want to be able to delete all you can provide a custom button delete all that will do that for you.
The alternative is write a custom active admin action with a lot of javascript to provide pagination. It's still a lot of custom code, no generic solution provided.
Last alternative, you can disable pagination for that active admin page, but you may have a lot of problems loading the entire table every time
You can override the default batch action to destroy/delete all the users like this:
ActiveAdmin.register User do
batch_action :destroy do |ids|
User.delete_all
redirect_to collection_path, alert: "Deleted all the Users!"
end
end
See this for more information.
So lets say I have a form for submitting a new post.
The form has a hidden field which specify's the category_id. We are also on the show view for that very category.
What I'm worried about, is that someone using something like firebug, might just edit the category id in the code, and then submit the form - creating a post for a different category.
Obviously my form is more complicated and a different scenario - but the idea is the same. I also cannot define the category in the post's create controller, as the category will be different on each show view...
Any solutions?
EDIT:
Here is a better question - is it possible to grab the Category id in the create controller for the post, if its not in a hidden field?
Does your site have the concept of permissions / access control lists on the categories themselves? If the user would have access to the other category, then I'd say there's no worry here since there's nothing stopping them from going to that other category and doing the same.
If your categories are restricted in some manner, then I'd suggest nesting your Post under a category (nested resource routes) and do a before_filter to ensure you're granted access to the appropriate category.
config/routes.rb
resources :categories do
resources :posts
end
app/controllers/posts_controller
before_filter :ensure_category_access
def create
#post = #category.posts.new(params[:post])
...
end
private
def ensure_category_access
#category = Category.find(params[:category_id])
# do whatever you need to do. if you don't have to validate access, then I'm not sure I'd worry about this.
# If the user wants to change their category in their post instead of
# going to the other category and posting there, I don't think I see a concern?
end
URL would look like
GET
/categories/1/posts/new
POST
/categories/1/posts
pst is right- never trust the user. Double-check the value sent via the view in your controller and, if it does't match something valid, kick the user out (auto-logout) and send the admin an email. You may also want to lock the user's account if it keeps happening.
Never, ever trust the user, of course ;-)
Now, that being said, it is possible to with a very high degree of confidence rely on hidden fields for temporal storage/staging (although this can generally also be handled entirely on the server with the session as well): ASP.NET follows this model and it has proven to be very secure against tampering if used correctly -- so what's the secret?
Hash validation aka MAC (Message Authentication Code). The ASP.NET MAC and usage is discussed briefly this article. In short the MAC is a hash of the form data (built using a server -- and perhaps session -- secret key) which is embedded in the form as a hidden field. When the form submission occurs this MAC is re-calculated from the data and then compared with the original MAC. Because the secrets are known only to the server it is not (realistically) possible for a client to generate a valid MAC from the data itself.
However, I do not use RoR or know what modules, if any, may implement security like this. I do hope that someone can provide more insight (in their own answer ;-) if such solutions exist, because it is a very powerful construct and easily allows safe per-form data association and validation.
Happy coding.
I have a model in my database whose 'show' action is open to viewing at URLs like:
mysite.com/project/12
mysite.com/project/14
The way my system is set up, there are a couple of defined methods through which these should be accessible:
A custom route I've set up is accessible to any visitor (registered or unregistered) who has this route. As an example, this custom route might be mysite.com/companyname/projectid, which the company might pass out itself to certain people it wants to have access. Note that this custom route runs a separate controller action, which sets some internal analytics then redirects to the show action.
Direct access when linked to by a registered user's home page.
I want to restrict the ability to start with mysite.com/project/14 then simply change the IDs, thereby seeing any project. How can I do this?
Clarification
My goal with this question is not just to obfuscate record IDs to make discovering certain records harder. Instead, I would like there to be only two allowable means of accessing project/12:
A user clicks on a link we provide on their home page (how can I ensure this link alone reaches project 12?)
A user or simple visitor is redirected here by another (specific) controller action.
Typing in project/12 directly should not be possible. At the moment, I imagine the best way to do this would be for the two methods above to pass a code that gets picked up by the project#show action. I just don't know how to implement this and if there are potential drawbacks.
Whatever you come up with - it is going to end up being security through obscurity due to this simple requirement:
A user clicks on a link we provide on
their home page (how can I ensure this
link alone reaches project 12?)
What you can do, however, is make it difficult to just straight-up guess the correct URL for the project.
My thought would be to give every Project a unique 'token' - If you are not logged in as the owner of the project, then you must use the token to access it.
For instance, in your project model you could have this:
class Project
before_create :set_public_token
protected
def set_public_token
# Randomizes a 20-digit long hex code
self.token = ActiveSupport::SecureRandom.hex(20)
end
end
Then, in your project's show action you would need to have this:
class ProjectsController < ApplicationController
def show
#project = Project.find(params[:id])
# Obviously you would changed signed_in? to whatever method
# you have that verifies someone is logged in
if !signed_in? || #project.owner_id != current_user.id
raise "Unauthorized Access" if #project.token != params[:token]
end
end
end
Then the owner of the project can share the 'public' link of their project to people they want to have access to it, which would look something like this:
www.example.com/projects/14?token=3jks83kasdkt84h6cd86
Again, anyone with that url could access the project, and I don't think you will be able to sanely get away from that - but it makes it a lot more difficult to do so.
This is the same concept many password reset functions work. Anyone with access to the password reset token could reset your password after you've requested a password. But knowing what token to use will take you ages (Make the token longer to make it harder to bruteforce).
That personally is how I would handle it, and how I've seen this sort of thing handled in the past (photobucket, private gists on github, etc)
The easiest way is to associate a project with a user or account, then require authentication when browsing your non public routes. If you setup an association, you can then do:
#user = current_user
#project = #user.projects.find(params[:id])
This will ensure that a given user can only find projects they 'own'.
If you don't want authorization, and just want obfuscation, you won't be able to use the 'id' alone in the route (as it is sequential). You could either pair the 'id' with a random key stored in the model (/projects/1?key=1234) or use a GUID instead of an id.
OK so another attempt now that I sort of understand.
First in your public controller action you want to do something like this:
def public_redirect
session[:authorized_for] = params[:id]
redirect_to resource_show_path(params[:id])
end
Now in your private controller:
def show
#resource = current_user.resources.find params[:id]
if #resource # authorized
respond_with #resource # ok
elsif session[:authorized_for] == params[:id] #redirected from public route
#resource = Resource.find params[:id]
respond_with #resource # ok
else
raise NotAuthorizedException # not ok, do something
end
end
This relies on sessions. This is certainly hackable, but it would be much harder then figuring out the public route. See http://guides.rubyonrails.org/security.html#sessions.
You can reuse the session technique for other similar needs (like for links from home pages where you can't verify the user from the controller, etc.
I have a project that has a similar requirement. Now first I feel the need to say that this is security by obscurity - and thus not much security at all. But for some apps that can be OK.
I have a on create callback on my model that generates a random string (or number) that I use as my ID - thus it is impossible hard to guess another resource's path.