Rails fragment caching with personalization - ruby-on-rails

My site has a top navigation menu bar which has infrequently changed contents and it's therefore a good candidate for fragment caching. But if a user is signed in, part of the menu bar will have the user's name in it, and is therefore personalised and can't be cached.
I can either:
Fragment cache the static part and serve up the personalised part separately. That seems pretty easy.
Cache the lot, possibly in memcached or CloudFront, keep the user's name in the session, and use JavaScript to extract the user's name from the session and personalise the page at the client end.
What is the best option to go with? Preferably based on personal experience of similar situations.

Try this:
##in user.rb to cache user
after_save :invalidate_cache
def self.serialize_from_session(key, salt)
single_key = key.is_a?(Array) ? key.first : key
Rails.cache.fetch("user:#{single_key}") do
User.where(:id => single_key).entries.first
end
end
def invalidate_cache
Rails.cache.delete("user:#{id}")
end
Credit: Rails Devise: How do I (mem)cache devise's database requests for the user object?

Related

Some questions about security in Rails 5

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

How to store where a new user was referred? Using Rails + Devise

I have a rails app that uses devise. I'm curious to know, is it possible in the User table to somehow track where a new user came from, the HTTP referrer?
I'd like to know which came from Facebook, Twitter, LinkedIn, Google+ in order to track a viral loop.
Any ideas? Seen anyone do this? Possible? Where should this live in the rails app? Still very new. Thanks
It could be done like this. May require some tweaking and fixing but You'll get an idea
Make before filter for Application controller, you will call it for any action
def landing_filter
if from_other_site(request.referrer) and !session[:referer].blank?
session[:referer] = request.referrer #you don't want to delete first entrance
end
end
from_other_site should be the method which will check domain name in referrer url, if it match your then return false, otherwise true
in devise/registration/new.erb.html view add in form hidden field
<%= f.hidden_field :referrer, session[:referrer] %>
and don't forget to add migration with new database field for user
Save referer somewhere and after creating a user copy information to user table. Using session to save referer works but permanent cookies are better. Cookies can persist the information even when user closes browser and comes again in the next day.
# so basically in ApplicationContreller using before_filter
def referer_before_filter
if cookies[:referer].blank?
cookies.permanent[:referer] = request.env["HTTP_REFERER"] || 'none'
end
end
# and in signup action somewhere else saving that information
#user.referer = cookies[:referer] # or maybe to some other table
Instead of modifying every action you can also use rails sweepers/observers to handle automatic saving every time an object is created.
A good gem to automatically save referer and other needed information is
https://github.com/holli/referer_tracking . You can choose do you want to save information manually or use sweepers to do saving automatically.

Preventing discoverability in a RESTfully routed model

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.

My cookie token is strong enough in order to use that for user authentication purposes?

