Skip to content

Instantly share code, notes, and snippets.

@schakko
Created July 16, 2012 11:44
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save schakko/3122260 to your computer and use it in GitHub Desktop.
Save schakko/3122260 to your computer and use it in GitHub Desktop.
Enable Active Directory support for Gitorious with correct Domain format
# Gitorious config/authentication.yml with Active Directory support
# This is based on https://gitorious.org/gitorious/mainline/merge_requests/181
production:
# Disable database authentication altogether
#disable_default: true
# additional methods, an array of hashes
methods:
- adapter: Gitorious::Authentication::LDAPAuthentication
# IP/hostname to LDAP server
host: domain-controller.domain.local
# Bind user must be user in your Active Directory which can only login
bind_username: your-bind-user@domain.local
bind_password: your-bind-password
# The base DN to search
base_dn: ou=Users,DC=domain,DC=local
# Use this suffix for every login, so that the user can login with username "test", which resolves to "test@domain.local"
username_domain_suffix: domain.local
# What LDAP attribute to use for user authentication. Default is CN
username_attribute: sAMAccountName
login_atribute: sAMAccountName
encryption: none
# encoding: utf-8
#--
# Copyright (C) 2011 Gitorious AS
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++
require "net/ldap"
module Gitorious
module Authentication
class LDAPAuthentication
attr_reader(:server, :port, :encryption, :attribute_mapping, :base_dn,
:connection_type, :distinguished_name_template, :connection, :login_attribute,
:bind_username, :bind_password, :username_attribute,
:username_domain_suffix)
def initialize(options)
validate_requirements(options)
setup_attributes(options)
end
def validate_requirements(options)
server_provided = options.key?("server") || options.key?("host")
raise ConfigurationError, "Server name required" unless server_provided
raise ConfigurationError, "Base DN required" unless options.key?("base_dn")
end
def setup_attributes(options)
@login_attribute = options["login_attribute"] || "CN"
@server = options["host"] || options["server"]
@port = (options["port"] || 389).to_i
@attribute_mapping = options["attribute_mapping"] || default_attribute_mapping
encryption_opt = options["encryption"] || "simple_tls"
@encryption = encryption_opt.to_sym if encryption_opt != "none"
@base_dn = options["base_dn"]
@connection_type = options["connection_type"] || Net::LDAP
@callback_class = options["callback_class"].constantize if options.key?("callback_class")
build_distinguished_name_template(options["distinguished_name_template"])
@bind_username = options["bind_username"]
@bind_password = options["bind_password"]
@username_attribute = options["username_attribute"] || "cn"
@username_domain_suffix = options["username_domain_suffix"]
end
def post_authenticate(options)
if @callback_class
return @callback_class.post_authenticate(options)
else
return true
end
end
# Ask the LDAP server if the credentials are correct
def valid_credentials?(username, password)
if @bind_username.nil?
return ldap_valid_credentials?(build_username(username), password)
else
use_login_username = get_login_username(username)
return false unless use_login_username
return ldap_valid_credentials?(use_login_username, password)
end
return false
end
# Returns the username which shall be used for authentication
def get_login_username(username)
if not @username_domain_suffix.nil?
if username.index("@").nil?
return username + "@" + @username_domain_suffix
end
# User provided his username like USER@DOMAIN
return username
end
return get_dn_of_user(username)
end
# The actual authentication callback
def authenticate(credentials)
return false unless valid_credentials?(credentials.username, credentials.password)
if existing_user = User.find_by_login(transform_username(credentials.username))
user = existing_user
else
user = auto_register(credentials.username)
end
return unless post_authenticate({
:connection => connection,
:username => credentials.username,
:user_filter => username_filter(credentials.username),
:base_dn => base_dn})
user
end
# Transform a username usable towards LDAP into something that passes Gitorious'
# username validations
def transform_username(username)
username.gsub(".", "-")
end
def auto_register(username)
result = connection.search(:base => base_dn, :filter => username_filter(username),
:attributes => attribute_mapping.keys, :return_result => true, :size => 1)
if result.size > 0
data = result.detect do |element|
attribute_mapping.keys.all? {|ldap_name| element[ldap_name] }
end
user = User.new
user.login = transform_username(username)
attribute_mapping.each do |ldap_name, our_name|
user.write_attribute(our_name, [*data[ldap_name]].first)
end
user.password = "left_blank"
user.password_confirmation = "left_blank"
user.terms_of_use = '1'
user.aasm_state = "terms_accepted"
user.activated_at = Time.now.utc
user.save!
# Reset the password to something random
user.reset_password!
return user
end
nil
end
# private
def username_filter(username)
Net::LDAP::Filter.eq(@username_attribute, username)
end
# The default mapping of LDAP -> User attributes
def default_attribute_mapping
{"displayname" => "fullname", "mail" => "email"}
end
def build_username(login)
distinguished_name_template.sub("{}", login)
end
# return the dn of the user that has an attribute named search_attribute with a value of seach_value, or nil if not found
def get_dn_of_user(search_value)
if @bind_username
connect
connection.auth(@bind_username, @bind_password)
if connection.bind
filter = Net::LDAP::Filter.eq("objectClass", "user") & Net::LDAP::Filter.eq(@username_attribute, search_value)
result = connection.search(:base => @base_dn, :filter => filter,
:attributes => [], :return_result => true, :size => 1)
if result.size > 0
return [*result.first["dn"]].first
end
end
end
nil
end
def build_distinguished_name_template(template)
@distinguished_name_template = template || "#{login_attribute}={},#{base_dn}"
end
def ldap_valid_credentials?(username, password)
return false if password.blank?
connect
connection.auth(username, password)
return connection.bind
end
def connect
@connection ||= @connection_type.new({:encryption => encryption, :host => server, :port => port})
end
end
end
end
@prelegalwonder
Copy link

Does this allow for gitorious to do the ldap search instead of using mod_ldap via apache?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment