Multi-tenancy Rails Appartment generic elevator - ruby-on-rails

I use Apartment work in my Rails 4 application.
I followed this step :
1)
#config/initializer/apartement.rb
require 'apartment/elevators/generic'
2)
#config/initializer/application.rb
config.middleware.use 'Apartment::Elevators::Generic', Proc.new {|request|
remember_token = request.cookies['remember_token']
if remember_token.blank?
nil
else
User.find_by(remember_token: remember_token).backoffice_company_id
end
}
This works fine but it's not efficient because a request is done on the database on each request on the application.
How can I avoid this database access when the remember_token is unchanged from the last request (because it's the same user) ?
I tried to store this in a session variable but it's seems to be global à this level (application.rb) when 2 users use the application.
Thanks
Bruno

Came across this setting something similar up today for specific subdomain routing on main domain, but domain-level routing on others. Digging into the code that ships with apartment, they use tenant.presence, rather than ending with the return of the if/else. Checking presence (http://api.rubyonrails.org/classes/Object.html#method-i-presence) essentially gives an ||= inline while checking.
See: What is the point of object.presence?

Related

Migrating sessions from cookie_store to Redis in Rails 3

I need to switch the session store in my Rails 3 app from cookie_store to redis-session-store. There are many reasons for this (security, SSO with other Rails and non-Rails apps). Are there any best practices on how to do it without loosing all current sessions?
What i could imagine is a two steps approach:
Collect all user sessions for N days and store them in the DB or in Redis (update if already stored).
Use stored user sessions to create entries in Redis.
Alternatively, on the fly migration would also be possible. Means read cookies, use secret key to decrypt the session data and store it as a new session in Redis.
I realize this ticket is pretty old, but this may help others. We ended up changing our session store to Redis, but then still looking for the legacy cookie (for a week or two) before no longer respecting them.
There are probably some security concerns to consider before using this strategy - you want to make sure those risks are worth it compared to the cost of having to sign your entire user-base out all at once. With Rails, the cookies are encrypted and can't be tampered with.
Here's what we used:
class SessionsController < Devise::SessionsController
LEGACY_COOKIE_NAME = "_old_session_name".freeze
def new
return if detect_valid_cookie
super
end
private
def detect_valid_legacy_cookie
legacy_cookie = request.cookie_jar.encrypted[LEGACY_COOKIE_NAME].presence || {}
valid_user_id = legacy_cookie['warden.user.user.key'].try(:first).try(:first)
return unless valid_user_id
user = User.find_by(:id => valid_user_id)
return unless user
if sign_in user
request.cookie_jar.delete(LEGACY_COOKIE_NAME)
redirect_to root_path # or whever you want
true
else
false
end
end
end
Stolen from here:
http://www.happybearsoftware.com/almost-protect-yourself-from-cookie-session-store.html (the last two sections)
Basically, use this:
Rails.application.config.action_dispatch.cookies_serializer = :hybrid
Quote follows:
This will cause Rails to accept sessions serialized with Marshal and exchange them for sessions serialized with JSON.
After you're confident that all your users sessions have been converted to JSON, you can roll out another release that flips the config value to :json.
Note: If you're storing complex Ruby objects in the session and need them to be serialized with Marshal, you won't be able to use the JSON serializer.

Rails 4 multitencancy with subdomain login management

Scenario: Multi-tenant rails app that uses subdomains and devise
Problem: I want the user to be able to log into mydomain.com then be forwarded to their own subdomain1.mydomain.com address as a logged-in user. Right now they can only log directly into their own subdomain.
I'm a relative Rails newbie and I can't find a simple solution (although it seems like there must be one). Ideally I would like to have mydomain.com and subdomain1.mydomain.com share one cookie, but my skills aren't there for writing custom middleware. Obviously since it's multitenant I can't share one session across all subdomains. Stuck on this for a few days and curious if there is a simple solution (such as a config.session_store domain setting) that I'm missing before I start looking at OAuth or other more cumbersome solutions. Any help will be appreciated!
Edit: Of course I only found this after posting. Log a user into their subdomain after registration with Rails and Devise . Will try the config.session_store domain: :all with a before filter recommendation and post any details if it doesn't work, seems like a good idea at least.
Edit: SOLUTION that worked for my particular Devise with subdomains setup:
class ApplicationController < ActionController::Base
before_action :check_subdomain
def check_subdomain
unless request.subdomain == "" or request.subdomain == session[:subdomain]
redirect_to request.protocol+request.domain
end
end
end
session_store.rb
My::Application.config.session_store :cookie_store, key: '_my_session' , :domain => :all, :tld_length => 2
Basically I set the subdomain in the session with session[:subdomain] at login and use that to scope the session to the current user. Otherwise when the domain is set to :all in session_store it breaks the scope. If the user is not authorized it redirects them to the public home page via the request.protocol (http:// or https://) +request.domain redirect. Simple! Now users can move between the base domain and their subdomain within the same session.
Cookie
From what you've posted, I'd estimate you have a problem with the tracking of your session cookie. We had a similar problem with our subdomain-powered application, which lead to the cookie being dropped each time you switched between the two
We found the remedy here:
Share session (cookies) between subdomains in Rails?
#config/initializers/session_store.rb
Your_App::Application.config.session_store :cookie_store, key: '_your_app_session', domain: :all, tld_length: 2
The trick is the tld_length argument - this allows you to define how many "levels" of the domain can be accommodated; IE if you're using a sub domain, you'll need to set the tld_length to reflect it
Forwarding
I'm not sure whether you have a problem with your forwarding or not; I'll give you some ideas anyway.
When you log into a "subdomain", unless you've got a true multi-tenancy implementation of Rails (where each user is stored in a different database), you should be able to allow the users to login on the main form, and then redirect them to the subdomain without an issue
Something you need to consider is the subdomain constraint will only be populated if you use _url path helpers:
<%= link_to "Your Name", path_url(subdomain: "subdomain_1") %>
The reason for this is the _path helper is relative to the base URL, and consequently cannot populate the subdomain option. Alternatively, the _url path helper points to the URL in its entirety -- allowing you to define the sub domain as required
--
If you send the request & continue to want the user to remain signed-in, you'll need to ensure you're able to persist the authentication across the sub-domains. IE if you have a single-sign in form on the "main" page, you'll want to ensure you can continue the authentication into the subdomains

Path specific cookies in Rails app

I'm working on a minisite generator managed in one app.
Each minisite would have its own authentication.
With subdomains, I'm able to be specifically logged in and have settings for only one minisite but not another.
Now, I would like to have a failover where each minisite is accessible without setting up subdomains : http://mygenerator/minisites/1123
Is it possible for cookies to be scoped at the minisite/ path level ?
Or, is there a way to dynamically tweak the cookie key in a before_filter at the controller level ?
I took a look at the :path option in session_store config, but I don't think it's relevant, and it screws up Devise in a redirection loop.
Thanks !
I know its a late reply but i havent found anything in the internet answering this question.
My problem was that the application always running on a subpath but the session cookie was always issued to /
For me this was fixed by setting and using Rails.application.config.x.base_url
for your specific problem you can figure the path to set from the request object given in the method
This needs to be added in a new file in your rails Applcation ./config/initializers/session_store.rb
module SessionPath
extend ActiveSupport::Concern
def self.included(base)
base.class_eval do
alias_method :set_cookie_original, :set_cookie
alias_method :set_cookie, :set_cookie_extended
end
end
def set_cookie_extended(request, session_id, cookie)
cookie[:path] = Rails.application.config.x.base_url #or what you need
set_cookie_original(request, session_id, cookie)
end
end
ActionDispatch::Session::AbstractStore.send(:include, SessionPath)

Confirm on Apache Passenger deployment: rails access session in model

I am using this to access session in Model.
http://www.zorched.net/2007/05/29/making-session-data-available-to-models-in-ruby-on-rails/
Can anybody confirm that it will work with Apache + Passenger deployment too?
Or if there are any other alternatives to achieve the same?
Thanks,
Imran
I did not find any code on the internet that works, so I did some research and wrote my own. It works for Rails 3.2.x and probably on some other versions.
Insert this in your ApplicationController
# Set a filter that is invoked on every request
before_filter :_set_current_session
protected
def _set_current_session
# Define an accessor. The session is always in the current controller
# instance in #_request.session. So we need a way to access this in
# our model
accessor = instance_variable_get(:#_request)
# This defines a method session in ActiveRecord::Base. If your model
# inherits from another Base Class (when using MongoMapper or similar),
# insert the class here.
ActiveRecord::Base.send(:define_method, "session", proc {accessor.session})
end
I will not remind you that accessing your session from a model may lead to bad code. Other posts may tell you, that you are stupid. Though there are some valid reasons to access the session from your model, like implementing a Model.save method, that saves to the current users session.
Yes. It is the only efficient way I found to use session data in model. I also used it and never faced any deployment issue with Apache + passenger.
But you need to confirm when you will be playing with session values. On each new request to server, session value gets stored in thread and we can access it in model. If you are applying any logic by using thread value, then also make sure with situation when thread value might be nil also.
Because I got an issue where on development, my every code worked fine but on production, during starting server it caused an issue as initially it considered thread value as nil.

Rails saving IP address with every create/update request

I'd like to do the following:
define a before_filter in application.rb that extracts the user's IP address and stores it anywhere, preferably in the session.
define two before filters in all my models as before_create and before_update that add the current user's IP to the object to be stored.
Problem: I cannot access session[] neither env[] in a model. Can anyone help with a standard solution that I don't know yet?
Regards
Jason
Try this. In your user model add a class attribute accessor
cattr_accessor :current_ip
In your application controller add:
before_filter :set_current_ip
protected
def set_current_ip
User.current_ip = request.env['REMOTE_ADDR']
end
Then in your model you should be able to just call User.current_ip
We do something similar to get the current_user object passed through.
You're having trouble doing what you want because Rails is designed not to allow you to have access to session information in your models. It's the classic separation of concerns with MVC. Models are meant to work independently of your other layers, and you'll be thankful they do when you start doing things with Rake or other system tasks where you won't have a session.
The
cattr_accessor :current_ip
is a horrible approach. It's a hack and it should be apparent why. Yes, it may work, but it's the wrong approach to this problem.
Since you're tracking "who" did "what" by their IP, the logical place for this to happen is in the controller layer. There are several approaches you can take, including using CacheSweepers as auditors, as outlined in the Rails Recipes book. CacheSweepers can observe models but also have access to all controller information. Using the ditry attributes in a rails model, you can see exactly what changed.
#user = User.find_by_login "bphogan"
#user.login = "Brian"
#user.save
#user.changed
=> ["login"]
#user.changes
=> {"login"=>["bphogan", "brian"]}
#user.login_was
=> "bphogan"
Combine this with the session info you have and you have a pretty awesome auditor.
Does that help?
If you want to save the IP in the session, you can create a before filter in the applicationController. Like this, for each action, the filter is called and the ip is stored.
authlogic is a plugin to manage users login/sessions etc, it has a built in option to track the users IP
What you really need is a versioning plugin - I suggest having a look at one of the fine solutions at http://ruby-toolbox.com/categories/activerecord_versioning.html
Edit: archived version of that link (was 404 since sometime in 2012): https://web.archive.org/web/20111004161536/http://ruby-toolbox.com:80/categories/activerecord_versioning.html

Resources