I have 2 models, users and common_app.
Users has_one common_app.
In the common_app controller, I define almost everything using the current_user helper. This essentially makes it so that the edit forms ignore the id that the user POSTs via the web browser.
It looks like so -->
class CommonAppsController < ApplicationController
before_action :signed_in_user
def new
if current_user.common_app.present?
redirect_to current_user
else
#common_app = current_user.build_common_app
end
end
def create
#common_app = current_user.build_common_app(common_app_params)
if #common_app.save
flash[:success] = "Common App Created!"
redirect_to root_url
else
redirect_to 'common_apps/new'
end
end
def update
if current_user.common_app.update_attributes(common_app_params)
flash[:success] = "Common App Updated"
redirect_to root_url
else
render 'common_apps/edit'
end
end
def show
#common_app = current_user.common_app
end
def edit
#common_app = current_user.common_app
end
private
def common_app_params
params.require(:common_app).permit(:current_city,:grad_year,:read_type,
:listen_speak,:time_in_china,
:cover_letter,:resume) ####fill in the correct ones here
end
# is correct_user necessary?
end
What makes me wary though is that I am not using a correct_user before action. If I were to not use it, would there be a security hole here? I.e could someone POST through a shell or something?
If yes, how would you change the controller, to include the before filter?
PS: I'm also a bit confused about the correct use of # variables. If I am overusing them, or doing something wacky with them, please let me know and help me become a better noob :)
PPS: This is my SessionsHelper Module, for the signed_in_user before filter to work -- >
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
remember_token = User.encrypt(cookies[:remember_token])
#current_user ||= User.find_by(remember_token: remember_token)
end
def current_user?(user)
user == current_user
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def redirect_back_or(default) # this creates friendly forwarding for the app
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
def store_location
session[:return_to] = request.url if request.get?
end
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in."
end
end
end
I don't see any security problem here. Even without the before_action :signed_in_user, since you always go through the current_user.common_app association, if a user where not signed in, the action would simply fail.
So the controller is sound. As long as your authentication system has no flaws, the controller itself exposes no weakness.
In Ruby, variables prefixed with '#' are instance variables. In the context of a Rails controller, the distinction is simple:
Use instance variables for values you want to make available to your view, and normal variables for everything else.
Related
I'm going through Michael Hartl's The Ruby on Rails Tutorial, Chapter 8.3 Logging Out Sessions and I don't understand how removing the session[:user_id] can remove the #current_user as well:
here is the SessionController:
class SessionsController < ApplicationController
def new
end
def create
user =User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in(user)
redirect_to user
else
#flash.now will only flash once - if a new request or view is rendered,the flash will go away now
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end
def destroy
log_out
redirect_to root_path
end
end
and here is the SessionsHelper for the login and logout helpers:
module SessionsHelper
def log_in(user)
session[:user_id] = user.id
end
def current_user
#find the user if #user is not defined yet, or else, just keep the current user
#that way we dont have to do a database search every time current_user is called
#current_user ||= User.find_by(id: session[:user_id])
end
def logged_in?
!current_user.nil?
end
def log_out
session.delete(:user_id)
end
end
The way I understand, once #current_user is defined once logged in, shouldn't the variable still last even though the session[:user_id] has been removed since it is being set to itself?
#current_user ||= User.find_by(id: session[:user_id])
There was no action that I am aware of that removed the #current_user variable. But when I tested it during the debugger, I can see that once someone logs out, #current_user becomes nil.
Can someone explain the mechanics to me?
The session persists between requests. But the instance variable #current_user only persists for the length of one request. When the destroy action redirects to the root_path that is the start of a new request which will load the root page.
You may want to try this out so see that clearing the user_id out of the session doesn't clear out the instance variable:
def destroy
# Test code to initialize #current_user
current_user
Rails.logger.debug("#current_user before: #{#current_user.inspect}")
log_out
# Test code to check #current_user after updating session
Rails.logger.debug("#current_user after: #{#current_user.inspect}")
redirect_to root_path
end
And then check what ends up in log/development.log. #current_user will still be present after log_out but it will go away at the end of the request.
i am doing my first project and i need your help.
I am using act_as_shopping_cart gem, and user model like in Rails Tutorial.
How can i bind user and shopping cart? I tried bound shopping_cart.id and user.id, but without succes, still all users have same cart.
It my code:
Shopping cart controller:
class ShoppingCartsController < ApplicationController
before_filter :extract_shopping_cart
def create
#product = Sketchbook.find(params[:product_id])
#shopping_cart.add(#product, #product.price)
redirect_to shop_path
end
def show
end
private
def extract_shopping_cart
shopping_cart_id = session[:shopping_cart_id]
#shopping_cart = session[:shopping_cart_id] ? ShoppingCart.find(shopping_cart_id) : ShoppingCart.create
session[:shopping_cart_id] = #shopping_cart.id
end
end
User controller:
class UsersController < ApplicationController
before_action :logged_in_user, only: [:show, :edit, :update]
before_action :correct_user, only: [:edit, :update]
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to user_url(#user)
flash[:notice] = "Użytkownik stworzony"
else
render 'new'
end
end
def edit
end
def update
if #user.update_attributes(user_params)
redirect_to user_url(current_user)
flash[:notice] = "Dane zaktualizowane"
else
render 'edit'
end
end
def destroy
User.find(params[:id]).destroy
flash[:success] = "Konto usunięte"
redirect_to root_url
end
private
def user_params
params.require(:user).permit(:username, :name, :surname, :email, :adress, :city, :zip_code, :country, :password, :password_confirmation)
end
#confirms a logged user
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Zaloguj się"
redirect_to login_url
end
end
#confirms the correct user
def correct_user
#user = User.find(params[:id])
redirect_to(root_url) unless current_user?(#user)
end
end
Session helper:
module SessionsHelper
#log in method
def log_in(user)
session[:user_id] = user.id
end
#remember a user in a presisnet session
def remember(user)
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = user.remember_token
end
#returns true if the given user i current user
def current_user?(user)
user == current_user
end
#forgets a presistent session
def forget(user)
user.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
# returns logged user
def current_user
if (user_id = session[:user_id]) #open broweser
#current_user ||= User.find_by(id: session[:user_id])
elsif (user_id = cookies.signed[:user_id]) #cookie is present
user= User.find_by(id: cookies.signed[:user_id])
if user && user.authenticated?(cookies[:remember_token])
log_in user
#current_user = user
end
end
end
# Returns true if the user is logged in
def logged_in?
!current_user.nil?
end
# logs out current user
def log_out
forget(current_user)
session.delete(:user_id)
#current_user = nil?
end
#stores the url trying to be accessed
def store_location
session[:forwarding_url] = request.url if request.get?
end
#redirect back to stored location (or to the default)
def redirect_back_or(default)
redirect_to(session[:forwarding_url] || default)
session.delete(:forwarding_url)
end
end
I'd also happy if you could give me some other guidances how to improve this code.
Rails lets you do lots of "magic" where code in one place magically affects behavior elsewhere in the app, with no explicit connection between the two. This is really powerful but I think is also a huge problem for beginning developers; when you're learning Rails, it's super hard to keep track of how one thing is related to another so having those connections hidden up in the inheritance chains is almost cruel.
So if you're in a place where the Rails magic is overwhelming (it certainly still is for me), my advice is to write stupid code that makes connections as local and simple as possible. Sometimes it's worth doing that even when it deviates from "the Rails way"; would you rather have "proper Rails" code, or code that's easy to understand and easy to maintain?
Anyway, your specific problem might relate to the session. In ShoppingCartsController you're defining #shopping_cart by looking up a session[:shopping_cart_id]. If multiple users (after logging out and logging in as someone else) are ending up with the same #shopping_cart.id, that must mean that their session values are the same. In the beginning of the relevant controller action, add a puts statement to give you extra info (it spits out to the console) about what the session values are:
puts session
If multiple users have the same session values, it likely means that the session isn't getting cleared properly when you log out and log in as someone else. You could test that by setting a different session variable, and seeing if that also persists from one user to another.
In general, adding lots of puts statements at the beginning of all relevant controller actions is a great (and super simple) way to get insight into what your application is thinking.
Hope that helps!
I am trying to redirect to the page from where I clicked login, but after logining in it doesn't redierect to previous page but stays on login page (although the user is already logged in).
Here is my code:
session_helper.rb
module SessionsHelper
def sign_in(user)
remember_token = User.new_remember_token
cookies.permanent[:remember_token] = remember_token
user.update_attribute(:remember_token, User.encrypt(remember_token))
self.current_user = user
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
def store_location
session[:return_to] = request.fullpath
end
end
sessions_controller.rb
class SessionsController < ApplicationController
include SessionsHelper
def new
end
def create
user = User.find_by_username(params[:session][:username])
if user && user.authenticate(params[:session][:password])
cookies.permanent[:remember_token] = user.remember_token
#redirect_to root_url,:notice => "Logged in!"
redirect_back_or user
else
flash[:error] = 'Invalid email/password combination' # Not quite right!
render 'new'
end
end
def destroy
cookies.delete(:remember_token)
#session[:user_id] = nil
redirect_to root_url, :notice => "Logged out!"
end
end
I also tried to write in create function in sessions_controller.rb
redirect_to request.referer
but it doesn't work.
Am I missing something?
Thanks for your help!
The problem happens at store_location.
Though you havn't said in question, I guess you probably put this method in before_filter. So, no matter GET or POST or other request, the request hit this filter at first and store location.
Now, in this case, actually the user has two requests. One is to #new by GET, and the other is to #create by POST. In the later, his last request to #new was recorded as the going back location. So you'll see you always go back to #new :)
The solution is to filter the location to be stored.
def store_location
disable_pattern = /\A\/user*/
session[:return_to] = request.fullpath unless request.fullpath ~= disable_pattern
end
This pattern could solve current problem but not exclusive. In practice you may see even JS/JSON requests has been recorded, so you may need to add more restrictions according to the specific case. For example, only apply before_filter on #show or #index, use white list, etc.
I think request.referer may not have worked because of a typo in the method. It should be request.referrer.
I'm following the tutorial on http://ruby.railstutorial.org
Specifically, chapter 9 (9.2.3)
http://ruby.railstutorial.org/chapters/updating-showing-and-deleting-users#top
I've managed to get the part when a user will get prompted to login when accessing a restricted page then be redirected back to the restricted page after successfully logging in.
I'm trying to get it so that after one redirects to the protected page, the next login attempt will direct back to the main user profile page, however, session.delete(:return_to) doesn't appear to be working and the user is repeatedly directed back to the originally saved protected page. Here's my code:
My session Controller:
class SessionsController < ApplicationController
def new
end
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_back_or user
# Sign the user in and redirect to the user's show page.
else
# Create an error message and re-render the signin form.
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
...
end
My session helper:
module SessionsHelper
def sign_in(user)
cookies.permanent[:remember_token] = user.remember_token
self.current_user = user
end
def signed_in?
!current_user.nil?
end
def current_user=(user)
#current_user = user
end
def current_user
#current_user ||= User.find_by_remember_token(cookies[:remember_token])
end
def current_user?(user)
user == current_user
end
def sign_out
self.current_user = nil
cookies.delete(:remember_token)
end
def redirect_back_or(default)
redirect_to(session[:return_to] || default)
session.delete(:return_to)
end
def store_location
session[:return_to] = request.url
end
end
Any help you can give would be brilliant! It seems like session.delete() simply isn't working.
The following block solved it. Nothing else needs to change.
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in." #unless signed_in?
end
end
When I did the tutorial, my code had the first lines of the SessionsController#create method as just:
user = User.find_by_email(params[:email])
if user && user.authenticate(params[:password])
But, I can see that the corresponding code in the book has changed to:
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
I attempted to use that new code in my sample_app, but most of my tests ended up failing. So, for you, I guess test adding the downcase method to your params[:session][:email] call first, and if that doesn't work, try substituting the lines out for the session-less code above and see if it works.
Update
After looking at your code, as far as I can tell, these are your problems:
You're calling session.delete(:return_to) in SessionsController#create for some reason. This line can be removed:
app/controllers/sessions_controller.rb
def create
user = User.find_by_email(params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
# session.delete(:return_to)
sign_in user
# ...
#...
end
Both lines of code in your UsersController#signed_in_user method need to be put in the unless block, not just the call to redirect_to:
app/controllers/users_controller.rb
def signed_in_user
unless signed_in?
store_location
redirect_to signin_url, notice: "Please sign in." #unless signed_in?
end
end
If you make these changes and run your tests, you'll still have a Nokogiri::XML::XPath::SyntaxError: on your call to
spec/requests/authentication_pages_spec.rb
it { should have_exact_title('title', text: full_title('')) }`
but I'm assuming this is a custom matcher you're planning to work on. If not and it's a mistake, remove it and all your tests will pass.
I am working on a basic authentication system for a rails app. The authentication is verifying account information from Active Directory using a net-ldap class (this part is working fine).
Something seems to be wrong with my session_helper however. Even though ActiveDirectoryUser.authenticate is successful, the signed_in helper always returns false. After signing in, the script redirects to root_path (default_controller's home) and then immediately redirects back to signin_path again-- as a result of the signed_in helper returning false.
See the code below. What am I missing?
Thanks
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
include SessionsHelper
end
default_controller.rb
class DefaultController < ApplicationController
before_filter :signed_in_user
def home
end
private
def signed_in_user
redirect_to signin_path, notice: "Please sign in." unless signed_in?
end
end
sessions_helper.rb
module SessionsHelper
def sign_in(user)
#current_user = user
end
def current_user
#current_user ||= nil
end
def signed_in?
!#current_user.nil?
end
def sign_out
#current_user = nil
end
end
sessions_controller.rb
class SessionsController < ApplicationController
def new
end
def create
user = ActiveDirectoryUser.authenticate(params[:session][:username],params[:session][:password])
if user.nil?
# authentication failed
flash.now[:error] = 'Invalid email/password combination'
render 'new'
else
# authentication succeeded
sign_in #user
flash[:error] = 'Great success'
redirect_to root_path
end
end
def destroy
sign_out
redirect_to root_path
end
end
You should use session for to persist that kind of data (will be assessable for every request), it's user data. But I highly recommend you to use something like the devise gem that do all that authentication things and more for you. Why reinvent the weel right?
I believe this would work for you.
module SessionsHelper
def sign_in(user)
session[:user_id] = user.id
end
def current_user
ActiveDirectoryUser.find(session[:user_id]) ||= nil
end
def signed_in?
!session[:user_id].nil?
end
def sign_out
session[:user_id] = nil
end
end