Last active
August 29, 2015 14:08
-
-
Save Silthus/88285640bb3e68c63b87 to your computer and use it in GitHub Desktop.
For installation and usage please see this Redmine HowTo: http://www.redmine.org/projects/redmine/wiki/Alternativecustom_authentication_HowTo
This file contains 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
# Redmine Woltlab Community Framework 2 Authentication Source | |
# | |
# Copyright (C) 2014 Michael 'Silthus' Doering | |
# | |
# This program is free software; you can redistribute it and/or | |
# modify it under the terms of the GNU General Public License | |
# as published by the Free Software Foundation; either version 2 | |
# of the License, or (at your option) any later version. | |
# | |
# This program is distributed in the hope that it will be useful, | |
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
# GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License | |
# along with this program; if not, write to the Free Software | |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
# bcrypt gem is needed for woltlab double salted hash | |
require 'bcrypt' | |
# to avoid timing attacks we are going to use a secure compare and its fast because the gem is in c | |
# http://stackoverflow.com/questions/25275063/faster-constant-time-string-comparison-in-ruby | |
require 'fast_secure_compare/rails' | |
# Let's have a new class for our ActiveRecord-based connection | |
# to our alternative authentication database. Remember that we're | |
# not assuming that the alternative authentication database is on | |
# the same host (and/or port) as Redmine's database. So its current | |
# database connection may be of no use to us. ActiveRecord uses class | |
# variables to store state (yay) like current connections and such; thus, | |
# dedicated class... | |
class WCFCustomDB_ActiveRecord < ActiveRecord::Base | |
PAUSE_RETRIES = 5 | |
MAX_RETRIES = 50 | |
BCrypt::Engine.cost = 8 | |
end | |
# Subclass AuthSource | |
class AuthSourceWCF < AuthSource | |
# authentication() implementation | |
# - Redmine will call this method, passing the login and password entered | |
# on the Sign In form. | |
# | |
# +login+ : what user entered for their login | |
# +password+ : what user entered for their password | |
def authenticate(login, password) | |
retVal = nil | |
unless(login.blank? or password.blank?) | |
# Get a connection to the authenticating database. | |
# - Don't use ActiveRecord::Base when using establish_connection() to get at | |
# your alternative database (leave Redmine's current connection alone). | |
# Use class you prepped above. | |
# - Recall that the values stored in the fields of your auth_sources | |
# record are available as self.fieldName | |
# First, get the DB Adapter name and database to use for connecting: | |
adapter, dbName = self.base_dn.split(':') | |
# Second, try to get a connection, safely dealing with the MySQL<->ActiveRecord | |
# failed connection bug that can still arise to this day (regardless of | |
# reconnect, oddly). | |
retryCount = 0 | |
connPool = WCFCustomDB_ActiveRecord.establish_connection( | |
:adapter => adapter, | |
:host => self.host, | |
:port => self.port, | |
:username => self.account, | |
:password => self.account_password, | |
:database => dbName, | |
:reconnect => true | |
) | |
db = connPool.checkout() | |
# Third, query the alternative authentication database for needed info. | |
# The query will only check for a valid user, authentication will take place later | |
resultRow = db.select_one( | |
"SELECT #{self.attr_login}, #{self.attr_firstname}, #{self.attr_lastname}, #{self.attr_mail}, password " + | |
"FROM `wcf1_user` " + | |
"WHERE #{self.attr_login} = '#{db.quote_string(login)}'" | |
) | |
unless(resultRow.nil? or resultRow.empty?) | |
user = resultRow[self.attr_login] | |
unless(user.nil? or user.empty?) | |
# And last, after we found a valid user we need to authenticate that user | |
# Woltlab uses the stored password hash as salt so we use that | |
# and override the random salt generation. | |
salt = resultRow['password'] | |
saltedPassword = BCrypt::Password.new(BCrypt::Engine.hash_secret(BCrypt::Password.new(BCrypt::Engine.hash_secret(password, salt)), salt)) | |
# it is important to put the user input first, otherwise the length of the secret may be leaked | |
if (FastSecureCompare.compare(resultRow['password'], saltedPassword)) then | |
retVal = | |
{ | |
:firstname => resultRow[self.attr_firstname], | |
:lastname => resultRow[self.attr_lastname], | |
:mail => resultRow[self.attr_mail], | |
:auth_source_id => self.id | |
} if(onthefly_register?) | |
end | |
end | |
end | |
end | |
# Check connection back into pool. | |
connPool.checkin(db) | |
return retVal | |
end | |
def auth_method_name | |
"WCF2" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment