Метод аутентификации Rails Authlogic

Есть ли в Authlogic способ добавить условия к методу аутентификации? Я знаю, что с помощью find_by_login_method я могу указать другой метод для использования, но когда я использую этот, мне нужно передать другой параметр, поскольку метод find_by_login_method передает только параметр, который считается поле «логин_поле».

Что мне нужно сделать, так это проверить что-то, что является ассоциацией подлинной модели. Вот метод, который я хочу использовать

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


person Rabbott    schedule 22.04.2010    source источник
comment
вам нужно поменять местами пользователя && company.include?(company) на company.include?(company) && user, поэтому User#find_by_email_and_company возвращает модель пользователя вместо true или false для Array#include? метод   -  person jigfox    schedule 26.04.2010
comment
Ваше право на проблемы, которые может иметь Thread.current. Поэтому я обновил свой ответ альтернативным способом   -  person jigfox    schedule 27.04.2010
comment
это забавно, пост, на который вы ссылаетесь, - это пост, который я прочитал, который сообщил мне о том, что Thread.current плохой, спасибо за помощь, Дженс!   -  person Rabbott    schedule 27.04.2010
comment
Кроме того, вы можете обновить свое решение, ваш частный метод не имеет имени, он просто определен, если другие наткнутся на этот вопрос и попытаются использовать ваш код.   -  person Rabbott    schedule 27.04.2010
comment
простите за это. теперь это тоже исправлено.   -  person jigfox    schedule 02.05.2010


Ответы (3)


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

Если вы не охватите данные, у вас будут потенциальные дыры в безопасности вашей системы.

person Harish Shetty    schedule 30.04.2010
comment
Насколько безопасно иметь компанию в качестве поля формы (полагаю, скрытого), потому что с помощью Firebug я могу отображать скрытые поля и изменять их содержимое. Таким образом, если пользователь изменяет значение в поле формы для входа в систему, но затем после входа в систему он получает информацию, относящуюся к поддомену, это может вызвать проблемы. Нет? - person Rabbott; 02.05.2010
comment
Даже если пользователь изменит поддомен, ему будет разрешено войти в систему, ТОЛЬКО если у него есть учетная запись в поддомене. Вызовы после аутентификации должны гарантировать, что пользователь получает доступ к авторизованному контенту (что в любом случае требуется). - person Harish Shetty; 02.05.2010
comment
после входа в систему содержимое ограничивается поддоменом, без серьезных проверок авторизации вы не всегда можете проверять содержимое при каждой загрузке страницы. Итак, что я мог бы изменить в форме для компании на компанию, к которой у меня тоже есть доступ, что позволило бы мне чтобы залогиниться. Затем, после входа в систему, моя учетная запись больше не проверяется при каждой загрузке страницы, чтобы убедиться, что у меня есть доступ к контенту. - person Rabbott; 02.05.2010
comment
Какая необходимость в поддомене в вашем случае? - person Harish Shetty; 02.05.2010
comment
Содержание области видимости. companya.appname.com позволяет пользователям, имеющим доступ к этой компании, войти в систему, и весь контент относится к компании A. - person Rabbott; 02.05.2010
comment
Пока вы ограничиваете страницы текущим поддоменом и текущим пользователем, ваша система в порядке. Я не вижу проблем с отправкой названия компании в скрытом поле для аутентификации. - person Harish Shetty; 03.05.2010
comment
Черт, первый подход не работает с Rails 3. =( Мне очень нравится первый подход. - person Joshua Partogi; 02.07.2010
comment
Я не тестировал это на Rails3, так как использую Rails 2.3.8. Попробуйте зарегистрировать ошибку на github для AuthLogic. - person Harish Shetty; 02.07.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
person jigfox    schedule 26.04.2010
comment
Спасибо и за трюк с .map($:), я смогу использовать его везде, не могли бы вы объяснить это? Или просто дайте ссылку сюда, я могу прочитать об этом на ..? - person Rabbott; 27.04.2010
comment
ruby-doc.org/core/classes/Array.html#M002189 .map(&:имя_метода) — это краткая форма для .map{|o| o.method_name } Я не уверен, что это Ruby или это часть основных расширений Rails. Так что я не знаю, работает ли это на простом Ruby. - person jigfox; 27.04.2010
comment
Большое спасибо! - Я приму ваше решение ближе к концу Щедрости, чтобы дать другим возможность ответить, так как я не могу изменить его, как только приму его. - person Rabbott; 28.04.2010
comment
& означает рассматривать это как блок. поскольку символ не является блоком, он пытается вызвать для него метод to_proc. to_proc для символа вернет процедуру, которая принимает объект, и вызовет любой метод, который находится на символе. pragdave.pragprog.com/pragdave/2005/11/symbolto_proc.html - person Matt Briggs; 03.05.2010

С 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

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

person Enrico Carlesso    schedule 25.02.2011