Using Rails, is this approach vulnerable to causing separate, concurrent requests to use the wrong connection/database?
def find_user
#user= User.find_by_email(email)
unless #user
#search non_pro database
User.establish_connection("#{Rails.env}_non_pro".to_sym)
#user= User.find_by_email(email)
end
ensure
User.establish_connection("#{Rails.env}".to_sym)
end
What I want to know is if a first request causes the connection to be established with 'non_pro', will a second separate request run the risk of incorrectly connecting to 'non_pro' if it runs while the first is connected to 'non_pro'
I did a small test and found out that if you do User.establish_connection() this will change the connection globally for all concurrent requests, so they might fail by using the wrong database.
What you can do is making a subclass of User
class NonProUser < User
self.table_name = "users"
establish_connection("#{Rails.env}_non_pro".to_sym)
end
Then you can use NonProUser.find_by_email(email) to find your user on the other database.
Related
I am wondering how to limit the connection to a channel or the streaming of messages over a channel in rails5. Currently I groups with users in the groups working with pundit and the connection to the websocket happens within that group. If a malicious user randomly guessed groups they could potentially read a message over a socket they shouldn't.
When you create a new message the following code is run in my controller:
if message.save
ActionCable.server.broadcast(
"messages_{message.groupchat_id}_channel",
message: message.content,
user: message.user.email
)
head :ok
end
I have no idea what I'm doing.
Here's the solution I found to use Pundit with Activecable.
First we need access to the user model. You can do that by following the instructions in the Action Cable Overview - Connection Setup. Mainly you need to change the code in connection.rb
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
Note: you may need to use a different way of finding your user. I'm using devise_token_auth and so needed to pass the uid, token, and client_id to the connection.rb and then got the user via this code:
if user && user.valid_token?(token, client_id)
user
else
reject_unauthorized_connection
end
I mention this just because how you get your user may vary. The main thing is that you need to use identified_by current_user and set it.
Another thing which I did not immediately find in the documentation, is that the current_user is now accessible by your channels. Since the user name may be different than your pundit_user name, I found it easiest to manually pass the user to Pundit at that point. So in subscribing with my channel file, I had this code:
def subscribed
message = MessagePolicy::Scope.new(self.current_user, Project).resolve.find(params[:message])
stream_for message
end
You could of course also manually authorize this way, instead of using Scope:
MessagePolicy.new(self.current_user, message).show?
You can add another layer of security for actioncable connections in app/channels/application_cable/connection.rb file.
You can follow this tutorial. I think this give you some idea:
https://www.learnenough.com/action-cable-tutorial#sec-login_protection
I am developing a game using TCP. The clients send and listen the server using TCP. When the server receives a request, then it consults the database (SQL Server Express / Entity Framework) and sends a response back to client.
I'm trying to make a MMORPG, so I need to know all the players locations frequently, so I used a System.Timer to ask the server the location of the players around me.
The problem:
If I configure the timer to trigger for every 500ms a method that asks the server the currently players location, then I can open 2 instances of the client app, but it's laggy. If I configure to trigger for every 50ms, then when I open the second instance, the SQL Server throws this exception often:
"The connection was not closed. The connection's current state is open."
I mean, what the hell? I know I am requesting A LOT of things to the database in a short period, but how do real games deals with this?
Here is one code that throws the error when SQL Server seems to be overloaded (second line of the method):
private List<CharacterDTO> ListAround()
{
List<Character> characters = new List<Character>();
characters = ObjectSet.Character.AsNoTracking().Where(x => x.IsOnline).ToList();
return GetDto(characters);
}
Your real problem is ObjectSet is not Thread Safe. You should be creating a new database context inside ListAround and disposing it when you are done with it, not re-using the same context over and over again.
private List<CharacterDTO> ListAround()
{
List<Character> characters = new List<Character>();
using(var ObjectSet = new TheNameOfYourDataContextType())
{
characters = ObjectSet.Character.AsNoTracking().Where(x => x.IsOnline).ToList();
return GetDto(characters);
}
}
I resolved the problem changing the strategy. Now I don't update the players positions in real time to the database. Instead, I created a list (RAM memory) in the server, so I manage only this list. Eventually I will update the information to the database.
I have a ruby on rails application deployed to torquebox. I need some way to secure the websockets in my application. I am using the stomp websockets , is there a way to authenticate users while they make a websocket connection? I could use the username and password parameters but they are currently ignored. Is there any other way to authenticate this connection? Thanks!
You can authenticate a message to a Stomplet by using the session and a stored token. For this to work, you have to setup Rails to use the Torquebox session store. This can be done with an initializer, such as config/initializers/torquebox_init.rb:
AppName::Application.config.session_store :torquebox_store
Now the Stomplet will have access to the session. Here is an example Stomplet that uses the session param :authentication_token to match the User's authentication_token in the database. The auth token is checked for subscribing, sending a message, and unsubscribing:
require 'torquebox-stomp'
class StompletDemo
def initialize()
super
#subscribers = []
end
def configure(stomplet_config)
end
def on_message(stomp_message, session)
token = session[:authentication_token]
if is_authenticated?( token )
#subscribers.each do |subscriber|
subscriber.send( stomp_message )
end
end
end
def on_subscribe(subscriber)
session = subscriber.session
if is_authenticated?(session[:authentication_token])
#subscribers << subscriber
end
end
def on_unsubscribe(subscriber)
session = subscriber.session
if is_authenticated?(session[:authentication_token])
#subscribers.delete( subscriber )
end
end
def is_authenticated?(token)
User.where( authentication_token: token ).exists?
end
end
Now all you have to do is make sure that when the user authenticates, the session[:authentication_token] is set. Mostly like this will be set in a controller:
# user has successfully authenticates
session[:authentication_token] = #user.authentication_token
For other people having this issue, this is how I solved it.
https://gist.github.com/j-mcnally/6207839
Basically the token system didnt scale for me, especially since I use devise.
If you want to host your websocket in say a chrome extension its easier to just pass username/password directly to stomp and have it manage its own virtual subscriber sessions in the stomplet. This also allow you to do some fun things as far as who you are pushing to.
I have an API that services a web-based plugin for processing email. The API is responsible for two things:
Creating SessionIDs so the plugin can setup a dynamic link; and
Once an email is sent, for receiving that SessionID, the email recipients and subject line, to store the information into a new session.
Imagine the scenario where the plugin sends a request to the API:
PUT http://server.com/api/email/update/<SessionID> -d "to=<address1,address2>&subject=<subject>"
In testing this works fine: the data is saved normally. However, the plugin can't help but send that request several times a second, bombarding my server with identical requests. The result is that I get my EmailSession object saving multiple copies of the recipients.
In terms of my database schema, I have an EmailSession model, which has_many EmailRecipients.
Here's the relevant part of the update method in my API's controller:
#email_session = EmailSession.find_or_create_by_session_id(:session_id => params[:id], :user_id => #user.id)
if opts[:params][:cm_to].blank? == false
self.email_recipients.destroy_all
unless opts[:params][:cm_to].blank?
opts[:params][:cm_to].strip.split(",").each do |t|
self.email_recipients << EmailRecipient.create(:recipient_email => t)
end
end
end
Admittedly, the "find_or_create" dynamic method is new to me, and I wonder if there's something about that screwing up the works.
The symptoms I'm seeing include:
ActiveRecord errors complaining about attempts to save a non-unique key into the database (I have an index on the SessionId)
Duplicate recipients ending up in the EmailRecipients collection
In the case of multiple users employing the plugin, I get recipients from other emails ending up in the wrong email session collections.
I've attempted to employ delayed_job to attempt to serialize these requests somehow. I haven't had much luck with it thanks to various bugs in the current release. But I'm wondering if there's a more fundamental problem with my approach to this solution? Any help would be appreciated.
I'm still not sure I understand what you're doing, but here's my advice.
First off I don't think you are using find_or_create_by properly. This method has slightly confusing semantics (which is why 3.2 introduces some clearer alternatives) but as it stands it isn't using the user_id to find the record (although it is setting user_id if a record is created). I don't think this is what you wanted. Instead use find_or_create_by_session_id_and_user_id
This can still raise a duplicate key error since in between find_or_create checking and it creating the record there is time for someone else to create the record. If you weren't doing anything other than creating email session rows the rescuing this duplicate key error and then retrying should take of that: on the retry you'll find the row that blocked your insert.
However when you then go on to add recipients you still have a potential issue because 2 things could be trying to remove recipients and add them to the same email session at the same time. This might be a good usecase for pessimistic locking.
begin
EmailSession.transaction do
session = EmailSession.lock(true).find_or_create_by_bla_bla(...)
# use the session object here, add recipients etc.
end
rescue ActiveRecord::StatementInvalid => e
end
What is happening here is that when the email session is retrieved from the db, the row is locked (even if it doesn't exist yet - effectively you can lock the gap where the record would go). This means that anyone else wanting to add recipients or do any other manipulation has to wait for the lock to be released. Locks last as long as the transaction in which they occur lasts so all your work should happen in here (even if in the second part you are not actually changing the email session object any more).
You may end up with deadlocks - I don't know what else is going on in your app but you should be prepared for them if you are using transactions. That's what the rescue block is for: if the error message looks like a deadlock then you should probably retry some limited number of times.
Locks are (at least on MySQL) row level locks: as long as you have an index on session_id,user_id then just because one of your instance has one email session object locked doesn't stop another instance from using another one.
I'm having a problem using an authlogic single access token to access a page when logout on timeout is set to true and a timeout is set.
user.rb:
acts_as_authentic do |c|
c.logged_in_timeout = 15.minutes
end
user_session.rb:
logout_on_timeout true
controller:
def single_access_allowed?
["download_xml"].include?(action_name)
end
If I try to access a page/method using the token it redirects straight away to my login page. The logout on timeout works when its turned on.
If i remove the timeout code and just have acts_as_authentic in the user.rb, the single access token works.
I want to be able to use the single access token so another application can open an xml file from my ruby on rails website.
Any ideas on what I might have done wrong and where to look to fix it and make it work?
Using authlogic 3.0.3 and rails 3.0.7.
This reply from jgdreyes last Sept 27 at https://github.com/binarylogic/authlogic/issues/64 worked for me:
I went ahead and extended Authlogic's stale? method so that it does
not see requests as stale? if accessing via single_access?. This keeps
logic for logout_on_timeout intact.
class UserSession < Authlogic::Session::Base logout_on_timeout true
def stale?
return false if single_access?
super
end
end