Created
July 16, 2012 11:44
Enable Active Directory support for Gitorious with correct Domain format
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
# 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 |
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
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Does this allow for gitorious to do the ldap search instead of using mod_ldap via apache?