Skip to content

Commit

Permalink
Merge pull request #2 from roymarantz/use-net-ldap
Browse files Browse the repository at this point in the history
add support ldap statrttls
  • Loading branch information
lecid committed Aug 24, 2015
2 parents 2dfffa6 + e17d9db commit 410b101
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 77 deletions.
33 changes: 24 additions & 9 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@ Rack Middleware LDAP authentication
Copyright (c) 2014 Romain GEORGES. See COPYRIGHT for details.
Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen> for Rack Project

== Presentation
== Presentation

Rack::Auth::Ldap is a basic authentication module with LDAP support
Rack::Auth::Ldap is heavily based on Rack:Auth::Basic from the Rack main Project by Christian Neukirchen
Rack::Auth::Ldap is a basic authentication module with LDAP support
Rack::Auth::Ldap is heavily based on Rack:Auth::Basic from the Rack main Project by Christian Neukirchen

This is an additional module for Rack to authenticate users against an LDAP serveur
This is an additional module for Rack to authenticate users against an LDAP server

== Usage

== Usage

=== Initialise
=== Initialize

In you config.ru, simply add :

Expand All @@ -32,8 +31,7 @@ In you config.ru, simply add :
use Rack::Auth::Ldap
run Sinatra::Application

this configuration activate the Basic Authencation for the entire application

this configuration activate the Basic Authentication for the entire application

=== Configure

Expand Down Expand Up @@ -71,3 +69,20 @@ if you want to deactivate root authentication before user binding :

development:
<<: *ldap_defaults

to use ldaps add:
simple_tls: true

to use start tls add:
start_tls: true

if you need to set openssl options add a "tls_options" hash e.g.:
tls_options:
ca_path: /my/certificate/dir
or
tls_options:
ca_file: /my/certificate/file

to help debug tls you can add:
debug: true
which will send debugging messages to stdout
117 changes: 61 additions & 56 deletions lib/rack/auth/ldap.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require 'rack'
require 'ldap'
require 'net/ldap'
require 'rack/auth/abstract/handler'
require 'rack/auth/abstract/request'
require 'yaml'
Expand All @@ -10,20 +10,20 @@ module Rack
# the auth module from Rack Sources
module Auth

# class Config provide Yaml config mapping for Rack::Auth::Module

# class Config provide Yaml config mapping for Rack::Auth::Module
# the class map ldap configurations values
# @note this class is not provide to be used standalone
class Config
class Config

# initializer for Config class
# initializer for Config class
# @param [Hash<Symbol>] options initialisation options
# @option options [Symbol] :file The YAML filename (default to ./ldap.yml, the config.ru path)
# @return [Config] object himself
# @return [Config] object himself
def initialize(options = { :file => './ldap.yml'})
@values = defaults
target = (ENV['RACK_ENV'])? ENV['RACK_ENV'] : 'test'
config_values = ::YAML.load_file(::File.expand_path(options[:file], Dir.pwd))[target]
config_values = ::YAML.load_file(::File.expand_path(options[:file], Dir.pwd))[target]
debug = ::File.open("/tmp/test.txt",'a+')
debug.puts ENV['RACK_ENV']
debug.close
Expand All @@ -32,12 +32,12 @@ def initialize(options = { :file => './ldap.yml'})
end
@values.merge! config_values
@values.keys.each do |meth|
bloc = Proc.new {@values[meth] }
self.class.send :define_method, meth, &bloc
end
bloc = Proc.new {@values[meth] }
self.class.send :define_method, meth, &bloc
end
end
private

private
# private method with default configuration values for LDAP
# @return [Hash<Symbol>] the default values of LDAP configuration
def defaults
Expand All @@ -50,39 +50,43 @@ def defaults
:port => 389,
:scope => :subtree,
:username_ldap_attribute => 'uid',
:ldaps => false,
:starttls => false,
:tls_options => nil,
:debug => false
}
end


end

# class Ldap, the main authentication component for Rack
# class Ldap, the main authentication component for Rack
# inherited from the default Rack::Auth::AbstractHandler
# @note please do not instantiate, this classe is reserved to Rack
# @example Usage
# @note please do not instantiate, this classe is reserved to Rack
# @example Usage
# # in a config.ru
# gem 'rack-auth-ldap'
# require 'rack/auth/ldap'
# use Rack::Auth::Ldap
class Ldap < AbstractHandler

# the config read accessor
# @attr [Rack::Auth::Config] the read accessor to the LDAP Config object
attr_reader :config

# initializer for the Ldap Class
# @note please don not instantiate without rack config.ru
# @note please don not instantiate without rack config.ru
# @see Rack::Auth::Ldap
# @return [Ldap] self object
# @param [Block,Proc,Lambda] app the rack application
# @param [hash<Symbol>] config_options the configurable options
# @option config_options [Symbol] :file the path to the YAML configuration file
# @option config_options [Symbol] :file the path to the YAML configuration file
def initialize(app, config_options = {})
super(app)
@config = Config.new(config_options)
end

