Skip to content

Instantly share code, notes, and snippets.

@Silthus
Last active August 29, 2015 14:08
Show Gist options
  • Save Silthus/88285640bb3e68c63b87 to your computer and use it in GitHub Desktop.
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
# 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