Rails Authlogic身份验证方法

时间:2010-04-22 23:45:36

标签: ruby-on-rails authlogic

在Authlogic中,有没有办法可以为身份验证方法添加条件?我知道使用 find_by_login_method 我可以指定另一种方法,但是当我使用它时,我需要传递另一个参数,因为 find_by_login_method 方法只传递被认为的参数'login_field'。

我需要做的是检查一下真实模型的关联。这是我想要使用的方法

  # make sure that the user has access to the subdomain that they are 
  # attempting to login to, subdomains are company names
  def self.find_by_email_and_company(email, company)
    user = User.find_by_email(email)

    companies = []
    user.brands.each do |b|
      companies << b.company.id
    end

    user && companies.include?(company)
  end

但由于只有一个参数发送到find_by_email_and_company方法,因此失败。

该公司实际上是子域名,所以为了得到它,我只是将它放在表格中的隐藏字段中(只有这样我才能想到它才能进入模型)

有没有一种方法我可以以某种方式覆盖..?

使用下面的答案,我提出了以下有效的方法:

  

用户模型(User.rb)

  def self.find_by_email_within_company(email)
    # find the user
    user = self.find_by_email(email)

    # no need to continue if the email address is invalid
    return false if user.nil?

    # collect the subdomains the provided user has access to
    company_subdomains = user.brands.map(&:company).map(&:subdomain)

    # verify that the user has access to the current subdomain
    company_subdomains.include?(Thread.current[:current_subdomain]) && user
  end
  

应用程序控制器

  before_filter :set_subdomain

  private

    def set_subdomain
      # helper that retreives the current subdomain 
      get_company

      Thread.current[:current_subdomain] = @company.subdomain
    end
  

用户会话模型(UserSession.rb)

find_by_login_method :find_by_email_within_company

我已经阅读了一些关于使用Thread.current和冲突的命名空间的事情。这是一个很好的解决方案,对我有用但很想在赏金到期之前听到任何其他建议,否则,+ 100到Jens Fahnenbruck: )

3 个答案:

答案 0 :(得分:2)

Authlogic提供API来处理基于子域的身份验证。

class User < ActiveRecord::Base
  has_many :brands
  has_many :companies, :through => :brands
  acts_as_authentic
end

class Brand < ActiveRecord::Base
  belongs_to :user
  belongs_to :company
end

class Company < ActiveRecord::Base
  has_many :brands
  has_many :users, :through => :brands
  authenticates_many :user_sessions, :scope_cookies => true
end

会话控制器:

class UserSessionsController < ApplicationController      

  def create
    @company = Company.find(params[:user_session][:company])
    @user_session = @company.user_sessions.new(params[:user_session])
    if @user_session.save 
    else
    end    
  end
end

另一方面

以下是使用当前方法解决问题的方法(我会使用第一种方法):

将自定义数据设置为用于创建email对象的哈希的键UserSession。 AuthLogic会将此值传递给find_by_login方法。在find_by_login方法中,访问所需的值。

<强>假设: 子域ID在表单中名为company的字段中设置。

class UserSessionsController < ApplicationController      

  def create
    attrs = params[:user_session].dup #make a copy
    attrs[:email] = params[:user_session] # set custom data to :email key

    @user_session = UserSession.new(attrs)
    if @user_session.save 
    else
    end    
  end
end

型号代码

您可以简化和优化用于查找具有给定电子邮件和子域的用户的代码,如下所示:

class User < ActiveRecord::Base
  def find_by_email params={}
   # If invoked in the normal fashion then ..
   return User.first(:conditions => {:email => params}) unless params.is_a?(Hash)

   User.first(:joins => [:brands => :company}],
     :conditions => ["users.email = ? AND companies.id = ?", 
                      params[:email], params[:company]])
  end
end

修改1

用户通过身份验证后,系统应提供对授权数据的访问权限。

如果您维护同一个表中所有域的数据,则必须按子域和经过身份验证的用户对数据进行范围调整。 假设您的Post模型包含company_iduser_id列。当用户登录时,您希望显示子域的用户帖子。这是为子域定义用户数据的一种方法:

Posts.find_by_company_id_and_user_id(current_company, current_user)

Posts.for_company_and_user(current_company, current_user) # named scope

如果不对数据进行调整,则系统中可能存在安全漏洞。

答案 1 :(得分:1)

在您的lib文件夹中添加一个包含以下内容的文件:

class Class
  def thread_local_accessor name, options = {}
    m = Module.new
    m.module_eval do
      class_variable_set :"@@#{name}", Hash.new {|h,k| h[k] = options[:default] }
    end
    m.module_eval %{
      FINALIZER = lambda {|id| @@#{name}.delete id }

      def #{name}
        @@#{name}[Thread.current.object_id]
      end

      def #{name}=(val)
        ObjectSpace.define_finalizer Thread.current, FINALIZER  unless @@#{name}.has_key? Thread.current.object_id
        @@#{name}[Thread.current.object_id] = val
      end
    }

    class_eval do
      include m
      extend m
    end
  end
end

我发现了here

然后在控制器中添加代码,如下所示:

class ApplicationController < ActionController
  before_filter :set_subdomain
  private
  def set_subdomain
    User.subdomain = request.subdomains[0]
  end
end

现在您可以在您的用户模型中执行以下操作(假设您的公司模型有一个名为子域的方法:

class User < ActiveRecord::Base
  thread_local_accessor :subdomain, :default => nil

  def self.find_by_email_within_company(email)
    self.find_by_email(email)
    company_subdomains = user.brands.map(&:company).map(&:subdomain)
    company_subdomains.include?(self.subdomain) && user
  end
end

和FYI:

companies = user.brands.map(&:company).map(&:subdomain)

相同
companies = []
user.brands.each do |b|
  companies << b.company.subdomain
end

答案 2 :(得分:0)

使用rails 3,您可以使用此解决方法:

class UserSessionsController < ApplicationController
...
def create
    @company = <# YourMethodToGetIt #>
    session_hash = params[:user_session].dup
    session_hash[:username] = { :login => params[:user_session][:username], :company => @company }
    @user_session = UserSession.new(session_hash)

    if @user_session.save
        flash[:notice] = "Login successful!"
        redirect_back_or_default dashboard_url
    else
        @user_session.username = params[:user_session][:username]
        render :action => :new
    end

...
end

然后

class UserSession < Authlogic::Session::Base
    find_by_login_method :find_by_custom_login
end

class User < ActiveRecord::Base
...
    def self.find_by_custom_login(hash)
        if hash.is_a? Hash
            return find_by_username_and_company_id(hash[:login], hash[:company].id) ||
              find_by_email_and_company_id(hash[:login], hash[:company].id)
        else
            raise Exception.new "Error. find_by_custom_login MUST be called with {:login => 'username', :company => <Company.object>}"
        end
    end
...
end

这很简单,“正确”。我花了很多时间找出来,但它运作正常!