Учитывая, что я на Rails 3.1, Ruby 1.9.2 со стандартной настройкой устройства для входа пользователя в систему по имени и паролю (например, аналогично https://github.com/RailsApps/rails3-devise-rspec-cucumber):
Мне нужно добавить слой, который проверяет подлинность опубликованного имени пользователя и пароля по отношению к внешней службе до создания нового пользователя (регистрация) или при входе пользователя.
(FWIW, в дальнейшем я планирую использовать стратегию / гем https://github.com/chicks/devise_aes_encryptable для шифрования секретного пароля и при входе в систему с локальным паролем расшифровывать пароль удаленной службы, выполнить аутентификацию, затем продолжить вход в систему, то есть иметь два пароля, один зашифрованный в одну сторону, другой обратимый ... не спрашивайте почему, во всяком случае)
В lib / util / authenticate.rb у меня есть класс аутентификации, который возвращает логическое значение для этой службы, например
Util::Authenticate.authenticate(username,password)
Но я не могу понять, как добавить фильтр для проверки подлинности на его основе в публикации формы перед продолжением проверки подлинности (для регистрации или входа).
Что я пробовал:
У меня есть модель User, и я подумал поставить
before_filter :authenticate_against_my_service, :only => [:create, :new]
в UserController, но это не сработало
Итак, я попытался открыть контроллер Devise Sessions, который не работал и не занимал его подклассы (например, в README),
class User::SessionsController < Devise::SessionsController
# something
end
# in config/routes.rb
devise_for :users, :controllers => { :sessions => "users/sessions" }
и не наследовать контроллер Devise Registrations (например, http://www.tonyamoyal.com/2010/07/28/rails-authentication-with-devise-and-cancan-customizing-devise-controllers/) и добавлять фильтр before_filter (такой же, как выше).
# in app/controllers/users/registrations_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :check_permissions, :only => [:new, :create, :cancel]
skip_before_filter :require_no_authentication
def check_permissions
authorize! :create, resource
end
end
# and in config/routes.rb
root :to => "home#index"
# replace "devise_for :users" with the below
devise_for :users, :controllers => { :registrations => "users/registrations" }
# other related code
devise_for :users do
get 'logout' => 'devise/sessions#destroy'
end
# resources :users, :only => :show MUST be below devise_for :users
resources :users, :only => :show
Я думаю, что мне нужно сделать это в контроллере, потому что как только параметры попадут в модель, у меня не будет незашифрованного пароля для отправки во внешнюю службу.
Я рассмотрел некоторые расширения для таких идей, как https://raw.github.com/nbudin/devise_cas_authenticatable/master/lib/devise_cas_authenticatable/strategy.rb например,
требуется 'разработка / стратегия / основа'
module Devise
module Strategies
class CasAuthenticatable < Base
# True if the mapping supports authenticate_with_cas_ticket.
def valid?
mapping.to.respond_to?(:authenticate_with_cas_ticket) && params[:ticket]
end
# Try to authenticate a user using the CAS ticket passed in params.
# If the ticket is valid and the model's authenticate_with_cas_ticket method
# returns a user, then return success. If the ticket is invalid, then either
# fail (if we're just returning from the CAS server, based on the referrer)
# or attempt to redirect to the CAS server's login URL.
def authenticate!
ticket = read_ticket(params)
if ticket
if resource = mapping.to.authenticate_with_cas_ticket(ticket)
# Store the ticket in the session for later usage
if ::Devise.cas_enable_single_sign_out
session['cas_last_valid_ticket'] = ticket.ticket
session['cas_last_valid_ticket_store'] = true
end
success!(resource)
elsif ticket.is_valid?
username = ticket.respond_to?(:user) ? ticket.user : ticket.response.user
redirect!(::Devise.cas_unregistered_url(request.url, mapping), :username => username)
#fail!("The user #{ticket.response.user} is not registered with this site. Please use a different account.")
else
fail!(:invalid)
end
else
fail!(:invalid)
end
end
protected
def read_ticket(params)
#snip
end
end
end
end
Warden::Strategies.add(:cas_authenticatable, Devise::Strategies::CasAuthenticatable)
и прочитайте о стратегиях аутентификации devise / warden, например, https://github.com/hassox/warden/wiki/Strategies но интересно, нужно ли мне на самом деле создать новую стратегию (и могу ли я выяснить, как это сделать)
РЕДАКТИРОВАТЬ, ВОЗМОЖНЫЕ РЕШЕНИЯ:
Мне нравится предложение Ално, и я попробую его, хотя он больше похож на обезьяну, чем на то, как предполагается использовать устройство / охранник
module Devise::Models::DatabaseAuthenticatable
alias_method :original_valid_password?, :valid_password?
def valid_password?
if Util::Authenticate.authenticate(username,password)
original_valid_password?
else
false
end
end
end
В качестве альтернативы я рассмотрел добавление стратегии аутентификации начальника, но сложно определить все движущиеся части, например, от Пользовательская стратегия аутентификации для устройства
initializers / authentication_strategy.rb: # это также может быть в initializers / devise.rb, нет?
Warden::Strategies.add(:custom_external_authentication) do
def valid?
# code here to check whether to try and authenticate using this strategy;
return true # always use the strategy as only user's authenticate
end
def authenticate!
# code here for doing authentication; if successful, call success!
# whatever you've authenticated, e.g. user; if fail, call fail!
if Util::Authenticate.authenticate(username, password)
success!(true) # I don't think I want to return a user, as I'll let database authenticatable handle the rest of the authentication
# I don't think I'm using success! correctly here: https://github.com/hassox/warden/wiki/Strategies
# success!(User.find(someid))
else
fail!("Username and password not valid for external service. Please ensure they are valid and try again.")
end
end
end
добавить следующее в инициализаторы / devise.rb
Devise.setup do |config|
config.warden do |manager|
manager.default_strategies.unshift :custom_external_authentication # will this check before or after database authentication? I want before, I think
end
end
ИЛИ с Как добавить стратегию в Devise
class ExternalServiceStrategy
def valid?
true # always use this
end
def authenticate!
# external boolean service call
end
end
Warden::Strategies.add(:database_authenticatable, ExternalServiceStrategy) # will this work before the db authentication?