I'm tryig to get the user address from facebook with Omniauth but did not work.
i added their address on update callback after login.
If i removed their address from omniauth the app did not update their address.
Someone have any idea how to get their address and why the app did not edit and update their address after the login?
thank's
def omniauth_callback
auth_hash = request.env['omniauth.auth']
user = User.find_by_uid(auth_hash[:uid])
if user.nil? && auth_hash[:info][:email]
user = User.find_by_email(auth_hash[:info][:email])
end
if user.nil?
email_domain = ''
email_domain = '#facebook.com' if auth_hash[:provider] == 'facebook'
user = User.new(email: auth_hash[:info][:email] || auth_hash[:info][:nickname] + email_domain, name: auth_hash[:info][:first_name] || '', surname: auth_hash[:info][:last_name] || '', gender: 'I')
user.password_digest = ''
user.save!(validate: false)
end
user.update_attribute(:login_at, Time.zone.now)
user.update_attribute(:address)
user.update_attribute(:neighborhood)
user.update_attribute(:postal_code)
user.update_attribute(:ip_address, request.remote_ip)
user.update_attribute(:provider, auth_hash[:provider])
user.update_attribute(:uid, auth_hash[:uid])
user.update_attribute(:oauth_token, auth_hash[:credentials][:token])
user.update_attribute(:oauth_expires_at, Time.at(auth_hash[:credentials][:expires_at]))
cookies[:auth_token] = { value: user.oauth_token, expires: user.oauth_expires_at}
redirect_to root_url
end
One reason your code will not work is because this
user.update_attribute(:address)
Doesn't do anything - except raise an error. You have to pass a value into update_attribute as well as specify the field.
Also as #mysmallidea points out, you'd be better advised to use update as that will allow you to update multiple fields in one database action.
If present, the address data will be within the auth_hash. So I suggest that you first work out the structure of that hash. In your development environment, add the following:
Rails.logger.info auth_hash.inspect
That will output the current auth_hash to logs/development.log. Use that to determine where in the hash the address data is. You'll then be able to do something like:
user.update address: auth_hash[:info][:address]
However, you may find that the address is not included in the data returned by the facebook oauth system. In which case you will need to return to their documentation to see if this is possible.
Related
I am working on a fairly large Rails app which due to large no of activities uses service objects (& light-services gem). Typically actions are performed in steps in the services, with many of the functions/objects used in these steps instantiated in the model it is working with (Customer.rb) in this case.
In one step I am trying to generate a Devise reset_password_token, but hit a glitch
Customer.rb
def self.generate_token
Devise.token_generator
end
and then in service object reset_password.rb
def generate_token
raw, hashed = Devise.token_generator.generate(Customer, :reset_password_token)
producer.reset_password_token = hashed
producer.reset_password_token_sent_at = Time.now.utc
#token = raw
end
All that goes in to db is the timestamp, and a nil, nil for token although token gets put in to the url endpoint so is being generated
If anyone encountered this scenario before, tips more than welcome!
Here is rest of code, not setting the token in DB properly.
def validate_producer_email
self.valid_email = EmailValidator.valid?(producer.email)
end
# #return [TrueClass, FalseClass]
def validate_token
self.producer.reset_password_token = reset_password_token, params['reset_password_token']
customer = Customer.producer.find_by(reset_password_token: reset_password_token)
# true if customer.email && Devise.secure_compare(reset_password_token, params[:reset_password_token])
customer.present?
end
def update_producer
return if ask_underwriter?
Producer::Update
.with(self)
.run(
producer: producer,
force: current_administrator.blank?,
params: {
customer: {
reset_password_token: reset_password_token
}
}
)
end
If anyone has any tips on how to fix?
Thanks
I'm pretty new to Rails but we have an app using Devise and Omniauth for authentication and have recently integrated Omniauth-Saml by following the Omniauth documentation for Devise integration: https://github.com/omniauth/omniauth-saml#devise-integration The authentication works and we can create users and use these accounts without any issues.
In the SAML response attributes is an lacode (4-digit string). We want to check this user attribute against a reference lacode. If their cag matches the reference cag we want to set the verified_at attribute in the user.rb model.
I've updated the user model and to test if I set the oauth_lacode to "9064" to match the oauth_lacode_ref then the code works and the user's verified_at time and date are set at point of account creation.
app/models/user.rb
# Get the existing user by email if the provider gives us a verified email.
def self.first_or_initialize_for_oauth(auth)
oauth_email = auth.info.email
oauth_email_confirmed = oauth_email.present? && (auth.info.verified || auth.info.verified_email)
oauth_lacode = auth.extra.raw_info.lacode
oauth_lacode_ref = "9064"
oauth_lacode_confirmed = oauth_lacode == oauth_lacode_ref
oauth_user = User.find_by(email: oauth_email) if oauth_email_confirmed
oauth_user || User.new(
username: auth.info.name || auth.uid,
email: oauth_email,
oauth_email: oauth_email,
password: Devise.friendly_token[0, 20],
terms_of_service: "1",
confirmed_at: oauth_email_confirmed ? DateTime.current : nil,
verified_at: oauth_lacode_confirmed ? DateTime.current : nil
)
end
I'm not mapping and calling the lacode from the hash correctly as I see this error in the log "NoMethodError (undefined method `lacode' for #OneLogin::RubySaml::Attributes:0x00007f7a5040ad40):"
This is how I'm mapping the attributes in config/initializers/devise.rb
attribute_statements: { email: ['urn:oid:0.9.2342.19200300.100.1.22'],
lacode: ['urn:oid:0.9.2342.19200300.100.1.17']}
I have confirmed with the IDP that 'urn:oid:0.9.2342.19200300.100.1.17' is mapped to the lacode in the SAML response.
As in the User model above, this is how I'm trying to access the lacode from within the User model.
"saml_cag = auth.extra.raw_info.lacode"
This is the guidance from Omniauth Saml:
:attribute_statements - Used to map Attribute Names in a SAMLResponse
to entries in the OmniAuth info hash. For example, if your
SAMLResponse contains an Attribute called 'EmailAddress', specify
{:email => ['EmailAddress']} to map the Attribute to the corresponding
key in the info hash. URI-named Attributes are also supported, e.g.
{:email =>
['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress']}.
Note: All attributes can also be found in an array under
auth_hash[:extra][:raw_info], so this setting should only be used to
map attributes that are part of the OmniAuth info hash schema.
Does this sentence at the end mean I don't need to/can't map the attribute. Can anyone help or point me in the right direction?
I managed to get this working. Only attribute names specified in the Omniauth Hash Schema can be used.
Mapping the lacode to description in the attribute statement, I was able to access it using "auth.info.description"
What is the proper way to pass the parameters to a function?
For example:
def self.find_by_example(username, email)
user = User.find_by_username(username) || User.find_by_email(email)
end
I would like to find the user by his username or email but if a create a function passing the 2 parameters Rails shows
(wrong number of arguments (given 1, expected 2))
When I call User.find_by_example('example')
I still don't get it, the parameters passed in must not be the attribute?
and why does it say "given 1"?
You must be calling the function like `User.find_by_example("name to find") and the function expects two arguments (name and email). You could define the function as:
def self.find_by_example(term)
user = User.find_by_username(term) || User.find_by_email(term)
end
And call it User.find_by_example("Name to find") or User.find_by_example("email#to_find.com")
This does not work ok if you have users with a username like an email. And it is not much efficient if you wish to search by other fields. SO you could also:
def self.find_by_example(terms)
if terms[:email]
user = User.find_by_email(terms[:email])
elsif terms[:username]
user = User.find_by_username(terms[:username])
elsif terms[:id]
user = User.find_by_id(terms[:id])
elsif terms[:document]
user = User.find_by_document(terms[:document])
end
end
And call the method User.find_by_example(:email => "email#example.com"). This is similar to the find_by method that Active Record already provides (but allows many arguments), so no need to implement it.
The proposed and accepted answer is not really the equivalent of the code asked in the question. It is accepted, so one might assume that it guessed the OP intent correctly. But I think it can be useful for (especially junior) programmers to think about the problem more deeply.
Think of what method should do
(not just if it immediately gives you result you wish to see, there can be surprises in the edge cases)
The original code
def self.find_by_example(username, email)
user = User.find_by_username(username) || User.find_by_email(email)
end
Could be used this way x.find_by_example(nil, 'test#example.com').
If we assume there can't be users with NULL username (which IMO is a reasonable assumption), the call would result in finding an user strictly by email.
The proposed solution does not give you this possibility:
def self.find_by_example(term)
user = User.find_by_username(term) || User.find_by_email(term)
end
x.find_by_example('test#example.com') would return user with such username if exists, and (possibly other) user with such e-mail otherwise.
In other words - you have less control which field is used to find a user (which can be correct, if that's really what you need)
So it depends on the OP intent.
If one want to retain how the original method works, but improve the interface, it could be done like this:
def self.find_by_example2(username: nil, email: nil)
user = User.find_by_username(username) || User.find_by_email(email)
end
And calling x.find_by_example2(email: 'test#example.com') is equivalent to x.find_by_example(nil, 'test#example.com') but looks better.
Bonus: Performance implications
The proposed solution
def self.find_by_example(term)
user = User.find_by_username(term) || User.find_by_email(term)
end
makes second query when the user is not find by username. You can improve it as well if you wish to employ some sql magic:
def self.find_by_example(term)
user = User.where("username = ? OR (username IS NULL and email = ?)", term, term).first
end
There's another possibility (though not 100% equivalent to the accepted solution):
def self.find_by_example(term)
user = User.where("username = ? OR email = ?", term, term).first
end
(I'll leave as an exercise the answer how those are different, to keep this post short...ish)
Bonus 2: flexibility
This
def self.find_by_example(terms)
if terms[:email]
user = User.find_by_email(terms[:email])
elsif terms[:username]
user = User.find_by_username(terms[:username])
elsif terms[:id]
user = User.find_by_id(terms[:id])
elsif terms[:document]
user = User.find_by_document(terms[:document])
end
end
is a waste of your time, because rails gives you already better interface to do this.
Instead of calling
x.find_by_example(document: 'foo')
you could just do
User.find_by(document: 'foo')
There's really no need to implement it that way, it's basically crippled version of ActiveRecord interface, that you have to maintain as you add new fields to User model.
I would like to create rake task to set the username of all users' without a username to the part before the '#' in their email address. So if my email is test#email.eu, my username should become test. If it's not available, prepend it by a number (1).
So i have problem witch checking uniqness of username. Code below isn`t working after second loop ex: when i have three emails: test#smt.com, test#smt.pl, test#oo.com username for test#oo.com will be empty.
I have of course uniqness validation for username in User model.
desc "Set username of all users wihout a username"
task set_username_of_all_users: :environment do
users_without_username = User.where(:username => ["", nil])
users_without_username.each do |user|
username = user.email.split('#').first
users = User.where(:username => username)
if users.blank?
user.username = username
user.save
else
users.each_with_index do |u, index|
pre = (index + 1).to_s
u.username = username.insert(0, pre)
u.save
end
end
end
end
Other ideas are in Gist: https://gist.github.com/3067635#comments
You could use a simple while loop for checking the username:
users_without_username = User.where{ :username => nil }
users_without_username.each do |user|
email_part = user.email.split('#').first
user.username = email_part
prefix = 1
while user.invalid?
# add and increment prefix until a valid name is found
user.username = prefix.to_s + email_part
prefix += 1
end
user.save
end
However, it might be a better approach to ask the user to enter a username upon next login.
if i understand your code correct, you are changing the username of existing users in the else branch? that does not look as if it's a good idea.
you should also use a real finder to select your users that don't have a username. otherwise you will load all the users before selecting on them.
i don't know if it "matches your requirements" but you could just put a random number to the username so that you do not have the problem of duplicates.
another thing that you can use is rubys retry mechanism. just let active-record raise an error and retry with a changed username.
begin
do_something # exception raised
rescue
# handles error
retry # restart from beginning
end
In your query User.find_by_username(username), you only expect 1 record to be provided. So you don't need any each. You should add your index in another way.
I'm trying to run the following unit-test:
def test_passwordchange
# check success
assert_equal #longbob, Usuario.autenticar("longbob", "longtest")
#change password
#longbob.password = "nonbobpasswd"
#longbob.password_confirmation = "nonbobpasswd"
assert #longbob.save!
#new password works
assert_equal #longbob, Usuario.autenticar("longbob", "nonbobpasswd")
#old pasword doesn't work anymore
assert_nil Usuario.autenticar("longbob", "longtest")
#change back again
#longbob.password = "longtest"
#longbob.password_confirmation = "longtest"
assert #longbob.save!
assert_equal #longbob, Usuario.autenticar("longbob", "longtest")
assert_nil Usuario.autenticar("longbob", "nonbobpasswd")
end
However, it throws error on the 1st line that contains "assert_equal" that says:
<#<Usuario ID: 1000003, login: "longbob", hashed_password: "078cf6ae2de80ed6c004c8c8576a5572e077a52c", salt: "1000", nombre: nil, apellido: nil, email: "lbob#mcbob.com", telefono: nil, tipo_usuario: nil, foto: nil, bol_activo: nil>> expected but was <nil>.
Here's my authenticate method:
def self.authenticate (login, pass)
u=find(:first, :conditions=>["login = ?", login])
return nil if u.nil?
return u if Usuario.encrypt(pass, u.salt)==u.hashed_password
nil
end
Also, I defined the following:
def password=(pass)
#password=pass
self.salt = Usuario.random_string(10) if !self.salt?
self.hashed_password = Usuario.encrypt(#password, self.salt)
end
So, I guess that should update the hashed_password every time I reassigned something to "password"... right?
Whats happening?
Thx.
UPDATE: I noticed that if I change:
assert_equal #longbob,
Usuario.autenticar("longbob",
"nonbobpasswd")
to
assert_equal #longbob2,
Usuario.autenticar("longbob",
"nonbobpasswd")
It passes that test, however it fails in the following line... Trowing the same error... What's up with that?
To answer after my comment, we find out that the problem is the call to save does not update the record in the database. I have suggested you to write another test to see this behaviour and be able to fix it, the test you have writed start to be too long and in fact the bug does not have anything related to the authenticate mechanism.
Here is the test I would write :
def test_change_password_save
old_hash = #longbob.hashed_password
#longbob.password = "nonbobpasswd"
#longbob.password_confirmation = "nonbobpasswd"
#longbob.save
assert_not_equal(old_hash, #longbox.reload.hashed_password)
end
If this test failed, I would suggest you to write another question in stackoverflow for this bug.
You're probably getting a validation error. Check the contents of #longbob.errors.
What happens if you split this into two separate statements? (Which is good practice anyway)
#longbob.password = #longbob.password_confirmation = "nonbobpasswd"
See, #password_confirmation= is actually a method, which might not return the value that was passed to it, depending on how the method was implemented.
Is it possible that changing the password variables doesn't update the hashed_password field in the database.
You probably need something like this in Usuario:
before_save :rehash
def rehash
self.hashed_password = ??? #replace ??? with whatever your hashing logic is
end