Rails 4,Devise&具有多个提供程序的{Omiouth -setup配置

时间:2015-12-14 01:05:12

标签: ruby-on-rails devise omniauth

我正在努力(拼命)用Devise和Omniauth设置我的Rails 4应用程序。

我目前的努力是努力让本教程适合我。

http://sourcey.com/rails-4-omniauth-using-devise-with-twitter-facebook-and-linkedin/

目前,我有:

的Gemfile:

gem 'devise', '3.4.1'
gem 'devise_zxcvbn'
gem 'omniauth'
gem 'omniauth-oauth2', '1.3.1'
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
gem 'omniauth-twitter'
gem 'omniauth-linkedin-oauth2'
gem 'google-api-client', require: 'google/api_client'
# gem 'oath'

我的gem文件和教程之间的主要区别在于教程不包含omniauth-oauth2 gem。我已将此包含在内,因为它是谷歌的要求(教程不使用)。

Identity.rb:

class Identity < ActiveRecord::Base


  belongs_to :user
  validates_presence_of :uid, :provider
  validates_uniqueness_of :uid, :scope => :provider


  def self.find_for_oauth(auth)
    find_or_create_by(uid: auth.uid, provider: auth.provider)
  end


end

我的devise.rb包含4个策略提供商中每个提供商的config.omniauth。

我的配置/环境文件包含邮件程序操作的电子邮件设置。

routes.rb中:

devise_for :users, #class_name: 'FormUser',
             :controllers => {
                # :registrations => "users/registrations",
                # :omniauth_callbacks => "users/authentications"
                :omniauth_callbacks => 'users/omniauth_callbacks'
           }
  match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup

Omniauth回调控制器:

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def self.provides_callback_for(provider)
    class_eval %Q{
      def #{provider}
        @user = User.find_for_oauth(env["omniauth.auth"])

        if @user.persisted?
           sign_in_and_redirect @user,  event: :authentication

          set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
        else
          session["devise.#{provider}_data"] = env["omniauth.auth"]
          redirect_to new_user_registration_url
        end
      end
    }
  end



  [:twitter, :facebook, :linkedin, :google_oauth2].each do |provider|
    provides_callback_for provider
  end

  def after_sign_in_path_for(resource)
    if resource.email_verified?
      super resource
    else
      finish_signup_path(resource)
    end
  end 

end

教程和我的代码之间的主要区别:我已经包含了谷歌策略

User.rb

class User < ActiveRecord::Base

  TEMP_EMAIL_PREFIX = 'change@me'
  TEMP_EMAIL_REGEX = /\Achange@me/

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable,
          :confirmable, :lockable,
         # :zxcvbnable,
         :omniauthable, :omniauth_providers => [:facebook, :linkedin, :twitter, :google_oauth2 ]



  mount_uploader :image, AvatarUploader

  # --------------- associations

  has_many :articles
  has_many :authentications, :dependent => :delete_all

  has_many :comments

  belongs_to :organisation
  has_one :profile
  has_many :qualifications
  has_many :identities

  has_many :trl_assessments, as: :addressable

  # has_and_belongs_to_many :projects

  # --------------- scopes

  # --------------- validations

   # validates_presence_of :first_name, :last_name
   validates_uniqueness_of :email

  # per sourcey tutorial - how do i confirm email registrations are unique?
  # this is generating an error about the options in the without function -- cant figure out the solution
  validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update 

  # --------------- class methods


# sourcey tutorial

 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

    # p '11111'

    # Create the user if needed
    if user.nil?
      # p 22222
      # 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?
        # p 33333
        user = User.new(
          # at least one problem with this is that each provider uses different terms to desribe first name/last name/email. See notes on linkedin above
          first_name: auth.info.first_name,
          last_name: auth.info.last_name,
          email: email,
          #username: auth.info.nickname || auth.uid,
          password: Devise.friendly_token[0,20])
# 
        # debugger

        if email_is_verified
           user.skip_confirmation!
        end
        # 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 && TEMP_EMAIL_REGEX =~ self.email
  end



    def full_name
        [*first_name.capitalize, last_name.capitalize].join(" ")
    end


  def formal_name
      [*self.profile.try(:title), first_name.capitalize, last_name.capitalize].join(" ")
  end


  #----------- user to be approved by admin (for now); later approved by approver


  def disapprove
    self.approved = false
  end

  def approved
    self.approved = true
  end

  def active_for_authentication?
    super && approved?
  end

  def inactive_message
    if !approved?
      :not_approved
    else
      super # Use whatever other message
    end
  end

  def self.send_reset_password_instructions(attributes={})
    recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
    if !recoverable.approved?
      recoverable.errors[:base] << I18n.t("devise.failure.not_approved")
    elsif recoverable.persisted?
      recoverable.send_reset_password_instructions
    end
    recoverable
  end


  # --------------- callbacks

  # after_create :add_default_role

  # --------------- instance methods

  # --------------- private methods

  # def add_default_role
  #   add_role(:pending) if self.roles.blank?
  # end

end

我的代码和教程之间的主要区别:

  1. 将名称更改为在名字和姓氏之间拆分(以适合我的架构),因此使用了auth.info而不是原始信息。

  2. 在本教程中,find_for_oauth方法会创建一个新用户,其电子邮件属性包含问号和电子邮件的替代方法?我删除了问号以及它右侧的所有内容,因为我无法通过此行中的错误&amp;我无法理解它想要做什么。

  3. 我尝试使用if email_is_verified方法处理确认异常,而不是仅仅跳过确认。

  4. 我将email_verified方法更改为:

    self.email&amp;&amp; TEMP_EMAIL_REGEX = ~self.email

  5. 我不知道为什么会发生这种变化 - 我试图通过这些问题获得codementor.io的帮助,这是所做的更改之一(没有解释原因)。

    1. 我有额外的验证来检查电子邮件地址的唯一性。
    2. 用户控制器:

      class UsersController < ApplicationController
      
      before_action :set_user, only: [:index, :show, :edit, :update, :destroy]
      
        def index
          if params[:approved] == "false"
            @users = User.find_all_by_approved(false)
          else
            @users = User.all
          end
      
        end
      
        # GET /users/:id.:format
        def show
          # authorize! :read, @user
        end
      
        # GET /users/:id/edit
        def edit
          # authorize! :update, @user
        end
      
        # PATCH/PUT /users/:id.:format
        def update
          # authorize! :update, @user
          respond_to do |format|
            if @user.update(user_params)
              sign_in(@user == current_user ? @user : current_user, :bypass => true)
              format.html { redirect_to @user, notice: 'Your profile was successfully updated.' }
              format.json { head :no_content }
            else
              format.html { render action: 'edit' }
              format.json { render json: @user.errors, status: :unprocessable_entity }
            end
          end
        end
      
        # GET/PATCH /users/:id/finish_signup
        def finish_signup
          # authorize! :update, @user 
          if request.patch? && params[:user] #&& params[:user][:email]
            if @user.update(user_params)
              @user.skip_reconfirmation!
              sign_in(@user, :bypass => true)
              redirect_to @user, notice: 'Your profile was successfully updated.'
            else
              @show_errors = true
            end
          end
        end
      
        # DELETE /users/:id.:format
        def destroy
          # authorize! :delete, @user
          @user.destroy
          respond_to do |format|
            format.html { redirect_to root_url }
            format.json { head :no_content }
          end
        end
      
        private
          def set_user
            @user = User.find(params[:id])
          end
      
          def user_params
            params.require(:user).permit(policy(@user).permitted_attributes)
            # accessible = [ :first_name, :last_name, :email ] # extend with your own params
            # accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank?
            # accessible << [:approved] if user.admin
            # params.require(:user).permit(accessible)
          end
      
      end
      

      教程的主要变化:强权参数现在在权威用户策略中定义(完全按照上面的注释),尽管我已经尝试用控制器中的强参数来解决这个问题。它对当前的问题没有任何影响。

      完成注册表单:

      <%= form_for(current_user, :as => 'user', :url => 
      finish_signup_path(current_user), :html => { role: 'form'}) do |f| %>
              <% if @show_errors && current_user.errors.any? %>
      
                <div id="error_explanation">
                <% current_user.errors.full_messages.each do |msg| %>
                  <%= msg %><br>
                <% end %>
                </div>
              <% end %>
      
          <div class="form-group">
            <!--  f.label :false  -->
            <div class="controls">
              <%= f.text_field :email, :autofocus => true, :value => '', class: 'form-control input-lg', placeholder: 'Example: email@me.com -- use your primary work address' %>
              <p class="help-block">Please confirm your email address. No spam.</p>
            </div>
          </div>
          <div class="actions">
            <%= f.submit 'Continue', :class => 'btn btn-primary' %>
      

      目前,当我尝试在twitter上注册时,收到此错误消息:

      ActiveRecord::StatementInvalid in Users::OmniauthCallbacksController#twitter
      PG::NotNullViolation: ERROR: null value in column "email" violates not-null constraint DETAIL: Failing row contains (19, null, null, null, $2a$10$aye71.1oEClA5vsy..8WzeknYl9wxi7W8VDEGlNbzakQQyJqe8i8q, null, null, null, 0, null, null, null, null, 
      

      4e25979ab053eb4a26a6ba451d9, null, 2015-12-14 01:00:08.431268, null, 0, null, null, 2015-12-14 01:00:08.428961, 2015-12-14 01:00:08.428961, null, f). : INSERT INTO "users" ("email", "encrypted_password", "created_at", "updated_at", "confirmation_token", "confirmation_sent_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"

      我不知道这个错误意味着什么,或者我为什么会这样做。我认为这很可能与我的代码和上面提到的教程之间的差异有关,但是我无法看到它们中的哪一个导致了问题或者如何解决它。

      当我使用linkedin尝试相同的操作时,我收到此错误:

      NoMethodError (undefined method `approved?' for #<User:0x007fa90c867600>):
      2015-12-14T01:02:10.472783+00:00 app[web.1]:   app/models/user.rb:130:in `active_for_authentication?'
      

      我的架构中有一个已批准的属性。我还添加了一个名为&#39; approved&#39;我的用户模型试图解决这个问题,但它没有工作。

      任何人都可以看到任何可能有用的东西。我一直试图在3年的最佳时间内完成这项工作。我已经将$$$$转入代码导师,支付了各种顾问,并参加了我所在地区的所有会议,但我找不到任何可以提供帮助的人。

      考虑到Moustafa关于linkedin问题的建议:

      我从user.rb中注释掉了这些批准方法:

      # def disapprove
        #   self.approved = false
        # end
      
        # def approved?
        #   self.approved = true
        # end
      
        # def active_for_authentication?
        #   super && approved?
        # end
      
        # def inactive_message
        #   if !approved?
        #     :not_approved
        #   else
        #     super # Use whatever other message
        #   end
        # end
      

      前两个是否试图解决这个问题?问题和后两个是因为,在本教程之后,我想允许管理员批准新注册的用户,然后才能登录。

      https://github.com/plataformatec/devise/wiki/How-To:-Require-admin-to-activate-account-before-sign_in

      无论如何,对这些进行评论并再次尝试,我得到了这个错误:

      Started PATCH "/users/1/finish_signup" for 49.191.133.120 at 2015-12-14 08:18:49 +0000
      2015-12-14T08:18:49.536922+00:00 app[web.1]: 
      2015-12-14T08:18:49.536927+00:00 app[web.1]: NameError (undefined local variable or method `user' for #<UsersController:0x007f7215732e48>):
      2015-12-14T08:18:49.536929+00:00 app[web.1]:   app/controllers/users_controller.rb:72:in `user_params'
      2015-12-14T08:18:49.536930+00:00 app[web.1]:   app/controllers/users_controller.rb:43:in `finish_signup'
      2015-12-14T08:18:49.536941+00:00 app[web.1]: 
      

      我的用户控制器的第72行有:

      accessible << [:approved] if user.admin
      

      ATTEMPT 2:

      我取消注释了最后两个已批准的方法(在设计教程中建议用于管理员批准新注册用户的方法)。当我保存并重新启动服务器时,主页甚至无法加载。日志说:

      Completed 500 Internal Server Error in 21ms (ActiveRecord: 1.8ms)
      2015-12-14T08:45:09.932008+00:00 app[web.1]: 
      2015-12-14T08:45:09.932010+00:00 app[web.1]: ActionView::Template::Error (undefined method `approved?' for #<User:0x007f6afe8a2878>):
      2015-12-14T08:45:09.932012+00:00 app[web.1]:     12:                 <span class="deviselinks" style="padding-right:30px">
      2015-12-14T08:45:09.932011+00:00 app[web.1]:     10:             <div class="col-md-8>"> 
      2015-12-14T08:45:09.932012+00:00 app[web.1]:     11:               <ul style="text-align: right">
      2015-12-14T08:45:09.932016+00:00 app[web.1]:   app/models/user.rb:130:in `active_for_authentication?'
      2015-12-14T08:45:09.932014+00:00 app[web.1]:     14:                     Hi <%= link_to(current_user.first_name.titlecase, profile_path(current_user)) %></span>
      2015-12-14T08:45:09.943612+00:00 heroku[router]: at=info method=GET path="/" host=www.[].com request_id=a5646532-14a6-46ed-8dd7-6e4741688cf3 fwd="49.191.133.120" dyno=web.1 connect=1ms service=34ms status=500 bytes=1754
      

      我不知道完整的堆栈跟踪意味着什么 - 是终端命令吗?

1 个答案:

答案 0 :(得分:0)

以下是我的意见:

在以下代码中,如果未经过验证,我会抓住您离开email variable = nil的问题。但如果电子邮件未经过验证,您就不会为电子邮件提供任何虚拟数据。然后你尝试创建一个email = nil的用户,这会导致例外。

  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

对于twitter案例:

  • verified下存在extra.raw_info密钥,这意味着任何尝试通过Twitter进行身份验证都会失败,因为即使电子邮件已经过验证。您查看的位置错误,email nil email = nil,然后您尝试创建一个email的用户帐户,该帐户会被设计阻止,因为user object是必需的。在这里找到omniauth-twitter的链接,查看omniauth提供的哈希值。

  • 关于linkedin的第二个问题,显然电子邮件已经过验证approved?查找名为user model的方法,而approved方法中有一个名为@extends('layouts.main)的方法完全不同的方法。