# call wrapper to provide authentication if not
# call wrapper to provide authentication if not
# @param [Hash] env the rack environnment variable
# @return [Array] the tri-dimensional Array [status,headers,[body]]
def call(env)
Expand All @@ -100,53 +104,57 @@ def call(env)
private

# forge a challange header for HTTP basic auth with the realm attribut
# @return [String] the header
# @return [String] the header
def challenge
'Basic realm="%s"' % realm
end
# do the LDAP connection => search => bind with the credentials get into request headers

# do the LDAP connection => search => bind with the credentials get into request headers
# @param [Rack::Auth::Ldap::Request] auth a LDAP authenticator object
# @return [TrueClass,FalseClass] Boolean true/false
def valid?(auth)
dn = ''
conn = LDAP::Conn.new(@config.hostname, @config.port)
conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 )
conn.simple_bind(@config.rootdn,@config.passdn) if @config.auth
filter = "(#{@config.username_ldap_attribute}=#{auth.username})"
conn.search(@config.basedn, ldap_scope(@config.scope), filter) do |entry|
dn = entry.dn
def valid?(auth)
# how to connect to the ldap server: ldap, ldaps, ldap + starttls
if @config.ldaps
enc = { :method => :simple_tls }
elsif @config.starttls
enc = { :method => :start_tls }
enc[:tls_options] = @config.tls_options if @config.tls_options
else
enc = nil # just straight ldap
end
return false if dn.empty?
conn.unbind
conn = LDAP::Conn.new(@config.hostname, @config.port)
conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 )
begin
return conn.simple_bind(dn, auth.password)
rescue LDAP::ResultError
return false
conn = Net::LDAP.new( :host => @config.hostname, :port => @config.port,
:base => @config.basedn,
:encryption => enc )

$stdout.puts "Net::LDAP.new => #{conn.inspect}" if @config.debug

if @config.auth
$stdout.puts "doing auth for #{@config.rootdn.inspect}" if @config.debug
conn.auth @config.rootdn, @config.passdn
# conn.get_operation_result.message has the reson for a failure
return false unless conn.bind
end
end

private
filter = Net::LDAP::Filter.eq(@config.username_ldap_attribute,
auth.username)

# helper to map ruby-ldap scope with internal scope symbols
# @param [Symbol] _scope a scope in [:subtree,:one]
# @return [Fixnum,Integer] the constant value form ruby-ldap
def ldap_scope(_scope)
res = {
:subtree => ::LDAP::LDAP_SCOPE_SUBTREE,
:one => ::LDAP::LDAP_SCOPE_ONELEVEL
}
return res[_scope]
$stdout.puts "Net::LDAP::Filter.eq => #{filter.inspect}" if @config.debug

# find the user and rebind as them to test the password
#return conn.bind_as(:filter => filter, :password => auth.password)
$stdout.puts "doing bind_as password.size: #{auth.password.size}..." if @config.debug
ret = conn.bind_as(:filter => filter, :password => auth.password)
$stdout.puts "bind_as => #{ret.inspect}" if @config.debug
ret
end

private


# Request class the LDAP credentials authenticator
# @note please do not instantiate manually, used by Rack::Auth:Ldap
# @note please do not instantiate manually, used by Rack::Auth:Ldap
class Request < Auth::AbstractRequest

# return true if the auth scheme provide is really a basic scheme
# @return [FalseClass,TrueClass] the result
def basic?
Expand All @@ -158,7 +166,7 @@ def basic?
def credentials
@credentials ||= params.unpack("m*").first.split(/:/, 2)
end

# read accessor on the first credentials, username
# @return [String] the username
def username
Expand All @@ -176,6 +184,3 @@ def password
end
end
end



12 changes: 6 additions & 6 deletions lib/rack/auth/ldap/version.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
# Author : Romain GEORGES
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
# Author : Romain GEORGES

# the Rack module from Rack Sources
# the Rack module from Rack Sources
module Rack

# the Rack::Auth module from Rack Sources
# the Rack::Auth module from Rack Sources
module Auth
# the current version for Rack::Auth::Ldap => gem rack-auth-ldap
# used by gemspec
LDAP_VERSION = "1.0"
LDAP_VERSION = "1.1"
end
end
9 changes: 3 additions & 6 deletions rack-auth-ldap.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require 'rack/auth/ldap/version'
Gem::Specification.new do |s|
s.name = "rack-auth-ldap"
s.summary = %Q{Rack middleware providing LDAP authentication}
s.email = "[email protected]"
s.email = "[email protected]"
s.homepage = "http://www.github.com/lecid/rack-auth-ldap"
s.authors = ["Romain GEORGES"]
s.version = Rack::Auth::LDAP_VERSION
Expand All @@ -20,12 +20,9 @@ Gem::Specification.new do |s|
s.add_development_dependency('roodi')
s.add_development_dependency('code_statistics')
s.add_development_dependency('yard-rspec')
s.add_dependency('ruby-ldap')
s.add_dependency('net-ldap')
s.add_dependency('rack')
s.required_ruby_version = '>= 1.9.0'
s.license = "BSD"
s.license = "BSD"
s.files = `git ls-files`.split($/)
end



0 comments on commit 410b101

Please sign in to comment.