Rails: undefined method `name' for Hash - ruby-on-rails

Update: part of the problem might have been solved by accessing the data like
first_name = graphdata[:first_name]
instead of like
first_name = userdata.first name
but when I go to save it, I'm now getting this error
undefined method `save!' for #<Class:0x00000102dbe068>
Original problem
I'm sure there's probably multiple problems with this code.
I'm playing around with a clone of a Rails app https://github.com/banane/sample-koala-rails-app that uses Koala to authenticate with Facebook and get user data. It does this with the callback method in the home_controller.rb below.
I'm trying to save the authentication token and the user data to a User model that I created.
In the callback method below, I inserted this code
user = User.oath(session[:access_token], #user_info) ### my code/ likely wrong
session[:user_id] = user.id ### my code/ likely wrong
to try to save the data to the User model, using the 'oath' class method I made on the User model (see below). Note,I'm not even sure if I can pass that instance variable #user_info into the method...
When I test it, the error I get is
undefined method `name' for #<Hash:0x00000103457d70>
and the trace says
app/models/user.rb:8:in `oath'
app/controllers/home_controller.rb:30:in `callback'
Here's the code
User.rb
def self.oath(access_token, userdata) #my code/ likely wrong
name = userdata.name
first_name = userdata.first_name
last_name = userdata.last_name
username = userdata.username
gender = userdata.gender
email = userdata.email
access_token = access_token
save!
end
Home_controller.rb (towards the end of the callback method I try to save the user data)
def callback
if params[:code]
# acknowledge code and get access token from FB
session[:access_token] = session[:oauth].get_access_token(params[:code])
end
# auth established, now do a graph call:
#api = Koala::Facebook::API.new(session[:access_token])
begin
#user_info = #api.get_object("me")
#graph_data = #api.get_object("/me/statuses", "fields"=>"message")
rescue Exception=>ex
puts ex.message
end
user = User.oath(session[:access_token], #user_info) #my code/likely wrong
session[:user_id] = user.id
respond_to do |format|
format.html { }
end
end
The #user_info has all this data in it
{"id"=>"8331884858", "name"=>"Michael MYLASTNAME", "first_name"=>"Michael", "last_name"=>"MYLASTNAMe", "link"=>"http://www.facebook.com/myusername", "username"=>"myusername", "gender"=>"male", "email"=>"myemail", "timezone"=>5, "locale"=>"en_US", "verified"=>true, "updated_time"=>"2012-07-29T06:52:12+0000"}

It sounds like you may be just accessing the hash incorrectly, try this:
def self.oath(access_token, userdata)
create! userdata.merge({:access_token => access_token})
end

Related

How to define a current_model for a model in rails?

I think my question title is bit confusing. But what I am meaning to ask is I am creating my own authentication system using mobile. Just like devise comes with current_user to create a session, I want to know how can I achieve same on a different model.
I have a model called Commuter. It also has a id with it.
A record of commuter looks like this.
Commuter.last
<Commuter id: 867, phone_number: "9483942090">
I am trying to create a session after verfying the mobile number with my controller method as follows:
def verify
#commuter = Commuter.where(phone_number: params[:phone_number]).first
if (#commuter && #commuter.authenticate_otp(params[:otp],drift:300))
#commuter.auth_active = true
if #commuter.save
#Removed from session after verified it
session[:phone_number] = nil
session[:is_verified] = nil
#signed in commuter after verified it
sign_in(:commuter, #commuter)
flash[:notice] = "Your mobile no is verified."
end
else
flash[:alert] = "You have entered wrong otp.Please check again."
end
puts "#{current_commuter.phone_number}"
redirect_to root_path
end
I just a puts there to debug. So right now I am getting current_commuter as undefined local variable for obvious reasons I guess. So I wanted to know how can achieve this session based current commuter ?
You can save the Commuter id in the session as session[:cid] = 1 and create a method on your base controller like this
def current_commuter
#commuter ||= Commuter.find session[:cid]
end
helper_method :current_commuter

Rails MonkeyPatch changes not being picked up

I'm trying to 'MonkeyPatch' this controller in my implementation so that it can handle a third parameter ('productname').
The original activate method in the gem reads
def activate
if Digest::MD5.hexdigest(params["security_data"] + SaasySimple.config.secret) == params["security_hash"]
SaasySimple.config.model.activate( params['token'], params['id'] )
end
end
My entire new file, placed in lib/monkeys/sassysimple.rb, reads
module SaasySimple
class SubscriptionsController < ApplicationController
def activate
if Digest::MD5.hexdigest(params["security_data"] + SaasySimple.config.secret) == params["security_hash"]
SaasySimple.config.model.activate( params['token'], params['id'], params['productname'] )
end
end
end
end
This isn't working - I'm still getting an error of subscriptions#activate (ArgumentError) "wrong number of arguments (2 for 3)", which I believe is caused because my user method (see below) is expecting productname but not getting it from the un-monkeypatched version. Can someone tell me why putting the file in lib isn't working? Thanks!
This is the user method:
def self.activate(token, id, productname)
user = User.find( id )
user.token = token
user.status = 'active'
user.package = productname
user.save!
end
I'd put the monkeypatch in an config/initializers/sassysimple.rb
Have you verified that the controller really sees all three params?
Is User#activate called anywhere else in the code? e.g. grep for it.

Devise models run before_save multiple times?

My client wants all user data encrypted, so I've created a before_save and after_find call back that will encrypt certain properties using Gibberish:
# user.rb
before_save UserEncryptor.new
after_find UserEncryptor.new
# user_encryptor.rb
class UserEncryptor
def initialize
#cipher = Gibberish::AES.new("password")
end
def before_save(user)
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.email = encrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
def after_find(user)
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.email = decrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
private
def encrypt(value)
#cipher.enc(value)
end
def decrypt(value)
#cipher.dec(value)
end
end
Well, when the user first signs up using Devise, the model looks about like it should. But then once the user confirms, if I inspect the user, the first_name and last_name properties look to have been encrypted multiple times. So I put a breakpoint in the before_save method and click the confirmation link, and I see that it's getting executed three times in a row. The result is that the encrypted value gets encrypted again, and then again, so next time we retrieve the record, and every time thereafter, we get a twice encrypted value.
Now, why the heck is this happening? It's not occurring for other non-devise models that are executing the same logic. Does Devise have the current_user cached in a few different places, and it saves the user in each location? How else could a before_save callback be called 3 times before the next before_find is executed?
And, more importantly, how can I successfully encrypt my user data when I'm using Devise? I've also had problems with attr_encrypted and devise_aes_encryptable so if I get a lot of those suggestions then I guess I have some more questions to post :-)
I solved my problem with the help of a coworker.
For encrypting the first and last name, it was sufficient to add a flag to the model indicating whether or not it's been encrypted. That way, if multiple saves occur, the model knows it's already encrypted and can skip that step:
def before_update(user)
unless user.encrypted
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.encrypted = true
end
end
def after_find(user)
if user.encrypted
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.encrypted = false
end
end
For the email address, this was not sufficient. Devise was doing some really weird stuff with resetting cached values, so the email address was still getting double encrypted. So instead of hooking into the callbacks to encrypt the email address, we overrode some methods on the user model:
def email_before_type_cast
super.present? ? AES.decrypt(super, KEY) : ""
end
def email
return "" unless self[:email]
#email ||= AES.decrypt(self[:email], KEY)
end
def email=(provided_email)
self[:email] = encrypted_email(provided_email)
#email = provided_email
end
def self.find_for_authentication(conditions={})
conditions[:email] = encrypted_email(conditions[:email])
super
end
def self.find_or_initialize_with_errors(required_attributes, attributes, error=:invalid)
attributes[:email] = encrypted_email(attributes[:email]) if attributes[:email]
super
end
def self.encrypted_email decrypted_email
AES.encrypt(decrypted_email, KEY, {:iv => IV})
end
This got us most of the way there. However, my Devise models are reconfirmable, so when I changed a user's email address and tried to save, the reconfirmable module encountered something funky, the record got saved like a hundred times or so, and then I got a stack overflow and a rollback. We found that we needed to override one more method on the user model to do the trick:
def email_was
super.present? ? AES.decrypt(super, KEY) : ""
end
Now all of our personally identifiable information is encrypted! Yay!

Get password inside authenticate_or_request_with_http_digest

I have an app which connects to an iphone app, which in turn authenticates it's users via http_digest.
I'm using authlogic, and in my schema users of the website are "users" and users of the phone app are "people". So, i have user_sessions and people_sessions. To handle the http_digest auth, i'm using the authenticate_or_request_with_http_digest method like this:
def digest_authenticate_person
authenticate_or_request_with_http_digest do |email, password|
#ldb is just a logging method i have
ldb "email = #{email.inspect}, password = #{password.inspect}"
person = Person.find_by_email(email)
if person
ldb "Authentication successful: Got person with id #{person.id}"
#current_person_session = PersonSession.create(person)
else
ldb "Authentication failed"
#current_person_session = nil
end
return #current_person_session
end
end
I can see in the logs that password is nil: only email is passed through to the inside of the authenticate_or_request_with_http_digest block.
Im testing this with a curl call like so:
curl --digest --user fakename#madeup.xyz:apass "http://localhost:3000/reports.xml"
I'd expect "fakename#madeup.xyz" and "apass" to get passed through to the inside of the block. Once i have the password then i can use a combination of email and password to find (or not) a user, in the normal way. Does anyone know how i can get access to the password as well?
grateful for any advice - max
EDIT - on further googling, i think i'm using this method wrong: i'm supposed to just return the password, or the crypted password. But then how do i compare that against the password passed as part of the http_digest username?
Found the answer: i had a fundamental misunderstanding of how authenticate_or_request_with_http_digest works: after reading the documentation (in the source code of the gem) i realised that the purpose of this method is not to do the authentication, its purpose is to provide the "email:realm:password" string to the browser, let the browser encrypt it, and check the result against it's own calculated (or cached) version of this.
Here's how i set it up:
def current_person
if #current_person
#current_person
else
load_current_person
end
end
#use in before_filter for methods that require an authenticated person (mobile app user)
def require_person
unless current_person
redirect_to root_path
end
end
def load_current_person
#check user agent to see if we're getting the request from the mobile app
if request.env['HTTP_USER_AGENT'] =~ /MobileAppName/
result = digest_authenticate_person
if result == 401
return 401
elsif result == true
#make authlogic session for person
#current_person_session = PersonSession.new(#person_from_digest_auth)
#current_person = #person_from_digest_auth
end
end
end
#this method returns either true or 401
def digest_authenticate_person
authenticate_or_request_with_http_digest(Person::DIGEST_REALM) do |email|
person = Person.find_by_email(email)
#result = nil
if person
#need to send back ha1_password for digest_auth, but also hang on to the person in case we *do* auth them successfully
#person_from_digest_auth = person
#result = person.ha1_password
else
#person_from_digest_auth = nil
#result = false
end
#result
end
end

Problem with Active Record Querying

I am building a little application in Rails and what I am trying to do now is authenticate a user.
So I got this method in the controller class:
def login
if #user = User.authenticate(params[:txt_login], params[:txt_password])
session[:current_user_id] = #user.id
redirect_to root_url
end
end
Here is the definition of authenticate method (inside the User model class):
def self.authenticate(username, password)
#user = User.where(["username = ? AND password = ?", username, password])
return #user
end
The problem is that I get an error message saying:
undefined method `id' for #<ActiveRecord::Relation:0x92dff10>
I confirm that the user I was trying to log in really exists in the database (besides it tries to get the id of a user and this instruction is wrapped inside an if in case 0 users are returned from the authenticate method).
Why am I obtaining this error message? Knowing that when I change the User.where by User.find it works fine!
Thank you!
User.where("some_conditions") will return an array of User objects ( in simple terms ) , A User.find can return an array or a single object.( I am not sure because i don't see how you are using it )
As far what you see is ActiveRecord::Relation, this is what is returned when we call a find or a where or a order method on Rails 3 Models.
Also, You are storing password as a plain string which is a bad idea, you should use some available rails authentication plugins like Devise or Authlogic.

Resources