3 Commits
79d31f61c4
...
00e4cadda6
| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
00e4cadda6 |
server2
|
2 weeks ago |
|
|
a4581ce8d9 |
server
|
2 weeks ago |
|
|
5b07e0ea9f |
client authentication from any domain
|
2 weeks ago |
8 changed files with 584 additions and 302 deletions
-
15README.md
-
27examples/client.rb
-
22examples/ldapdb.yaml
-
42examples/rbslapd4.rb
-
153examples/server1.rb
-
93examples/server2.rb
-
97lib/ldap/server/router.rb
-
41lib/ldap/server/util.rb
@ -0,0 +1,27 @@ |
|||
#!/usr/bin/env ruby |
|||
|
|||
require 'simple_ldap_authenticator' |
|||
|
|||
raise 'Error: 2 arguments required => email + password' if ARGV.length != 2 |
|||
|
|||
email = ARGV.shift |
|||
password = ARGV.shift |
|||
|
|||
# puts "email=#{email}" |
|||
user = email.split('@').first |
|||
fqdn = email.split('@')[1] |
|||
fqdn_items = fqdn.split('.') |
|||
tld = fqdn_items.last |
|||
domain = fqdn_items[0..-2].join('.') |
|||
|
|||
# puts "user=#{user}" |
|||
# puts "domain=#{domain}" |
|||
# puts "tld=#{tld}" |
|||
# |
|||
SimpleLdapAuthenticator.servers = ['127.0.0.1'] |
|||
SimpleLdapAuthenticator.port = 1389 |
|||
SimpleLdapAuthenticator.use_ssl = false |
|||
# SimpleLdapAuthenticator.login_format = '%s@mydomain.com' |
|||
SimpleLdapAuthenticator.login_format = "uid=%s,ou=Users,dc=#{domain},dc=#{tld}" |
|||
|
|||
p SimpleLdapAuthenticator.valid?(user, password) |
|||
@ -1,17 +1,21 @@ |
|||
--- |
|||
dc=example,dc=com: |
|||
dc=zourit,dc=re: |
|||
cn: |
|||
- Top object |
|||
cn=fred flintstone,dc=example,dc=com: |
|||
cn=pvincent,dc=zourit,dc=re: |
|||
cn: |
|||
- Fred Flintstone |
|||
- pvincent |
|||
sn: |
|||
- Flintstone |
|||
- P.Vincent |
|||
mail: |
|||
- fred@bedrock.org |
|||
- fred.flintstone@bedrock.org |
|||
cn=wilma flintstone,dc=example,dc=com: |
|||
- pvincent@zourit.re |
|||
dc=pvincent,dc=re: |
|||
cn: |
|||
- Wilma Flintstone |
|||
- Top object |
|||
cn=admin,dc=pvincent,dc=re: |
|||
cn: |
|||
- admin |
|||
sn: |
|||
- Vincentdmin |
|||
mail: |
|||
- wilma@bedrock.org |
|||
- admin@pvincent.re |
|||
@ -0,0 +1,153 @@ |
|||
#!/usr/bin/env ruby |
|||
|
|||
# This is a modified version of rbslapd1.rb which uses a Router instead of |
|||
# subclassing the LDAP::Server::Operation class. |
|||
|
|||
# This is a trivial LDAP server which just stores directory entries in RAM. |
|||
# It does no validation or authentication. This is intended just to |
|||
# demonstrate the API, it's not for real-world use!! |
|||
|
|||
$:.unshift('../lib') |
|||
$debug = false |
|||
|
|||
require 'ldap/server' |
|||
require 'ldap/server/router' |
|||
|
|||
$logger = Logger.new($stderr) |
|||
|
|||
class HashOperation < LDAP::Server::Operation |
|||
def initialize |
|||
super |
|||
@hash = YAML.load_from_file('ldapdbp.yaml') # an object reference to our directory data |
|||
end |
|||
|
|||
def search(basedn, scope, deref, filter) |
|||
basedn = basedn.downcase |
|||
|
|||
case scope |
|||
when LDAP::Server::BaseObject |
|||
# client asked for single object by DN |
|||
obj = @hash[basedn] |
|||
raise LDAP::ResultError::NoSuchObject unless obj |
|||
|
|||
send_SearchResultEntry(basedn, obj) if LDAP::Server::Filter.run(filter, obj) |
|||
|
|||
when LDAP::Server::WholeSubtree |
|||
@hash.each do |dn, av| |
|||
next unless dn.index(basedn, -basedn.length) # under basedn? |
|||
next unless LDAP::Server::Filter.run(filter, av) # attribute filter? |
|||
|
|||
send_SearchResultEntry(dn, av) |
|||
end |
|||
|
|||
else |
|||
raise LDAP::ResultError::UnwillingToPerform, 'OneLevel not implemented' |
|||
|
|||
end |
|||
end |
|||
end |
|||
|
|||
class LDAPController |
|||
def initialize |
|||
@directory = {} |
|||
|
|||
File.open('ldapdb.yaml') { |f| @directory = YAML.load(f.read) } |
|||
end |
|||
|
|||
def self.bind(request, version, dn, password, params) |
|||
$logger.debug 'Catchall bind request' |
|||
raise LDAP::ResultError::UnwillingToPerform, 'Invalid bind DN' |
|||
end |
|||
|
|||
def self.bindUser(request, version, dn, password, params) |
|||
user = params[:uid] |
|||
domain = params[:domain] |
|||
tld = params[:tld] |
|||
|
|||
# p "bindUser user=#{user} dn=#{dn}, password=#{password}, params=#{params}" |
|||
if user.length < 2 |
|||
$logger.warn "Denied access for user #{user}: Size < 2" |
|||
raise LDAP::ResultError::InvalidCredentials, 'Invalid credentials' |
|||
end |
|||
|
|||
$logger.info "Authenticated email=#{user}@#{domain}.#{tld} with password=<ANY>" |
|||
end |
|||
|
|||
def self.searchUsers(request, baseObject, scope, deref, filter, params) |
|||
$logger.info 'Search users' |
|||
domain = params[:domain] |
|||
tld = params[:tld] |
|||
|
|||
basedn = "#{domain}.#{tld}" |
|||
|
|||
operation = HashOperation.new |
|||
case scope |
|||
when LDAP::Server::BaseObject |
|||
# client asked for single object by DN |
|||
obj = directory |
|||
raise LDAP::ResultError::NoSuchObject unless obj |
|||
|
|||
operation.send_SearchResultEntry(basedn, obj) if LDAP::Server::Filter.run(filter, obj) |
|||
|
|||
when LDAP::Server::WholeSubtree |
|||
directory.each do |dn, av| |
|||
next unless dn.index(basedn, -basedn.length) # under basedn? |
|||
next unless LDAP::Server::Filter.run(filter, av) # attribute filter? |
|||
|
|||
operation.send_SearchResultEntry(dn, av) |
|||
end |
|||
|
|||
else |
|||
raise LDAP::ResultError::UnwillingToPerform, 'OneLevel not implemented' |
|||
|
|||
end |
|||
end |
|||
end |
|||
|
|||
router = LDAP::Server::Router.new($logger) do |
|||
# Different syntax but same thing |
|||
# bind nil => 'LDAPController#bind' |
|||
# route :bind, nil => 'LDAPController#bind' |
|||
|
|||
# Bind a route using variables. A hash with the variables will be passed |
|||
# to your function as last argument. |
|||
# bind 'uid=:uid,ou=Users,dc=mydomain,dc=com' => 'LDAPController#bindUser' |
|||
bind 'uid=:uid,dc=:domain,dc=:tld' => 'LDAPController#bindUser' |
|||
search 'dc=:domain,dc=:tld' => 'LDAPController#searchUsers' |
|||
|
|||
# search nil => 'LDAPController#search' |
|||
# search 'ou=Users,dc=mydomain,dc=com' => 'LDAPController#searchUsers' |
|||
end |
|||
|
|||
# This is the shared object which carries our actual directory entries. |
|||
# It's just a hash of {dn=>entry}, where each entry is {attr=>[val,val,...]} |
|||
|
|||
directory = {} |
|||
|
|||
# Let's put some backing store on it |
|||
|
|||
require 'yaml' |
|||
begin |
|||
File.open('ldapdb.yaml') { |f| directory = YAML.load(f.read) } |
|||
rescue Errno::ENOENT |
|||
end |
|||
|
|||
at_exit do |
|||
File.open('ldapdb.new', 'w') { |f| f.write(YAML.dump(directory)) } |
|||
File.rename('ldapdb.new', 'ldapdb.yaml') |
|||
end |
|||
|
|||
# Listen for incoming LDAP connections. For each one, create a Connection |
|||
# object, which will invoke a HashOperation object for each request. |
|||
|
|||
s = LDAP::Server.new( |
|||
port: 1389, |
|||
nodelay: true, |
|||
listen: 10, |
|||
# :ssl_key_file => "key.pem", |
|||
# :ssl_cert_file => "cert.pem", |
|||
# :ssl_on_connect => true, |
|||
router: router |
|||
) |
|||
s.run_tcpserver |
|||
s.join |
|||
@ -0,0 +1,93 @@ |
|||
#!/usr/bin/env ruby |
|||
|
|||
$:.unshift('lib') |
|||
$debug = true |
|||
|
|||
require 'ldap/server' |
|||
|
|||
$logger = Logger.new($stderr) |
|||
# We subclass the Operation class, overriding the methods to do what we need |
|||
|
|||
class HashOperation < LDAP::Server::Operation |
|||
def initialize(connection, messageID, hash) |
|||
super(connection, messageID) |
|||
@hash = hash # an object reference to our directory data |
|||
end |
|||
|
|||
# ici, c'est bizarre |
|||
# systèmatiquement appelé (SEARCH ou AUTH) |
|||
# du coup, je retourne super si pas de dn => SEARCH normal sans authentication |
|||
# sinon ça passe aussi !!! |
|||
def do_bind(protocolOp, controls) # :nodoc: |
|||
dn = protocolOp.value[1].value |
|||
dn = nil if dn == '' |
|||
return super if dn.nil? |
|||
|
|||
version = protocolOp.value[0].value |
|||
authentication = protocolOp.value[2] |
|||
password = authentication.value |
|||
$logger.info("AUTHENTICATION ..version=#{version}, dn=#{dn}") |
|||
super |
|||
end |
|||
|
|||
# def simple_bind(version, dn, password) |
|||
# super.simple_bind(version, dn, password) |
|||
# $logger.info("SIMPLE BIND...version=#{version}, dn=#{dn}") |
|||
# |
|||
# raise LDAP::ResultError::ProtocolError, 'version 3 only' if version != 3 |
|||
# raise LDAP::ResultError::InvalidCredentials, 'Invalid credentials' if password.nil? || password == '' |
|||
# |
|||
# $logger.info('authentication SUCCESS') |
|||
# end |
|||
# |
|||
def search(basedn, scope, deref, filter) |
|||
$logger.info("SEARCHING...basedn=#{basedn}, scope=#{scope}, deref=#{deref}, filter=#{filter}") |
|||
|
|||
basedn = basedn.downcase |
|||
result = nil |
|||
case scope |
|||
when LDAP::Server::BaseObject |
|||
# client asked for single object by DN |
|||
obj = @hash[basedn] |
|||
raise LDAP::ResultError::NoSuchObject unless obj |
|||
|
|||
result = send_SearchResultEntry(basedn, obj) if LDAP::Server::Filter.run(filter, obj) |
|||
when LDAP::Server::WholeSubtree |
|||
@hash.each do |dn, av| |
|||
next unless dn.index(basedn, -basedn.length) # under basedn? |
|||
next unless LDAP::Server::Filter.run(filter, av) # attribute filter? |
|||
|
|||
result = send_SearchResultEntry(dn, av) |
|||
end |
|||
|
|||
else |
|||
raise LDAP::ResultError::UnwillingToPerform, 'OneLevel not implemented' |
|||
|
|||
end |
|||
$logger.info "result=#{result}" |
|||
result |
|||
end |
|||
end |
|||
|
|||
# This is the shared object which carries our actual directory entries. |
|||
# It's just a hash of {dn=>entry}, where each entry is {attr=>[val,val,...]} |
|||
|
|||
directory = {} |
|||
require 'yaml' |
|||
File.open('examples/ldapdb.yaml') { |f| directory = YAML.load(f.read) } |
|||
$logger.info("DIRECTORY=#{directory}") |
|||
|
|||
# Listen for incoming LDAP connections. For each one, create a Connection |
|||
# object, which will invoke a HashOperation object for each request. |
|||
|
|||
s = LDAP::Server.new( |
|||
port: 1389, |
|||
nodelay: true, |
|||
listen: 10, |
|||
operation_class: HashOperation, |
|||
operation_args: [directory] |
|||
) |
|||
|
|||
$logger.info('server2 RUNNING...') |
|||
s.run_tcpserver |
|||
s.join |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue