I used Sorcery to set up authentication in Rails and I'm trying to create a model where the user id for the user is linked as reference to the model for data entered, but I get an error:
Couldn't find User without an ID
it refers to the following code:
def create
#user = User.find(params[:user_id])
#certificate = #user.certificates.create(certificate_params)
redirect_to certificate_path(#certificate)
end
To answer basic questions I've asked myself, the user is logged in...
That's actually a very weird error if the above is really your code. You should have gotten something like Couldn't find Bar with 'id'= instead. The error above is usually only given if you provide no args to find at all, ie. User.find()
Anyway, the underlying problem here is that params[:user_id] doesn't contain the value you expect it to, and probably it contains no value at all. You haven't shown enough of your other code to determine why that is, BUT it doesn't matter...
Unless you want people to be able to just change the URL and add a certificate for some other user in your system then you shouldn't rely on the params[:user_id] for creating associated objects, you should use current_user - that's kind of the whole point of authentication.
So:
def create
#certificate = current_user.certificates.create(certificate_params)
redirect_to certificate_path(#certificate)
end
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
I am fairly new to rails and am just getting into using cool gems and APIs. I have been made aware that this community values the contents of questions and answers, or it is expected that they are constructed in a specific way. The short way to ask my question and the long way are provided. Please let me know which is preferred here! It is a real question though!
SHORT VERSION
I have a users_controller and User object with a username attribute in a rails app. How do I create global variables for these users that is dynamically based on their username? Example: I want with my user (id = 1, first_name = "Rob", username = "rocky") to be callable as #rocky. So what would go below in my Users_controller that is based on the first code line below working for me in terminal:
#rocky = User.find_by_username("rocky")
WHATGOESHERE = User.find_by_username(params[:username]}
or should I be using this in some shape or form in place of params[:username]
#"#{user.username}"
Below is the longer version of my question. It is more detailed and follows more closely how I approached the issue. The first one... that I wrote second, is more concise but that's not always what people want... please let me know which is preferred on this site. Thanks!!
LONG VERSION
I need some clarity on a few things. I am using a gem called "has_friendship" to create friendships between my users (link to gem- https://github.com/sungwoncho/has_friendship ).
First, this is the documentations example of how to request a friendship, starting with the creation of the users.
#mac = User.create(name: "Mac")
#dee = User.create(name: "Dee")
# #mac sends a friend request to #dee
#mac.friend_request(#dee)
This is where I first became confused. My users don't have a "name" field. But that's ok. I managed to figure out that I just need to assign my created users global variables as they do... since my users will be interacting with each other behind their "username" attribute. So first question, How do I assign a dynamic variable name to each user? In the documentation, they are hard-coding in the names "Mac" and "Dee." I need to have this global variable be created upon the creation of the object.. So my plan is to do this in the controller. Here I am already defining #users and #user in users#show
#users = User.all
#user = User.includes(:wallet).find_by_id(params[:id])
So my thought process is that the left side of the equation should be the name of what you're naming and the right side is what that name is referring to. So for the right side, I'd think to put
User.find_by_username(params[:username])
as when in the terminal, if I replace the content in parenthesis with an actual username in quotes, it brings up that user's info. So how do I write the left side. I would think the left side is something like this:
#"#{params[:username]}"
So in full I currently have the following in my users_controller to assign global variables to my users based on their username atttribute...
#"#{params[:username]}" = User.find_by_username(params[:username])
This, especially the left side, does not look at all right to me. So I've looked around on google a bunch and the only other thing I can find that looks like the right way to do this is by using "instance_variable_set" but everything I've looked at doesn't make total sense for my situation... (as usual.. ha)
Ok what I get to know from your question is you want to use friend_request method to associate two users.
For this, you don't need to assign them to any variables. You can directly do this by something like this -
Suppose there are two user's
id=1 first_name='Rocky' username='rocky'
id=2 first_name='Nimish' username='nimish'
User.find_by(username: 'rocky').friend_request(User.find_by(username: 'nimish'))
OR
User.find_by_username('rocky').friend_request(User.find_by_username( 'nimish'))
Also, If you want to assign them to instance variable then it is not necessary to create an instance variable corresponding to the username value
You can simply assign them to #user and #requested_user and then
#user.friend_request(#requested_user)
I want to use this piece of code to retrieve a user's list of credit cards on file with Stripe to show on his profile (/users/:id)
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
Thing is, I'm not exactly sure where (in terms of Rails best practices) it fits. My first tought is to put it in the show method of the User controller since it's not really business logic and doesn't fit in the model. I've also looked at helper methods but they seem (from my understanding) to be used strictly when toying around with HTML.
Can any of you Rails experts chime in?
Thanks!
Francis
Good question. Whenever you see an instance variable in rails (starting with a #), it usually is a view/controller bit of code.
#stripe_cards = Stripe::Customer.retreive(self.stripe_customer_id).cards.all
However looking at the tail end of that
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
This might fit better of in a model, where you can reuse that same line, but have the safety of added error handling and predictable behavior. For example
# user.rb
def stripe_customer_cards
Stripe::Customer.retreive(self.stripe_customer_id).cards.all
rescue Stripe::InvalidRequestError
false # You could use this to render some information in your views, without breaking your app.
end
Also note the use of self. This usually implies use of a Rails model, because calling self in the controller actually refers to the controller, rendering it almost worthless, unless you really know what you are doing.
EDIT
To render an error message, simply write a call to redirect or render, with the alert option.
if #stripe_cards = current_user.stripe_customer_cards
# Your being paid, sweet!
else
# Render alert info :(
render 'my_view', alert: 'This is an alert'
redirect_to other_path, alert: 'Another alert'
end
I also like to make it a point to mention that you should not handle errors just because you can. Don't handle errors you don't expect. If you handle errors you don't expect it will
Confuse users
Make bugs in code harder to fix
Exaggerate the time before an error is recognized
I'd recommend adding a virtual attribute in your User model:
# app/models/user.rb
def cards
Stripe::Customer.retrieve(stripe_customer_id).cards.all # note the spelling of `retrieve`
end
Then, you'd be able to access all a users cards in the following manner:
user = User.first
#=> #<User id:1>
user.cards
#=> [Array of all cards]
I have spent hours trying to find a resource that explains the process of submitting form data directly to a session variable, but I have had no luck finding anything!
Essentially I am not wanting to store the data in the database when the user submits in this particular form, I just want it to be assigned to the session[:member_pin] variable when the user submits the form, so I can then check if the pin they entered matches the pin on the members database record.
Please let me know if you need anymore clarification for what I am trying to do, and thank you so much for your help!
You don't have to save the data to database every time a form is submitted. In your controller 's action, get the params you want and store them in the session. Eg.,
def some_action
session[:user_id] = User.find_by_pin(params[:pin]) if params[:pin]
end
Then in your application controller, make a helper method like this. Then you should be able to access "current_user" method in your views. (It will be nil if you haven't got any user verified with pins.
def current_user
User.find(session[:user_id]) if session[:user_id].present?
end
maybe something like this in your controller method:
session[:member_pin] = params[:member_pin_input_name]
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.