I am using devise omniauth in my rails application, here is the User class
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable
attr_accessible :email, :password, :password_confirmation, :remember_me, :encrypted_password, :fb_id
def set_facebook_info(info)
#facebook_info = info
end
def get_facebook_info
#facebook_info
end
def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
data = access_token.extra.raw_info
if user = User.find_by_email(data.email)
if not user.fb_id
user.fb_id = access_token.uid
user.save
end
user.set_facebook_info "whatever" <-- I tried here
user
else # Create a user with a stub password.
user = User.create(:email => data.email, :password => Devise.friendly_token[0,20], :fb_id => access_token.uid)
user
end
end
def self.new_with_session(params, session)
super.tap do |user|
user.set_facebook_info "whatever" # <-- I Tried here too
if data = session["devise.facebook_data"] && session["devise.facebook_data"]["extra"]["user_hash"]
user.email = data["email"]
end
end
end
end
I want to use set and get methods for keeping some facebook user information. but when I use current_user in view, it gives me no value of what I've set in my User class.
like in application.html.erb :
<span><%= current_user.get_facebook_info %></span>
returns an empty value
Does anybody has an idea about it ? It should be a common case. in general , how can we assign some non connected to DB attribute to current_user via devise ?
Thanks
Your #facebook_info attribute is only stored in memory, not persisted to the database. So on the next page load, Devise is going to load your model from the database again, and that is why the information is missing.
If you want to keep the Facebook info, you need to persist it to the database. Rails has some nice ways of storing hashes directly in a text column:
class User < ActiveRecord::Base
serialize :preferences
end
user = User.create(:preferences => { "background" => "black", "display" => large })
User.find(user.id).preferences # => { "background" => "black", "display" => large }
Source: http://api.rubyonrails.org/classes/ActiveRecord/Base.html
Related
I've been able to complete the oauth2 login process and grant the current_user access to the application with Devise. My main goal was to iterate over the user's positions from LinkedIn and create a Position.create(..) object for each individual position & then save their user_id to the object after a successful LinkedIn login attempt. Afterwards, I would just display the information inside of the app. It should check for a Position where its first_or_create, to avoid duplicating the data on each login attempt. How can I get the user's positions and company hash (JSON object) to store inside of the database?
I created a position model with a company JSON serializable attribute. I attempted to retrieve the user's profile using the auth.extra.info.positions fields. Afterwards, I tried to retrieve the information from the user in session. I could not succeed in getting an array of Positions back.
create_positions.rb | migration
class CreatePositions < ActiveRecord::Migration[5.2]
def change
create_table :positions do |t|
t.string :title
t.text :summary
t.date :start_date
t.date :end_date
t.boolean :is_current
t.json :company
t.references :user, foreign_key: true
t.timestamps
end
end
end
user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :omniauthable, :trackable, :confirmable,
:omniauth_providers => [:twitter, :facebook, :linkedin, :google_oauth2,
*(:developer if Rails.env.development?)]
has_many :positions
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.name = auth.info.name
user.password = Devise.friendly_token[0, 20]
user.provider = auth.provider
user.uid = auth.uid
end
end
protected
def confirmation_required?
false
end
end
position.rb
class Position < ApplicationRecord
serialize :company, JSON
belongs_to :user, dependent: :destroy
end
omniauth_callbacks_controller.rb
# frozen_string_literal: true
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :verify_authenticity_token
def sign_in_with(provider_name)
#user = User.from_omniauth(request.env['omniauth.auth'])
sign_in_and_redirect #user, event: :authentication
set_flash_message(:notice, :success, kind: provider_name) if is_navigational_format?
end
def save_user_positions
Position.create(:title => auth.extra.info.positions['title'], :summary => auth.extra.info.positions['summary'], :start_date => auth.extra.info.positions['start-date'], :end_date => auth.extra.info.positions['end-date'], :is_current => auth.extra.info.positions['is-current'], company: {id: auth.extra.info.positions.company['id']})
end
def facebook
sign_in_with 'Facebook'
end
def linkedin
sign_in_with 'LinkedIn'
end
def twitter
sign_in_with 'Twitter'
end
def google_oauth2
sign_in_with 'Google'
end
def developer
sign_in_with 'Developer'
end
end
Expected Results: When User logs into LinkedIn through omniauth, their positions/companies are saved to the rails database.
Actual Results: Only user email/name/basic attributes are retrievable.
I edited devise's RegistrationsController::create method to modify slightly the behaviour. What I'm trying to do is that if there are no admin users in the database, the one that first signs up is going to be assigned the admin role, else it will be a regular user.
However, the role, though assigned correctly to the object (tested), it's not being persisted to the database.
Model:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessor :role
Roles = [ :admin, :default ]
def is? requested_role
self.role == requested_role.to_s
end
def self.admin_role
return Roles[0]
end
def self.default_role
return Roles[1]
end
end
Modified devise method:
def create
build_resource(sign_up_params)
admin_user = User.find_by_role(User.admin_role)
if admin_user.nil?
resource.role = User.admin_role
else
resource.role = User.default_role
end
# here puts resource.role shows what's expected is indeed being assigned to the object
if resource.save
...
end
end
Why isn't the role being stored in the database? Why is it NULL?
You don't need the attr_accessor for :role if you have this defined as a column on your table. ActiveRecord gives you the database backed accessors just by having the relevant column defined in the relevant table.
Your attr_accessor will be overriding these and preventing them from persisting your changes to the database.
I'm trying to display all the comments made from a user in a User profile page (no matter if it's a public or a page behind the login), but I'm experiencing an issue that prints in my app Comment::ActiveRecord_Associations_CollectionProxy:0x00000103c89750 (it's not an error that kills my app, just prints that message).
the only element commentable within my app are 'hacks', and users can create comments on each hack.
user.rb
class User < ActiveRecord::Base
TEMP_EMAIL_PREFIX = 'change#me'
TEMP_EMAIL_REGEX = /\Achange#me/
include Gravtastic
gravtastic
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, :confirmable
validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update
def self.find_for_oauth(auth, signed_in_resource = nil)
# Get the identity and user if they exist
identity = Identity.find_for_oauth(auth)
# If a signed_in_resource is provided it always overrides the existing user
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
# Create the user if needed
if user.nil?
# Get the existing user by email if the provider gives us a verified email.
# If no verified email was provided we assign a temporary email and ask the
# user to verify it on the next step via UsersController.finish_signup
email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email)
email = auth.info.email if email_is_verified
user = User.where(:email => email).first if email
# Create the user if it's a new registration
if user.nil?
user = User.new(
name: auth.extra.raw_info.name,
#username: auth.info.nickname || auth.uid,
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0,20]
)
user.skip_confirmation!
user.save!
end
end
# Associate the identity with the user if needed
if identity.user != user
identity.user = user
identity.save!
end
user
end
def email_verified?
self.email && self.email !~ TEMP_EMAIL_REGEX
end
include TheComments::User
has_many :hacks
has_many :comments
def admin?
self == User.first
end
def comments_admin?
admin?
end
def comments_moderator? comment
id == comment.holder_id
end
end
comment.rb
class Comment < ActiveRecord::Base
belongs_to :user
include TheComments::Comment
# ---------------------------------------------------
# Define comment's avatar url
# Usually we use Comment#user (owner of comment) to define avatar
# #blog.comments.includes(:user) <= use includes(:user) to decrease queries count
# comment#user.avatar_url
# ---------------------------------------------------
# public
# ---------------------------------------------------
# Simple way to define avatar url
#
# def avatar_url
# src = id.to_s
# src = title unless title.blank?
# src = contacts if !contacts.blank? && /#/ =~ contacts
# hash = Digest::MD5.hexdigest(src)
# "https://2.gravatar.com/avatar/#{hash}?s=42&d=https://identicons.github.com/#{hash}.png"
# end
# ---------------------------------------------------
# private
# ---------------------------------------------------
# Define your content filters
# gem 'RedCloth'
# gem 'sanitize'
# gem 'MySmilesProcessor'
#
# def prepare_content
# text = self.raw_content
# text = RedCloth.new(text).to_html
# text = MySmilesProcessor.new(text)
# text = Sanitize.clean(text, Sanitize::Config::RELAXED)
# self.content = text
# end
# ---------------------------------------------------
end
hack.rb
class Hack < ActiveRecord::Base
belongs_to :user
acts_as_taggable # Alias for acts_as_taggable_on :tags
acts_as_taggable_on :tags
has_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }
validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
#For commenting
include TheComments::Commentable
# Denormalization methods
# Please, read about advanced using
def commentable_title
"Undefined Post Title"
end
def commentable_url
"#"
end
def commentable_state
"published"
end
end
user view
<p>
<strong>User email?:</strong>
<%= #user.email %>
<%= #user.comcoms %>
</p>
The gem I'm using for comments is The_Comments and there are some docs here that I have read widely, and I think #user.comcoms should return what I'm looking for but not :/
#user.comcoms will give all comments related to the particular users(#user in your case) commentable models. So, its going to return a collection of comments and Comment::ActiveRecord_Associations_CollectionProxy:0x00000103c89750 is showing the reference of that collection.
You need to iterate over the collection and display the required fields from an instance of Comment class.
Replace
<%= #user.comcoms %>
With
<% #user.comcoms.each do |comment| %>
<%= comment.raw_content %>
<%= comment.commentable_title %>
<%# Add other attributes %>
<% end %>
Refer to the link that you added in the question, for the attributes that can be accessed on Comment Comment API
So I was trying to use omniauth2 to check if the email had the right #domain.com but I think using a database table will allow more functionality as well as being more secure and such.
My previous question: Restrict Login with Google OAuth2.0 to Specific Whitelisted Domain Name on Ruby
I think I want to use a database table to check the email that google authenticated against a whitelist of emails, is there anyway to do this with devise and omniauth2? That way I can say only certain users are authorized after they get authenticated with Google. I have most info listed on my previous question but if there is some more info I can give let me know.
Thanks.
EDIT: Not sure how much this helps but here is a question similar; however, I am still using google and omniauth Whitelisting with devise
EDIT: I think the above "Whitelisting with devise" is pretty close to the answer, but there are still a few kinks to work out. I'm not sure how to start implementing everything I'm pretty new to ruby in particular.
Here is my route:
devise_for :user, :controllers => { :omniauth_callbacks => "user/omniauth_callbacks" }
And that controller:
class User::OmniauthCallbacksController < Devise::OmniauthCallbacksController
def google_oauth2
#user = User.find_for_google_oauth2(request.env["omniauth.auth"], current_user)
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.google_data"] = request.env["omniauth.auth"]
redirect_to new_user_registration_url
end
end
end
If I understand the Whitelisting with devise correctly I have to create another controller in between and use that to check the email? Any help would be greatly appreciated.
EDIT: Here is my user.rb I think this might hold the answer possibly?:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable, :omniauthable,
:recoverable, :rememberable, :trackable, :validatable, :omniauth_providers => [:google_oauth2]
attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :provider, :uid, :avatar
def self.find_for_google_oauth2(access_token, signed_in_resource=nil)
data = access_token.info
user = User.where(:email => data["email"]).first
unless user
user = User.create(name: data["name"],
email: data["email"],
password: Devise.friendly_token[0,20]
)
end
user
end
end
I'd add a validation to the User Model so, no user would be created if the email that comes from oauth is not form a certain domain:
validates :email,
presence: true,
uniqueness: true,
format: {
message: 'domain must be example.com',
with: /\A[\w+-.]+#example.com\z/i
}
This is it, for all the marbles, if I can get this issue solved then I have a project completed.
Anyway, I am using Ruby on Rails 3 with Devise for user authentication. As you may know, in the user admin/edit by default, a user has to enter their current password in the current_password field if they provide a new password. There is a TON of information out there on how to disable current_password so users can change and save freely.
However, I can find very little on doing the opposite: requiring the current password for more fields...in my case, the email field. AND only require the current password when that email addy is changed, not if it remains the same. Currently users can freely change their email without giving their current password, and for security reasons, I don't want this.
After looking through the Devise wiki, I did find this page and I thought I could reverse this code to complete this solution. I managed to work this little bit out in my user.rb model (I stripped out of the unnecessary logic for this post)....
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :confirmable, :lockable and :timeoutable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :name, :email, :password, :password_confirmation, :avatar, :remember_me, :agree
attr_accessor :accessible, :agree, :signed_in
attr_readonly :name
# Validation
validates :name, :presence => TRUE, :uniqueness => TRUE, :length => { :within => 4..20 }
validates :agree, :term_agreement => TRUE, :unless => :signed_in
validates_attachment_size :avatar, :less_than => 1.megabyte
validates_attachment_content_type :avatar, :content_type => ['image/jpeg', 'image/png', 'image/gif']
validates :current_password, :presence => TRUE, :if => :password_required?
protected
def password_required?
email_changed?
end
end
It "almost" works. If I save the user profile with changing nothing, or change other non-password required field (like the user avatar), the profile saves fine, no password required. So far, so good.
And if I change the email address, the validation is triggered....but what happens is that both the current_password AND password (for new password) fields trigger as required. And if I fill in the password in all three (password, password_confirmation, current_password) just for the hell of it, it won't take, just gives a validation error again.
Basically, ONLY the current_password should be required if the email address is changed. How would I make this work in my code?
UPDATE *******
I checked out my log in response to the below suggestion by bowsersenior, and see the following lines when I attempt to save and update the email...
User Load (0.2ms) SELECT `users`.`id` FROM `users` WHERE (LOWER(`users`.`email`) = LOWER('newaddress#changed.com')) AND (`users`.id <> 1) LIMIT 1
User Load (0.2ms) SELECT `users`.`id` FROM `users` WHERE (`users`.`name` = BINARY 'Administrator') AND (`users`.id <> 1) LIMIT 1
SQL (0.1ms) ROLLBACK
I wonder if that 'ROLLBACK' has something to do with the final issue?
Give this a try:
validates :current_password, :presence => TRUE, :if => :email_changed?
I strongly suggest you leave password_required? alone. That could lead to bugs with security and unstable behavior.
I believe the Devise way of doing this is as follows:
class RegistrationsController < Devise::RegistrationsController
def update
#user = User.find(current_user.id)
successfully_updated = if needs_password?(#user, params)
#user.update_with_password(params[:user])
else
# remove the virtual current_password attribute update_without_password
# doesn't know how to ignore it
params[:user].delete(:current_password)
#user.update_without_password(params[:user])
end
if successfully_updated
set_flash_message :notice, :updated
redirect_to after_update_path_for(#user)
else
render "edit"
end
end
private
def needs_password?(user, params)
user.email != params[:user][:email]
end
end
In other words, everything happens at the Controller level and we use either User#update_with_password, when user want to change his email (we know that by invoking needs_password? in the update action) or User#update_without_password, when user changes his other profile data.
Also remember you need to "register" this new RegistrationsController in your routes:
devise_for :users, :controllers => { :registrations => "registrations" }
Source:
https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-account-without-providing-a-password
Similar to Pawel's answer, but rather than override the controller method update, how about this, based on the devise wiki
class RegistrationsController < Devise::RegistrationsController
protected
def update_resource(resource, params)
if resource.email != params[:email] || params[:password].present?
super
else
params.delete(:current_password)
resource.update_without_password(params)
end
end
end