Created
November 8, 2012 16:46
-
-
Save spullen/4039991 to your computer and use it in GitHub Desktop.
HybridAuthenticatable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# db/migrate/<timestamp>_devise_create_user.rb | |
class DeviseCreateUsers < ActiveRecord::Migration | |
def change | |
create_table(:users) do |t| | |
## Database authenticatable | |
t.string :username, :null => false, :default => "" | |
t.string :email, :null => false, :default => "" | |
t.string :encrypted_password, :null => false, :default => "" | |
## Recoverable | |
t.string :reset_password_token | |
t.datetime :reset_password_sent_at | |
## Rememberable | |
t.datetime :remember_created_at | |
## Trackable | |
t.integer :sign_in_count, :default => 0 | |
t.datetime :current_sign_in_at | |
t.datetime :last_sign_in_at | |
t.string :current_sign_in_ip | |
t.string :last_sign_in_ip | |
## Confirmable | |
# t.string :confirmation_token | |
# t.datetime :confirmed_at | |
# t.datetime :confirmation_sent_at | |
# t.string :unconfirmed_email # Only if using reconfirmable | |
## Lockable | |
# t.integer :failed_attempts, :default => 0 # Only if lock strategy is :failed_attempts | |
# t.string :unlock_token # Only if unlock strategy is :email or :both | |
# t.datetime :locked_at | |
## Token authenticatable | |
# t.string :authentication_token | |
t.string :override_ldap, :default => false | |
t.timestamps | |
end | |
add_index :users, :username, :unique => true | |
add_index :users, :reset_password_token, :unique => true | |
# add_index :users, :confirmation_token, :unique => true | |
# add_index :users, :unlock_token, :unique => true | |
# add_index :users, :authentication_token, :unique => true | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# config/initializers/hybrid_authenticatable.rb | |
require 'devise/strategies/authenticatable' | |
require 'bcrypt' | |
module Devise | |
module Strategies | |
# Strategy for signing in a user based on his login and password using LDAP. | |
# Redirects to sign_in page if it's not authenticated | |
class HybridAuthenticatable < Authenticatable | |
# Authenticate a user based on login and password params, returning to warden | |
# success and the authenticated user if everything is okay. Otherwise redirect | |
# to sign in page. | |
def authenticate! | |
resource = User.find_by_username(params[:user][:username]) | |
return fail(:invalid) if resource.nil? || !valid_password? | |
if resource.override_ldap == true | |
puts 'Authentication by Database' | |
resource = mapping.to.find_for_database_authentication(authentication_hash) | |
if validate(resource) | |
resource.after_database_authentication | |
success!(resource) | |
end | |
else | |
puts 'Authentication by LDAP' | |
resource = mapping.to.authenticate_with_ldap(params[scope]) | |
if validate(resource) | |
resource.after_database_authentication | |
success!(resource) | |
else | |
fail(:invalid) | |
end | |
end | |
end | |
end | |
end | |
module Models | |
module HybridAuthenticatable | |
extend ActiveSupport::Concern | |
included do | |
attr_reader :password, :current_password | |
attr_accessor :password_confirmation | |
end | |
def self.required_fields(klass) | |
[:encrypted_password] + klass.authentication_keys | |
end | |
# Generates password encryption based on the given value. | |
def password=(new_password) | |
@password = new_password | |
self.encrypted_password = password_digest(@password) if self.override_ldap && @password.present? | |
end | |
def login_with | |
@login_with ||= Devise.mappings[self.class.to_s.underscore.to_sym].to.authentication_keys.first | |
self[@login_with] | |
end | |
def change_password!(current_password) | |
raise "Need to set new password first" if @password.blank? | |
Devise::LdapAdapter.update_own_password(login_with, @password, current_password) | |
end | |
def reset_password!(new_password, new_password_confirmation) | |
if new_password == new_password_confirmation && ::Devise.ldap_update_password | |
Devise::LdapAdapter.update_password(login_with, new_password) | |
end | |
clear_reset_password_token if valid? | |
save | |
end | |
# Checks if a resource is valid upon authentication. | |
def valid_ldap_authentication?(password) | |
if Devise::LdapAdapter.valid_credentials?(login_with, password) | |
return true | |
else | |
return false | |
end | |
end | |
def ldap_groups | |
Devise::LdapAdapter.get_groups(login_with) | |
end | |
def ldap_dn | |
Devise::LdapAdapter.get_dn(login_with) | |
end | |
def ldap_get_param(login_with, param) | |
Devise::LdapAdapter.get_ldap_param(login_with,param) | |
end | |
# Verifies whether an password (ie from sign in) is the user password. | |
def valid_password?(password) | |
if override_ldap | |
return false if encrypted_password.blank? | |
bcrypt = ::BCrypt::Password.new(encrypted_password) | |
password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt) | |
Devise.secure_compare(password, encrypted_password) | |
else | |
return true | |
end | |
end | |
# Set password and password confirmation to nil | |
def clean_up_passwords | |
self.password = self.password_confirmation = nil | |
end | |
# Update record attributes when :current_password matches, otherwise returns | |
# error on :current_password. It also automatically rejects :password and | |
# :password_confirmation if they are blank. | |
def update_with_password(params, *options) | |
current_password = params.delete(:current_password) | |
if params[:password].blank? | |
params.delete(:password) | |
params.delete(:password_confirmation) if params[:password_confirmation].blank? | |
end | |
result = if valid_password?(current_password) | |
update_attributes(params, *options) | |
else | |
params.delete(:password) | |
self.assign_attributes(params, *options) | |
self.valid? | |
self.errors.add(:current_password, current_password.blank? ? :blank : :invalid) | |
false | |
end | |
clean_up_passwords | |
result | |
end | |
# Updates record attributes without asking for the current password. | |
# Never allows to change the current password. If you are using this | |
# method, you should probably override this method to protect other | |
# attributes you would not like to be updated without a password. | |
# | |
# Example: | |
# | |
# def update_without_password(params={}) | |
# params.delete(:email) | |
# super(params) | |
# end | |
# | |
def update_without_password(params, *options) | |
params.delete(:password) | |
params.delete(:password_confirmation) | |
result = update_attributes(params, *options) | |
clean_up_passwords | |
result | |
end | |
def after_database_authentication | |
end | |
# A reliable way to expose the salt regardless of the implementation. | |
def authenticatable_salt | |
encrypted_password[0,29] if encrypted_password | |
end | |
protected | |
# Digests the password using bcrypt. | |
def password_digest(password) | |
::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s | |
end | |
module ClassMethods | |
Devise::Models.config(self, :pepper, :stretches) | |
# We assume this method already gets the sanitized values from the | |
# DatabaseAuthenticatable strategy. If you are using this method on | |
# your own, be sure to sanitize the conditions hash to only include | |
# the proper fields. | |
def find_for_database_authentication(conditions) | |
find_for_authentication(conditions) | |
end | |
# Authenticate a user based on configured attribute keys. Returns the | |
# authenticated user if it's valid or nil. | |
def authenticate_with_ldap(attributes={}) | |
auth_key = self.authentication_keys.first | |
return nil unless attributes[auth_key].present? | |
auth_key_value = (self.case_insensitive_keys || []).include?(auth_key) ? attributes[auth_key].downcase : attributes[auth_key] | |
# resource = find_for_ldap_authentication(conditions) | |
resource = where(auth_key => auth_key_value).first | |
if (resource.blank? and ::Devise.ldap_create_user) | |
resource = new | |
resource[auth_key] = auth_key_value | |
resource.password = attributes[:password] | |
end | |
if resource.try(:valid_ldap_authentication?, attributes[:password]) | |
if resource.new_record? | |
resource.ldap_before_save if resource.respond_to?(:ldap_before_save) | |
resource.save | |
end | |
return resource | |
else | |
return nil | |
end | |
end | |
end | |
end | |
end | |
end | |
Warden::Strategies.add(:hybrid_authenticatable, Devise::Strategies::HybridAuthenticatable) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# app/models/user.rb | |
class User < ActiveRecord::Base | |
devise :hybrid_authenticatable, :database_authenticatable, :ldap_authenticatable, :rememberable, :trackable, :validatable | |
attr_accessible :username, :email, :password, :password_confirmation, :remember_me, :override_ldap | |
end |
module TestMod
def say_hello
puts 'HELLO!'
end
def say_goodbye
puts 'GOODBYE!'
end
end
class MyTest
end
my_test = MyTest.new
my_test.extend(TestMod::ClassMethods)
my_test.say_hello
my_test.say_goodbye
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It'd help if the code sample was complete...