Commit 166d65b2 authored by Felix Schäfer's avatar Felix Schäfer

[#258] Unvendor net-ldap

parent 1322fbba
......@@ -18,6 +18,10 @@ group :test do
platforms :mri_19, :mingw_19 do gem 'ruby-debug19', :require => 'ruby-debug' end
end
group :ldap do
gem "net-ldap", '~> 0.2.2'
end
group :openid do
gem "ruby-openid", '~> 2.1.4', :require => 'openid'
end
......
This diff is collapsed.
= Net::LDAP Changelog
== Net::LDAP 0.0.4: August 15, 2006
* Undeprecated Net::LDAP#modify. Thanks to Justin Forder for
providing the rationale for this.
* Added a much-expanded set of special characters to the parser
for RFC-2254 filters. Thanks to Andre Nathan.
* Changed Net::LDAP#search so you can pass it a filter in string form.
The conversion to a Net::LDAP::Filter now happens automatically.
* Implemented Net::LDAP#bind_as (preliminary and subject to change).
Thanks for Simon Claret for valuable suggestions and for helping test.
* Fixed bug in Net::LDAP#open that was preventing #open from being
called more than one on a given Net::LDAP object.
== Net::LDAP 0.0.3: July 26, 2006
* Added simple TLS encryption.
Thanks to Garett Shulman for suggestions and for helping test.
== Net::LDAP 0.0.2: July 12, 2006
* Fixed malformation in distro tarball and gem.
* Improved documentation.
* Supported "paged search control."
* Added a range of API improvements.
* Thanks to Andre Nathan, andre@digirati.com.br, for valuable
suggestions.
* Added support for LE and GE search filters.
* Added support for Search referrals.
* Fixed a regression with openldap 2.2.x and higher caused
by the introduction of RFC-2696 controls. Thanks to Andre
Nathan for reporting the problem.
* Added support for RFC-2254 filter syntax.
== Net::LDAP 0.0.1: May 1, 2006
* Initial release.
* Client functionality is near-complete, although the APIs
are not guaranteed and may change depending on feedback
from the community.
* We're internally working on a Ruby-based implementation
of a full-featured, production-quality LDAP server,
which will leverage the underlying LDAP and BER functionality
in Net::LDAP.
* Please tell us if you would be interested in seeing a public
release of the LDAP server.
* Grateful acknowledgement to Austin Ziegler, who reviewed
this code and provided the release framework, including
minitar.
#--
# Net::LDAP for Ruby.
# http://rubyforge.org/projects/net-ldap/
# Copyright (C) 2006 by Francis Cianfrocca
#
# Available under the same terms as Ruby. See LICENCE in the main
# distribution for full licensing information.
#
# $Id: ChangeLog,v 1.17.2.4 2005/09/09 12:36:42 austin Exp $
#++
# vim: sts=2 sw=2 ts=4 et ai tw=77
Net::LDAP is copyrighted free software by Francis Cianfrocca
<garbagecat10@gmail.com>. You can redistribute it and/or modify it under either
the terms of the GPL (see the file COPYING), or the conditions below:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that you do
at least ONE of the following:
a) place your modifications in the Public Domain or otherwise make them
Freely Available, such as by posting said modifications to Usenet or
an equivalent medium, or by allowing the author to include your
modifications in the software.
b) use the modified software only within your corporation or
organization.
c) rename any non-standard executables so the names do not conflict with
standard executables, which must also be provided.
d) make other distribution arrangements with the author.
3. You may distribute the software in object code or executable form,
provided that you do at least ONE of the following:
a) distribute the executables and library files of the software, together
with instructions (in the manual page or equivalent) on where to get
the original distribution.
b) accompany the distribution with the machine-readable source of the
software.
c) give non-standard executables non-standard names, with instructions on
where to get the original software distribution.
d) make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial). But some files in the distribution are
not written by the author, so that they are not under this terms.
They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
files under the ./missing directory. See each file for the copying
condition.
5. The scripts and library files supplied as input to or produced as output
from the software do not automatically fall under the copyright of the
software, but belong to whomever generated them, and may be sold
commercially, and may be aggregated with this software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
= Net::LDAP for Ruby
Net::LDAP is an LDAP support library written in pure Ruby. It supports all
LDAP client features, and a subset of server features as well.
Homepage:: http://rubyforge.org/projects/net-ldap/
Copyright:: (C) 2006 by Francis Cianfrocca
Original developer: Francis Cianfrocca
Contributions by Austin Ziegler gratefully acknowledged.
== LICENCE NOTES
Please read the file LICENCE for licensing restrictions on this library. In
the simplest terms, this library is available under the same terms as Ruby
itself.
== Requirements
Net::LDAP requires Ruby 1.8.2 or better.
== Documentation
See Net::LDAP for documentation and usage samples.
#--
# Net::LDAP for Ruby.
# http://rubyforge.org/projects/net-ldap/
# Copyright (C) 2006 by Francis Cianfrocca
#
# Available under the same terms as Ruby. See LICENCE in the main
# distribution for full licensing information.
#
# $Id: README 141 2006-07-12 10:37:37Z blackhedd $
#++
# vim: sts=2 sw=2 ts=4 et ai tw=77
#-- encoding: UTF-8
# $Id: ber.rb 142 2006-07-26 12:20:33Z blackhedd $
#
# NET::BER
# Mixes ASN.1/BER convenience methods into several standard classes.
# Also provides BER parsing functionality.
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
#
module Net
module BER
class BerError < Exception; end
# This module is for mixing into IO and IO-like objects.
module BERParser
# The order of these follows the class-codes in BER.
# Maybe this should have been a hash.
TagClasses = [:universal, :application, :context_specific, :private]
BuiltinSyntax = {
:universal => {
:primitive => {
1 => :boolean,
2 => :integer,
4 => :string,
10 => :integer,
},
:constructed => {
16 => :array,
17 => :array
}
}
}
#
# read_ber
# TODO: clean this up so it works properly with partial
# packets coming from streams that don't block when
# we ask for more data (like StringIOs). At it is,
# this can throw TypeErrors and other nasties.
#
def read_ber syntax=nil
return nil if (StringIO == self.class) and eof?
id = getc # don't trash this value, we'll use it later
tag = id & 31
tag < 31 or raise BerError.new( "unsupported tag encoding: #{id}" )
tagclass = TagClasses[ id >> 6 ]
encoding = (id & 0x20 != 0) ? :constructed : :primitive
n = getc
lengthlength,contentlength = if n <= 127
[1,n]
else
j = (0...(n & 127)).inject(0) {|mem,x| mem = (mem << 8) + getc}
[1 + (n & 127), j]
end
newobj = read contentlength
objtype = nil
[syntax, BuiltinSyntax].each {|syn|
if syn && (ot = syn[tagclass]) && (ot = ot[encoding]) && ot[tag]
objtype = ot[tag]
break
end
}
obj = case objtype
when :boolean
newobj != "\000"
when :string
(newobj || "").dup
when :integer
j = 0
newobj.each_byte {|b| j = (j << 8) + b}
j
when :array
seq = []
sio = StringIO.new( newobj || "" )
# Interpret the subobject, but note how the loop
# is built: nil ends the loop, but false (a valid
# BER value) does not!
while (e = sio.read_ber(syntax)) != nil
seq << e
end
seq
else
raise BerError.new( "unsupported object type: class=#{tagclass}, encoding=#{encoding}, tag=#{tag}" )
end
# Add the identifier bits into the object if it's a String or an Array.
# We can't add extra stuff to Fixnums and booleans, not that it makes much sense anyway.
obj and ([String,Array].include? obj.class) and obj.instance_eval "def ber_identifier; #{id}; end"
obj
end
end # module BERParser
end # module BER
end # module Net
class IO
include Net::BER::BERParser
end
require "stringio"
class StringIO
include Net::BER::BERParser
end
begin
require 'openssl'
class OpenSSL::SSL::SSLSocket
include Net::BER::BERParser
end
rescue LoadError
# Ignore LoadError.
# DON'T ignore NameError, which means the SSLSocket class
# is somehow unavailable on this implementation of Ruby's openssl.
# This may be WRONG, however, because we don't yet know how Ruby's
# openssl behaves on machines with no OpenSSL library. I suppose
# it's possible they do not fail to require 'openssl' but do not
# create the classes. So this code is provisional.
# Also, you might think that OpenSSL::SSL::SSLSocket inherits from
# IO so we'd pick it up above. But you'd be wrong.
end
class String
def read_ber syntax=nil
StringIO.new(self).read_ber(syntax)
end
end
#----------------------------------------------
class FalseClass
#
# to_ber
#
def to_ber
"\001\001\000"
end
end
class TrueClass
#
# to_ber
#
def to_ber
"\001\001\001"
end
end
class Fixnum
#
# to_ber
#
def to_ber
i = [self].pack('w')
[2, i.length].pack("CC") + i
end
#
# to_ber_enumerated
#
def to_ber_enumerated
i = [self].pack('w')
[10, i.length].pack("CC") + i
end
#
# to_ber_length_encoding
#
def to_ber_length_encoding
if self <= 127
[self].pack('C')
else
i = [self].pack('N').sub(/^[\0]+/,"")
[0x80 + i.length].pack('C') + i
end
end
end # class Fixnum
class Bignum
def to_ber
i = [self].pack('w')
i.length > 126 and raise Net::BER::BerError.new( "range error in bignum" )
[2, i.length].pack("CC") + i
end
end
class String
#
# to_ber
# A universal octet-string is tag number 4,
# but others are possible depending on the context, so we
# let the caller give us one.
# The preferred way to do this in user code is via to_ber_application_sring
# and to_ber_contextspecific.
#
def to_ber code = 4
[code].pack('C') + length.to_ber_length_encoding + self
end
#
# to_ber_application_string
#
def to_ber_application_string code
to_ber( 0x40 + code )
end
#
# to_ber_contextspecific
#
def to_ber_contextspecific code
to_ber( 0x80 + code )
end
end # class String
class Array
#
# to_ber_appsequence
# An application-specific sequence usually gets assigned
# a tag that is meaningful to the particular protocol being used.
# This is different from the universal sequence, which usually
# gets a tag value of 16.
# Now here's an interesting thing: We're adding the X.690
# "application constructed" code at the top of the tag byte (0x60),
# but some clients, notably ldapsearch, send "context-specific
# constructed" (0xA0). The latter would appear to violate RFC-1777,
# but what do I know? We may need to change this.
#
def to_ber id = 0; to_ber_seq_internal( 0x30 + id ); end
def to_ber_set id = 0; to_ber_seq_internal( 0x31 + id ); end
def to_ber_sequence id = 0; to_ber_seq_internal( 0x30 + id ); end
def to_ber_appsequence id = 0; to_ber_seq_internal( 0x60 + id ); end
def to_ber_contextspecific id = 0; to_ber_seq_internal( 0xA0 + id ); end
private
def to_ber_seq_internal code
s = self.to_s
[code].pack('C') + s.length.to_ber_length_encoding + s
end
end # class Array
This diff is collapsed.
#-- encoding: UTF-8
# $Id: dataset.rb 78 2006-04-26 02:57:34Z blackhedd $
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
#
module Net
class LDAP
class Dataset < Hash
attr_reader :comments
def Dataset::read_ldif io
ds = Dataset.new
line = io.gets && chomp
dn = nil
while line
io.gets and chomp
if $_ =~ /^[\s]+/
line << " " << $'
else
nextline = $_
if line =~ /^\#/
ds.comments << line
elsif line =~ /^dn:[\s]*/i
dn = $'
ds[dn] = Hash.new {|k,v| k[v] = []}
elsif line.length == 0
dn = nil
elsif line =~ /^([^:]+):([\:]?)[\s]*/
# $1 is the attribute name
# $2 is a colon iff the attr-value is base-64 encoded
# $' is the attr-value
# Avoid the Base64 class because not all Ruby versions have it.
attrvalue = ($2 == ":") ? $'.unpack('m').shift : $'
ds[dn][$1.downcase.intern] << attrvalue
end
line = nextline
end
end
ds
end
def initialize
@comments = []
end
def to_ldif
ary = []
ary += (@comments || [])
keys.sort.each {|dn|
ary << "dn: #{dn}"
self[dn].keys.map {|sym| sym.to_s}.sort.each {|attr|
self[dn][attr.intern].each {|val|
ary << "#{attr}: #{val}"
}
}
ary << ""
}
block_given? and ary.each {|line| yield line}
ary
end
end # Dataset
end # LDAP
end # Net
#-- encoding: UTF-8
# $Id: entry.rb 123 2006-05-18 03:52:38Z blackhedd $
#
# LDAP Entry (search-result) support classes
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
module Net
class LDAP
# Objects of this class represent individual entries in an LDAP
# directory. User code generally does not instantiate this class.
# Net::LDAP#search provides objects of this class to user code,
# either as block parameters or as return values.
#
# In LDAP-land, an "entry" is a collection of attributes that are
# uniquely and globally identified by a DN ("Distinguished Name").
# Attributes are identified by short, descriptive words or phrases.
# Although a directory is
# free to implement any attribute name, most of them follow rigorous
# standards so that the range of commonly-encountered attribute
# names is not large.
#
# An attribute name is case-insensitive. Most directories also
# restrict the range of characters allowed in attribute names.
# To simplify handling attribute names, Net::LDAP::Entry
# internally converts them to a standard format. Therefore, the
# methods which take attribute names can take Strings or Symbols,
# and work correctly regardless of case or capitalization.
#
# An attribute consists of zero or more data items called
# <i>values.</i> An entry is the combination of a unique DN, a set of attribute
# names, and a (possibly-empty) array of values for each attribute.
#
# Class Net::LDAP::Entry provides convenience methods for dealing
# with LDAP entries.
# In addition to the methods documented below, you may access individual
# attributes of an entry simply by giving the attribute name as
# the name of a method call. For example:
# ldap.search( ... ) do |entry|
# puts "Common name: #{entry.cn}"
# puts "Email addresses:"
# entry.mail.each {|ma| puts ma}
# end
# If you use this technique to access an attribute that is not present
# in a particular Entry object, a NoMethodError exception will be raised.
#
#--
# Ugly problem to fix someday: We key off the internal hash with
# a canonical form of the attribute name: convert to a string,
# downcase, then take the symbol. Unfortunately we do this in
# at least three places. Should do it in ONE place.
class Entry
# This constructor is not generally called by user code.
def initialize dn = nil # :nodoc:
@myhash = Hash.new {|k,v| k[v] = [] }
@myhash[:dn] = [dn]
end
def []= name, value # :nodoc:
sym = name.to_s.downcase.intern
@myhash[sym] = value
end
#--
# We have to deal with this one as we do with []=
# because this one and not the other one gets called
# in formulations like entry["CN"] << cn.
#
def [] name # :nodoc:
name = name.to_s.downcase.intern unless name.is_a?(Symbol)
@myhash[name]
end
# Returns the dn of the Entry as a String.
def dn
self[:dn][0]
end
# Returns an array of the attribute names present in the Entry.
def attribute_names
@myhash.keys
end
# Accesses each of the attributes present in the Entry.
# Calls a user-supplied block with each attribute in turn,
# passing two arguments to the block: a Symbol giving
# the name of the attribute, and a (possibly empty)
# Array of data values.
#
def each
if block_given?
attribute_names.each {|a|
attr_name,values = a,self[a]
yield attr_name, values
}
end
end
alias_method :each_attribute, :each
#--
# Convenience method to convert unknown method names
# to attribute references. Of course the method name
# comes to us as a symbol, so let's save a little time
# and not bother with the to_s.downcase two-step.
# Of course that means that a method name like mAIL
# won't work, but we shouldn't be encouraging that
# kind of bad behavior in the first place.
# Maybe we should thow something if the caller sends
# arguments or a block...
#
def method_missing *args, &block # :nodoc:
s = args[0].to_s.downcase.intern
if attribute_names.include?(s)
self[s]
elsif s.to_s[-1] == 61 and s.to_s.length > 1
value = args[1] or raise RuntimeError.new( "unable to set value" )
value = [value] unless value.is_a?(Array)
name = s.to_s[0..-2].intern
self[name] = value
else
raise NoMethodError.new( "undefined method '#{s}'" )
end
end
def write
end
end # class Entry
end # class LDAP
end # module Net
#-- encoding: UTF-8
# $Id: pdu.rb 126 2006-05-31 15:55:16Z blackhedd $
#
# LDAP PDU support classes
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
module Net
class LdapPduError < Exception; end
class LdapPdu
BindResult = 1
SearchReturnedData = 4
SearchResult = 5
ModifyResponse = 7
AddResponse = 9
DeleteResponse = 11
ModifyRDNResponse = 13
SearchResultReferral = 19
attr_reader :msg_id, :app_tag
attr_reader :search_dn, :search_attributes, :search_entry
attr_reader :search_referrals
#
# initialize
# An LDAP PDU always looks like a BerSequence with
# at least two elements: an integer (message-id number), and
# an application-specific sequence.
# Some LDAPv3 packets also include an optional
# third element, which is a sequence of "controls"
# (See RFC 2251, section 4.1.12).
# The application-specific tag in the sequence tells
# us what kind of packet it is, and each kind has its
# own format, defined in RFC-1777.
# Observe that many clients (such as ldapsearch)
# do not necessarily enforce the expected application
# tags on received protocol packets. This implementation
# does interpret the RFC strictly in this regard, and
# it remains to be seen whether there are servers out
# there that will not work well with our approach.
#
# Added a controls-processor to SearchResult.
# Didn't add it everywhere because it just _feels_
# like it will need to be refactored.
#
def initialize ber_object
begin
@msg_id = ber_object[0].to_i
@app_tag = ber_object[1].ber_identifier - 0x60
rescue
# any error becomes a data-format error
raise LdapPduError.new( "ldap-pdu format error" )
end
case @app_tag
when BindResult
parse_ldap_result ber_object[1]
when SearchReturnedData
parse_search_return ber_object[1]
when SearchResultReferral
parse_search_referral ber_object[1]
when SearchResult
parse_ldap_result ber_object[1]
parse_controls(ber_object[2]) if ber_object[2]
when ModifyResponse
parse_ldap_result ber_object[1]
when AddResponse
parse_ldap_result ber_object[1]
when DeleteResponse
parse_ldap_result ber_object[1]
when ModifyRDNResponse
parse_ldap_result ber_object[1]
else
raise LdapPduError.new( "unknown pdu-type: #{@app_tag}" )
end
end
#
# result_code
# This returns an LDAP result code taken from the PDU,
# but it will be nil if there wasn't a result code.
# That can easily happen depending on the type of packet.
#
def result_code code = :resultCode
@ldap_result and @ldap_result[code]
end
# Return RFC-2251 Controls if any.
# Messy. Does this functionality belong somewhere else?
def result_controls
@ldap_controls || []
end
#
# parse_ldap_result
#
def parse_ldap_result sequence
sequence.length >= 3 or raise LdapPduError
@ldap_result = {:resultCode => sequence[0], :matchedDN => sequence[1], :errorMessage => sequence[2]}
end
private :parse_ldap_result
#
# parse_search_return
# Definition from RFC 1777 (we're handling application-4 here)
#
# Search Response ::=
# CHOICE {
# entry [APPLICATION 4] SEQUENCE {
# objectName LDAPDN,
# attributes SEQUENCE OF SEQUENCE {
# AttributeType,
# SET OF AttributeValue
# }
# },
# resultCode [APPLICATION 5] LDAPResult
# }
#
# We concoct a search response that is a hash of the returned attribute values.
# NOW OBSERVE CAREFULLY: WE ARE DOWNCASING THE RETURNED ATTRIBUTE NAMES.
# This is to make them more predictable for user programs, but it
# may not be a good idea. Maybe this should be configurable.
# ALTERNATE IMPLEMENTATION: In addition to @search_dn and @search_attributes,
# we also return @search_entry, which is an LDAP::Entry object.
# If that works out well, then we'll remove the first two.
#
# Provisionally removed obsolete search_attributes and search_dn, 04May06.
#
def parse_search_return sequence
sequence.length >= 2 or raise LdapPduError
@search_entry = LDAP::Entry.new( sequence[0] )
#@search_dn = sequence[0]
#@search_attributes = {}
sequence[1].each {|seq|
@search_entry[seq[0]] = seq[1]
#@search_attributes[seq[0].downcase.intern] = seq[1]
}
end
#
# A search referral is a sequence of one or more LDAP URIs.
# Any number of search-referral replies can be returned by the server, interspersed
# with normal replies in any order.
# Until I can think of a better way to do this, we'll return the referrals as an array.
# It'll be up to higher-level handlers to expose something reasonable to the client.
def parse_search_referral uris
@search_referrals = uris
end
# Per RFC 2251, an LDAP "control" is a sequence of tuples, each consisting
# of an OID, a boolean criticality flag defaulting FALSE, and an OPTIONAL
# Octet String. If only two fields are given, the second one may be
# either criticality or data, since criticality has a default value.
# Someday we may want to come back here and add support for some of
# more-widely used controls. RFC-2696 is a good example.
#
def parse_controls sequence
@ldap_controls = sequence.map do |control|
o = OpenStruct.new
o.oid,o.criticality,o.value = control[0],control[1],control[2]
if o.criticality and o.criticality.is_a?(String)
o.value = o.criticality
o.criticality = false
end
o
end
end
private :parse_controls
end
end # module Net
#-- encoding: UTF-8
# $Id: psw.rb 73 2006-04-24 21:59:35Z blackhedd $
#
#
#----------------------------------------------------------------------------
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#---------------------------------------------------------------------------
#
#
module Net
class LDAP
class Password
class << self
# Generate a password-hash suitable for inclusion in an LDAP attribute.
# Pass a hash type (currently supported: :md5 and :sha) and a plaintext
# password. This function will return a hashed representation.
# STUB: This is here to fulfill the requirements of an RFC, which one?
# TODO, gotta do salted-sha and (maybe) salted-md5.
# Should we provide sha1 as a synonym for sha1? I vote no because then
# should you also provide ssha1 for symmetry?
def generate( type, str )
case type
when :md5
require 'md5'
"{MD5}#{ [MD5.new( str.to_s ).digest].pack("m").chomp }"
when :sha
require 'sha1'
"{SHA}#{ [SHA1.new( str.to_s ).digest].pack("m").chomp }"
# when ssha
else
raise Net::LDAP::LdapError.new( "unsupported password-hash type (#{type})" )
end
end
end
end
end # class LDAP
end # module Net
#-- encoding: UTF-8
# $Id: ldif.rb 78 2006-04-26 02:57:34Z blackhedd $
#
# Net::LDIF for Ruby
#
#
#
# Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
#
# Gmail: garbagecat10
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#
# THIS FILE IS A STUB.
module Net
class LDIF
end # class LDIF
end # module Net
#-- encoding: UTF-8
# $Id: testber.rb 57 2006-04-18 00:18:48Z blackhedd $
#
#
$:.unshift "lib"
require 'net/ldap'
require 'stringio'
class TestBer < Test::Unit::TestCase
def setup
end
# TODO: Add some much bigger numbers
# 5000000000 is a Bignum, which hits different code.
def test_ber_integers
assert_equal( "\002\001\005", 5.to_ber )
assert_equal( "\002\002\203t", 500.to_ber )
assert_equal( "\002\003\203\206P", 50000.to_ber )
assert_equal( "\002\005\222\320\227\344\000", 5000000000.to_ber )
end
def test_ber_parsing
assert_equal( 6, "\002\001\006".read_ber( Net::LDAP::AsnSyntax ))
assert_equal( "testing", "\004\007testing".read_ber( Net::LDAP::AsnSyntax ))
end
def test_ber_parser_on_ldap_bind_request
s = StringIO.new "0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus"
assert_equal( [1, [3, "Administrator", "ad_is_bogus"]], s.read_ber( Net::LDAP::AsnSyntax ))
end
end
# $Id: testdata.ldif 50 2006-04-17 17:57:33Z blackhedd $
#
# This is test-data for an LDAP server in LDIF format.
#
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
#-- encoding: UTF-8
# $Id: testem.rb 121 2006-05-15 18:36:24Z blackhedd $
#
#
require 'test/unit'
require 'tests/testber'
require 'tests/testldif'
require 'tests/testldap'
require 'tests/testpsw'
require 'tests/testfilter'
#-- encoding: UTF-8
# $Id: testfilter.rb 122 2006-05-15 20:03:56Z blackhedd $
#
#
require 'test/unit'
$:.unshift "lib"
require 'net/ldap'
class TestFilter < Test::Unit::TestCase
def setup
end
def teardown
end
def test_rfc_2254
p Net::LDAP::Filter.from_rfc2254( " ( uid=george* ) " )
p Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
p Net::LDAP::Filter.from_rfc2254( "uid<george*" )
p Net::LDAP::Filter.from_rfc2254( "uid <= george*" )
p Net::LDAP::Filter.from_rfc2254( "uid>george*" )
p Net::LDAP::Filter.from_rfc2254( "uid>=george*" )
p Net::LDAP::Filter.from_rfc2254( "uid!=george*" )
p Net::LDAP::Filter.from_rfc2254( "(& (uid!=george* ) (mail=*))" )
p Net::LDAP::Filter.from_rfc2254( "(| (uid!=george* ) (mail=*))" )
p Net::LDAP::Filter.from_rfc2254( "(! (mail=*))" )
end
end
#-- encoding: UTF-8
# $Id: testldap.rb 65 2006-04-23 01:17:49Z blackhedd $
#
#
$:.unshift "lib"
require 'test/unit'
require 'net/ldap'
require 'stringio'
class TestLdapClient < Test::Unit::TestCase
# TODO: these tests crash and burn if the associated
# LDAP testserver isn't up and running.
# We rely on being able to read a file with test data
# in LDIF format.
# TODO, WARNING: for the moment, this data is in a file
# whose name and location are HARDCODED into the
# instance method load_test_data.
def setup
@host = "127.0.0.1"
@port = 3890
@auth = {
:method => :simple,
:username => "cn=bigshot,dc=bayshorenetworks,dc=com",
:password => "opensesame"
}
@ldif = load_test_data
end
# Get some test data which will be used to validate
# the responses from the test LDAP server we will
# connect to.
# TODO, Bogus: we are HARDCODING the location of the file for now.
#
def load_test_data
ary = File.readlines( "tests/testdata.ldif" )
hash = {}
while line = ary.shift and line.chomp!
if line =~ /^dn:[\s]*/i
dn = $'
hash[dn] = {}
while attr = ary.shift and attr.chomp! and attr =~ /^([\w]+)[\s]*:[\s]*/
hash[dn][$1.downcase.intern] ||= []
hash[dn][$1.downcase.intern] << $'
end
end
end
hash
end
# Binding tests.
# Need tests for all kinds of network failures and incorrect auth.
# TODO: Implement a class-level timeout for operations like bind.
# Search has a timeout defined at the protocol level, other ops do not.
# TODO, use constants for the LDAP result codes, rather than hardcoding them.
def test_bind
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
assert_equal( true, ldap.bind )
assert_equal( 0, ldap.get_operation_result.code )
assert_equal( "Success", ldap.get_operation_result.message )
bad_username = @auth.merge( {:username => "cn=badguy,dc=imposters,dc=com"} )
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_username
assert_equal( false, ldap.bind )
assert_equal( 48, ldap.get_operation_result.code )
assert_equal( "Inappropriate Authentication", ldap.get_operation_result.message )
bad_password = @auth.merge( {:password => "cornhusk"} )
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => bad_password
assert_equal( false, ldap.bind )
assert_equal( 49, ldap.get_operation_result.code )
assert_equal( "Invalid Credentials", ldap.get_operation_result.message )
end
def test_search
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
search = {:base => "dc=smalldomain,dc=com"}
assert_equal( false, ldap.search( search ))
assert_equal( 32, ldap.get_operation_result.code )
search = {:base => "dc=bayshorenetworks,dc=com"}
assert_equal( true, ldap.search( search ))
assert_equal( 0, ldap.get_operation_result.code )
ldap.search( search ) {|res|
assert_equal( res, @ldif )
}
end
# This is a helper routine for test_search_attributes.
def internal_test_search_attributes attrs_to_search
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
assert( ldap.bind )
search = {
:base => "dc=bayshorenetworks,dc=com",
:attributes => attrs_to_search
}
ldif = @ldif
ldif.each {|dn,entry|
entry.delete_if {|attr,value|
! attrs_to_search.include?(attr)
}
}
assert_equal( true, ldap.search( search ))
ldap.search( search ) {|res|
res_keys = res.keys.sort
ldif_keys = ldif.keys.sort
assert( res_keys, ldif_keys )
res.keys.each {|rk|
assert( res[rk], ldif[rk] )
}
}
end
def test_search_attributes
internal_test_search_attributes [:mail]
internal_test_search_attributes [:cn]
internal_test_search_attributes [:ou]
internal_test_search_attributes [:hasaccessprivilege]
internal_test_search_attributes ["mail"]
internal_test_search_attributes ["cn"]
internal_test_search_attributes ["ou"]
internal_test_search_attributes ["hasaccessrole"]
internal_test_search_attributes [:mail, :cn, :ou, :hasaccessrole]
internal_test_search_attributes [:mail, "cn", :ou, "hasaccessrole"]
end
def test_search_filters
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
search = {
:base => "dc=bayshorenetworks,dc=com",
:filter => Net::LDAP::Filter.eq( "sn", "Fosse" )
}
ldap.search( search ) {|res|
p res
}
end
def test_open
ldap = Net::LDAP.new :host => @host, :port => @port, :auth => @auth
ldap.open {|ldap|
10.times {
rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" )
assert_equal( true, rc )
}
}
end
def test_ldap_open
Net::LDAP.open( :host => @host, :port => @port, :auth => @auth ) {|ldap|
10.times {
rc = ldap.search( :base => "dc=bayshorenetworks,dc=com" )
assert_equal( true, rc )
}
}
end
end
#-- encoding: UTF-8
# $Id: testldif.rb 61 2006-04-18 20:55:55Z blackhedd $
#
#
$:.unshift "lib"
require 'test/unit'
require 'net/ldap'
require 'net/ldif'
require 'sha1'
require 'base64'
class TestLdif < Test::Unit::TestCase
TestLdifFilename = "tests/testdata.ldif"
def test_empty_ldif
ds = Net::LDAP::Dataset::read_ldif( StringIO.new )
assert_equal( true, ds.empty? )
end
def test_ldif_with_comments
str = ["# Hello from LDIF-land", "# This is an unterminated comment"]
io = StringIO.new( str[0] + "\r\n" + str[1] )
ds = Net::LDAP::Dataset::read_ldif( io )
assert_equal( str, ds.comments )
end
def test_ldif_with_password
psw = "goldbricks"
hashed_psw = "{SHA}" + Base64::encode64( SHA1.new(psw).digest ).chomp
ldif_encoded = Base64::encode64( hashed_psw ).chomp
ds = Net::LDAP::Dataset::read_ldif( StringIO.new( "dn: Goldbrick\r\nuserPassword:: #{ldif_encoded}\r\n\r\n" ))
recovered_psw = ds["Goldbrick"][:userpassword].shift
assert_equal( hashed_psw, recovered_psw )
end
def test_ldif_with_continuation_lines
ds = Net::LDAP::Dataset::read_ldif( StringIO.new( "dn: abcdefg\r\n hijklmn\r\n\r\n" ))
assert_equal( true, ds.has_key?( "abcdefg hijklmn" ))
end
# TODO, INADEQUATE. We need some more tests
# to verify the content.
def test_ldif
File.open( TestLdifFilename, "r" ) {|f|
ds = Net::LDAP::Dataset::read_ldif( f )
assert_equal( 13, ds.length )
}
end
# TODO, need some tests.
# Must test folded lines and base64-encoded lines as well as normal ones.
def test_to_ldif
File.open( TestLdifFilename, "r" ) {|f|
ds = Net::LDAP::Dataset::read_ldif( f )
ds.to_ldif
assert_equal( true, false ) # REMOVE WHEN WE HAVE SOME TESTS HERE.
}
end
end
#-- encoding: UTF-8
# $Id: testpsw.rb 72 2006-04-24 21:58:14Z blackhedd $
#
#
$:.unshift "lib"
require 'net/ldap'
require 'stringio'
class TestPassword < Test::Unit::TestCase
def setup
end
def test_psw
assert_equal( "{MD5}xq8jwrcfibi0sZdZYNkSng==", Net::LDAP::Password.generate( :md5, "cashflow" ))
assert_equal( "{SHA}YE4eGkN4BvwNN1f5R7CZz0kFn14=", Net::LDAP::Password.generate( :sha, "cashflow" ))
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment