Skip to content

Commit

Permalink
Add test suite with 100% line/branch coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyevans committed Jul 7, 2022
1 parent c053bb9 commit cf65eb4
Show file tree
Hide file tree
Showing 6 changed files with 459 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,2 +1,3 @@
/rdoc
/simple_ldap_authenticator-*.gem
/coverage
4 changes: 3 additions & 1 deletion LICENSE
@@ -1,4 +1,6 @@
Copyright (c) 2004-2007 Jeremy Evans
Copyright (c) 2006-2022 Jeremy Evans

test/ldapserver.rb Copyright (c) 2006-2011 by Francis Cianfrocca and other contributors.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
41 changes: 28 additions & 13 deletions Rakefile
@@ -1,20 +1,35 @@
require 'rake'
require 'rake/clean'
begin
require 'hanna/rdoctask'
rescue LoadError
require 'rake/rdoctask'
end

Rake::RDocTask.new do |rdoc|
rdoc.rdoc_dir = "rdoc"
rdoc.options += ["--quiet", "--line-numbers", "--inline-source"]
rdoc.main = "README"
rdoc.title = "simple_ldap_authenticator: Easy authentication to an LDAP server(s)"
rdoc.rdoc_files.add ["README", "LICENSE", "lib/simple_ldap_authenticator.rb"]
end
CLEAN.include %w'*.gem coverage rdoc'

desc "Package simple_ldap_authenticator"
task :package do
sh %{gem build simple_ldap_authenticator.gemspec}
end

### Specs

desc "Run tests"
task :test do
ruby = ENV['RUBY'] ||= FileUtils::RUBY
sh "#{ruby} #{"-w" if RUBY_VERSION >= '3'} test/simple_ldap_authenticator_test.rb"
end

task :default => :test

desc "Run tests with coverage"
task :test_cov do
ruby = ENV['RUBY'] ||= FileUtils::RUBY
ENV['COVERAGE'] = '1'
sh "#{ruby} test/simple_ldap_authenticator_test.rb"
end

### RDoc

require "rdoc/task"

RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = "rdoc"
rdoc.options += ['--inline-source', '--line-numbers', '--title', 'simple_ldap_authenticator: Easy authentication to an LDAP server(s)', '--main', 'README', '-f', 'hanna']
rdoc.rdoc_files.add %w"README LICENSE lib/simple_ldap_authenticator.rb"
end
5 changes: 5 additions & 0 deletions simple_ldap_authenticator.gemspec
Expand Up @@ -9,4 +9,9 @@ spec = Gem::Specification.new do |s|
s.extra_rdoc_files = ["LICENSE"]
s.require_paths = ["lib"]
s.rdoc_options = %w'--inline-source --line-numbers README lib'

s.add_development_dependency "minitest-global_expectations"
s.add_development_dependency "eventmachine"
s.add_development_dependency "net-ldap"
s.add_development_dependency "ldap"
end
291 changes: 291 additions & 0 deletions test/ldapserver.rb
@@ -0,0 +1,291 @@
# $Id$
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
# Gmail account: garbagecat10.
#
# This is an LDAP server intended for unit testing of Net::LDAP.
# It implements as much of the protocol as we have the stomach
# to implement but serves static data. Use ldapsearch to test
# this server!
#
# To make this easier to write, we use the Ruby/EventMachine
# reactor library.
#

#------------------------------------------------

module LdapServer
LdapServerAsnSyntaxTemplate = {
:application => {
:constructed => {
0 => :array, # LDAP BindRequest
3 => :array # LDAP SearchRequest
},
:primitive => {
2 => :string, # ldapsearch sends this to unbind
},
},
:context_specific => {
:primitive => {
0 => :string, # simple auth (password)
7 => :string # present filter
},
:constructed => {
3 => :array # equality filter
},
},
}

def post_init
#$logger.info "Accepted LDAP connection"
@authenticated = false
end

def receive_data data
@data ||= ""; @data << data
while pdu = @data.read_ber!(LdapServerAsnSyntax)
begin
handle_ldap_pdu pdu
rescue
#$logger.error "closing connection due to error #{$!}"
close_connection
end
end
end

def handle_ldap_pdu pdu
tag_id = pdu[1].ber_identifier
case tag_id
when 0x60
handle_bind_request pdu
when 0x63
handle_search_request pdu
when 0x42
# bizarre thing, it's a null object (primitive application-2)
# sent by ldapsearch to request an unbind (or a kiss-off, not sure which)
close_connection_after_writing
else
#$logger.error "received unknown packet-type #{tag_id}"
close_connection_after_writing
end
end

def handle_bind_request pdu
# TODO, return a proper LDAP error instead of blowing up on version error
if pdu[1][0] != 3 || pdu[1][1] == "bad_version"
send_ldap_response 1, pdu[0].to_i, 2, "", "We only support version 3"
elsif pdu[1][1] != "user"
send_ldap_response 1, pdu[0].to_i, 48, "", "Who are you?"
elsif pdu[1][2].ber_identifier != 0x80
send_ldap_response 1, pdu[0].to_i, 7, "", "Keep it simple, man"
elsif pdu[1][2] != "password"
send_ldap_response 1, pdu[0].to_i, 49, "", "Make my day"
else
@authenticated = true
send_ldap_response 1, pdu[0].to_i, 0, pdu[1][1], "I'll take it"
end
end

# --
# Search Response ::=
# CHOICE {
# entry [APPLICATION 4] SEQUENCE {
# objectName LDAPDN,
# attributes SEQUENCE OF SEQUENCE {
# AttributeType,
# SET OF AttributeValue
# }
# },
# resultCode [APPLICATION 5] LDAPResult
# }
def handle_search_request pdu
unless @authenticated
# NOTE, early exit.
send_ldap_response 5, pdu[0].to_i, 50, "", "Who did you say you were?"
return
end

treebase = pdu[1][0]
if treebase != "dc=bayshorenetworks,dc=com"
send_ldap_response 5, pdu[0].to_i, 32, "", "unknown treebase"
return
end

msgid = pdu[0].to_i.to_ber

# pdu[1][7] is the list of requested attributes.
# If it's an empty array, that means that *all* attributes were requested.
requested_attrs = if pdu[1][7].length > 0
pdu[1][7].map(&:downcase)
else
:all
end

filters = pdu[1][6]
if filters.length == 0
# NOTE, early exit.
send_ldap_response 5, pdu[0].to_i, 53, "", "No filter specified"
end

# TODO, what if this returns nil?
filter = Net::LDAP::Filter.parse_ldap_filter(filters)

$ldif.each do |dn, entry|
if filter.match(entry)
attrs = []
entry.each do |k, v|
if requested_attrs == :all || requested_attrs.include?(k.downcase)
attrvals = v.map(&:to_ber).to_ber_set
attrs << [k.to_ber, attrvals].to_ber_sequence
end
end

appseq = [dn.to_ber, attrs.to_ber_sequence].to_ber_appsequence(4)
pkt = [msgid.to_ber, appseq].to_ber_sequence
send_data pkt
end
end

send_ldap_response 5, pdu[0].to_i, 0, "", "Was that what you wanted?"
end

def send_ldap_response pkt_tag, msgid, code, dn, text
send_data([msgid.to_ber, [code.to_ber, dn.to_ber, text.to_ber].to_ber_appsequence(pkt_tag)].to_ber)
end
end

#------------------------------------------------

# Rather bogus, a global method, which reads a HARDCODED filename
# parses out LDIF data. It will be used to serve LDAP queries out of this server.
#
def load_test_data
ary = (<<END).split("\n")
dn: dc=bayshorenetworks,dc=com
objectClass: dcObject
objectClass: organization
o: Bayshore Networks LLC
dc: bayshorenetworks
dn: cn=Manager,dc=bayshorenetworks,dc=com
objectClass: organizationalrole
cn: Manager
dn: ou=people,dc=bayshorenetworks,dc=com
objectClass: organizationalunit
ou: people
dn: ou=privileges,dc=bayshorenetworks,dc=com
objectClass: organizationalunit
ou: privileges
dn: ou=roles,dc=bayshorenetworks,dc=com
objectClass: organizationalunit
ou: roles
dn: ou=office,dc=bayshorenetworks,dc=com
objectClass: organizationalunit
ou: office
dn: mail=nogoodnik@steamheat.net,ou=people,dc=bayshorenetworks,dc=com
cn: Bob Fosse
mail: nogoodnik@steamheat.net
sn: Fosse
ou: people
objectClass: top
objectClass: inetorgperson
objectClass: authorizedperson
hasAccessRole: uniqueIdentifier=engineer,ou=roles
hasAccessRole: uniqueIdentifier=ldapadmin,ou=roles
hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_eagle_user,ou=roles
hasAccessRole: uniqueIdentifier=greenplug_user,ou=roles
hasAccessRole: uniqueIdentifier=brandplace_logging_user,ou=roles
hasAccessRole: uniqueIdentifier=brandplace_report_user,ou=roles
hasAccessRole: uniqueIdentifier=workorder_user,ou=roles
hasAccessRole: uniqueIdentifier=bayshore_eagle_user,ou=roles
hasAccessRole: uniqueIdentifier=bayshore_eagle_superuser,ou=roles
hasAccessRole: uniqueIdentifier=kledaras_user,ou=roles
dn: mail=elephant@steamheat.net,ou=people,dc=bayshorenetworks,dc=com
cn: Gwen Verdon
mail: elephant@steamheat.net
sn: Verdon
ou: people
objectClass: top
objectClass: inetorgperson
objectClass: authorizedperson
hasAccessRole: uniqueIdentifier=brandplace_report_user,ou=roles
hasAccessRole: uniqueIdentifier=engineer,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles
hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles
hasAccessRole: uniqueIdentifier=ldapadmin,ou=roles
dn: uniqueIdentifier=engineering,ou=privileges,dc=bayshorenetworks,dc=com
uniqueIdentifier: engineering
ou: privileges
objectClass: accessPrivilege
dn: uniqueIdentifier=engineer,ou=roles,dc=bayshorenetworks,dc=com
uniqueIdentifier: engineer
ou: roles
objectClass: accessRole
hasAccessPrivilege: uniqueIdentifier=engineering,ou=privileges
dn: uniqueIdentifier=ldapadmin,ou=roles,dc=bayshorenetworks,dc=com
uniqueIdentifier: ldapadmin
ou: roles
objectClass: accessRole
dn: uniqueIdentifier=ldapsuperadmin,ou=roles,dc=bayshorenetworks,dc=com
uniqueIdentifier: ldapsuperadmin
ou: roles
objectClass: accessRole
dn: mail=catperson@steamheat.net,ou=people,dc=bayshorenetworks,dc=com
cn: Sid Sorokin
mail: catperson@steamheat.net
sn: Sorokin
ou: people
objectClass: top
objectClass: inetorgperson
objectClass: authorizedperson
hasAccessRole: uniqueIdentifier=engineer,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles
hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles
hasAccessRole: uniqueIdentifier=ogilvy_eagle_user,ou=roles
hasAccessRole: uniqueIdentifier=greenplug_user,ou=roles
hasAccessRole: uniqueIdentifier=workorder_user,ou=roles
END
hash = {}
while (line = ary.shift) && line.chomp!
if line =~ /^dn:[\s]*/i
dn = $'
hash[dn] = {}
while (attr = ary.shift) && attr.chomp! && attr =~ /^([\w]+)[\s]*:[\s]*/
hash[dn][$1.downcase] ||= []
hash[dn][$1.downcase] << $'
end
end
end
hash
end

#------------------------------------------------

if __FILE__ == $0
require 'eventmachine'
require 'logger'
#$logger = Logger.new $stderr
$ldif = load_test_data

require 'net/ldap'
LdapServerAsnSyntax = Net::BER.compile_syntax(LdapServer::LdapServerAsnSyntaxTemplate)
EventMachine.run do
port = (ENV['PORT'] || 3890).to_i
EventMachine.start_server "127.0.0.1", port, LdapServer
#EventMachine.add_periodic_timer 60, proc { $logger.info "heartbeat" }
#$logger.info "started LDAP server on 127.0.0.1 port #{port}"
end
end

0 comments on commit cf65eb4

Please sign in to comment.