I am running Ruby on Rails 3 and I would know if the code that I am using in order to set the cookie value for user authentication purposes is strong enough.
In my model I have:
require 'digest'
class User < ActiveRecord::Base
...
def make_cookie_id_salt(string)
secure_hash("#{self.id}--#{string}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
In my controller I have:
cookies.signed[:current_user_id] = { :value => [#user.id, #user.make_cookie_id_salt(#user.id)], :expires => 15.days.from_now }
Is it strong enough? If no, how I can improve that (make an example!)?
Everything that gets put into cookies is stored as plain text.
If you set a cookie, and then check the cookies in your browser you will notice (in your case the cookie name would be current_user_id) that it is represented by a string of characters like: G8gcm9sbCB5b3VyIG93biBhdXRoIHRvIGt... (Not quite plain text, right? It is actually Base64 encoded, but you can easily read it - require('base64'); Base64.decode64(string)).
Rails stores a special _yourapp_session cookie that is used to check the cookies validity. If for example someone/something was trying to modify it, it would get rejected.
Now in your case it doesn't really matter if you try to hash something in the cookie or not.
It is just used for authentication (to look up a user in the database by his id) and you are not storing any unique secret data (Which you should not place in a cookie anyway, but it would be the only reason to hash something)
Of course someone could steal the cookie of a user (if he used a public computer and hasn't cleared his cache, etc.) and log in, but there's no way to prevent that (No matter what kind of hashing was used to obfsucate it)
In conclusion you should be fine with what you have.
Rather than try to create your own, I suggest using the Authlogic gem. In a few minutes of configuration you get a complete authentication solution, including cookies and much more. If you really want to roll your own, install the Authlogic gem and take a look at how they do it.
Devise is another option. It's extremely configurable, pretty DRY, with exhausting wiki.
For now-days I prefer it over Authlogic.

Is it possible to specify two root pages in Rails (one for anonymous user another for logged in user)

I am building a product that has static pages and dynamic pages(product related). Both category of pages have different release life cycle. The marketing team working with the designer, release the static pages and the product pages are released by the engineering team.
The static pages reside in public/home and they are self contained. They don't need access to the Rails infrastructure other than providing links.
In this setup, I am trying to implement the following behavior:
When an un-authenticated visitor launches http://www.xyz.com, the user should be taken to the static landing page.
When an authenticated visitor launches http://www.xyz.com, the user should be taken to the product landing page (LandingsController, index action).
In my current implementation, I check if the user is authenticated in the Rails world and render the static page OR the product page.
I want to know the following:
1) How do you handle such scenarios?
2) Is there a way to avoid entering the Rails stack for static home page.
3) Is there a customization for the root_path method to return different root based on the context
1) How do you handle such scenarios?
The common answer would look like this:
class LandingsController < ApplicationController
before_filter :login_required
def index
...
end
...
private
def login_required
if not_logged_in? # This methods depends on your authentication strategy
send_file "/your/static/path/#{params[:action]}", :type => "application/html charset=utf8;"
return false # Halt chain
end
end
send_file documentation
And, depending on the correspondence between each of your actions and your templates, you can further abstract the login_required method into the ApplicationController, and validate if the file exists.
2) Is there a way to avoid entering the Rails stack for static pages
Yes. You have to take my word for it, because I haven't done it myself, but you can use a Rack middleware to do that. Here is an example of how to do something similar, with the exception that instead of a redirect, you would serve the file statically (just set the headers and the results of File.read as content) This depends on the authentication library you're working with, though.
3) Is there a customization for the root_path method to return different
root based on the context
You cannot define a conditional route (that is, defining multiple routes in the routes.rb file), but you can override the root_url method in ApplicationController, assuming you are using a named path root in your route definitions. Something like
class ApplicationController
def root_url(*options)
if logged_in?
"/return/something/custom"
else
super(*options)
end
end
end
This, however, sound really a bad idea, since 1) You should point to the same url, and let the controller handle the request (your links should be blind of where to take you), and 2) It may potentially break other stuff that rely on the root_url and root_path methods.
Unfortunately, Rails' routing can only route requests to different controllers based on something in the request, making the per-request session data just out of reach. Your current implementation is certainly the most common strategy. I am guessing something like this:
def index
if logged_in?
# any logged in logic you need.
else
render :file => 'public/home', :layout => false
end
end
The only way to refactor this to make it feel less "icky" is to move that render call to a before_filter. Since the filter will have rendered?, your action won't get invoked at all. Of course, you could also choose to redirect_to another location for authenticated (or non-authenticated) requests in a before filter, which would solve the problem entirely.
The only thing you could do would be based on the non-existence of the session cookie.
Write a middleware component or Rack application (etc.) that explicitly handles the request if no session cookie is present. Similarly, you could use middleware to re-write the request, and then pass it onto the application layer.
Use a similar strategy as #1, but do it via web server configuration (Apache or nginx), avoiding the Rails app entirely.
But, it's definitely possible for someone to have a session and yet not be logged in (e.g. if they went to another page which you didn't handle this way), or even have invalid session data, so you wouldn't be able to actually eliminate the code you have now. These changes would only serve to increase the performance of the session-less requests, but unless those pages are causing a significant problem (which I doubt), so I would not recommend doing so.

Resources