Метод аутентификации Rails Authlogic - PullRequest
4 голосов
/ 23 апреля 2010

В 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 к Йенсу Фененбруку: )

Ответы [ 3 ]

2 голосов
/ 30 апреля 2010

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 получите доступ к необходимым значениям.

Допущение: Идентификатор субдомена задается в поле с именем 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_id и user_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 голос
/ 26 апреля 2010

В вашей папке 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

Я нашел это здесь

Затем добавьте код в контроллер следующим образом:

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

И к вашему сведению:

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

совпадает с

companies = []
user.brands.each do |b|
  companies << b.company.subdomain
end
0 голосов
/ 25 февраля 2011

С рельсами 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

Что довольно просто и "правильно". Мне нужно много времени, чтобы это выяснить, но все работает отлично!

...