Replaced ruby-net-ldap with net-ldap 0.2.2 gem.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@8751 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
parent
d02f6a8e32
commit
73f9b825f0
|
@ -15,7 +15,6 @@
|
|||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
require 'net/ldap'
|
||||
require 'iconv'
|
||||
|
||||
class AuthSourceLdap < AuthSource
|
||||
|
|
|
@ -55,6 +55,7 @@ Rails::Initializer.run do |config|
|
|||
config.action_mailer.perform_deliveries = false
|
||||
|
||||
config.gem 'coderay', :version => '~>1.0.0'
|
||||
config.gem 'net-ldap', :version => '~>0.2.2'
|
||||
|
||||
# Load any local configuration that is kept out of source control
|
||||
# (e.g. gems, patches).
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
require 'rubygems'
|
||||
#require 'redgreen/autotest'
|
||||
require 'autotest/timestamp'
|
||||
|
||||
Autotest.add_hook :initialize do |autotest|
|
||||
%w{.git .hg .DS_Store ._* tmp log doc}.each do |exception|
|
||||
autotest.add_exception(exception)
|
||||
end
|
||||
end
|
||||
|
||||
# vim: syntax=ruby
|
|
@ -0,0 +1,2 @@
|
|||
--colour
|
||||
--format documentation
|
|
@ -0,0 +1,200 @@
|
|||
--- !ruby/object:Gem::Specification
|
||||
name: net-ldap
|
||||
version: !ruby/object:Gem::Version
|
||||
hash: 19
|
||||
prerelease:
|
||||
segments:
|
||||
- 0
|
||||
- 2
|
||||
- 2
|
||||
version: 0.2.2
|
||||
platform: ruby
|
||||
authors:
|
||||
- Francis Cianfrocca
|
||||
- Emiel van de Laar
|
||||
- Rory O'Connell
|
||||
- Kaspar Schiess
|
||||
- Austin Ziegler
|
||||
autorequire:
|
||||
bindir: bin
|
||||
cert_chain: []
|
||||
|
||||
date: 2011-03-26 00:00:00 Z
|
||||
dependencies:
|
||||
- !ruby/object:Gem::Dependency
|
||||
name: rubyforge
|
||||
prerelease: false
|
||||
requirement: &id001 !ruby/object:Gem::Requirement
|
||||
none: false
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
hash: 7
|
||||
segments:
|
||||
- 2
|
||||
- 0
|
||||
- 4
|
||||
version: 2.0.4
|
||||
type: :development
|
||||
version_requirements: *id001
|
||||
- !ruby/object:Gem::Dependency
|
||||
name: hoe-git
|
||||
prerelease: false
|
||||
requirement: &id002 !ruby/object:Gem::Requirement
|
||||
none: false
|
||||
requirements:
|
||||
- - ~>
|
||||
- !ruby/object:Gem::Version
|
||||
hash: 1
|
||||
segments:
|
||||
- 1
|
||||
version: "1"
|
||||
type: :development
|
||||
version_requirements: *id002
|
||||
- !ruby/object:Gem::Dependency
|
||||
name: hoe-gemspec
|
||||
prerelease: false
|
||||
requirement: &id003 !ruby/object:Gem::Requirement
|
||||
none: false
|
||||
requirements:
|
||||
- - ~>
|
||||
- !ruby/object:Gem::Version
|
||||
hash: 1
|
||||
segments:
|
||||
- 1
|
||||
version: "1"
|
||||
type: :development
|
||||
version_requirements: *id003
|
||||
- !ruby/object:Gem::Dependency
|
||||
name: metaid
|
||||
prerelease: false
|
||||
requirement: &id004 !ruby/object:Gem::Requirement
|
||||
none: false
|
||||
requirements:
|
||||
- - ~>
|
||||
- !ruby/object:Gem::Version
|
||||
hash: 1
|
||||
segments:
|
||||
- 1
|
||||
version: "1"
|
||||
type: :development
|
||||
version_requirements: *id004
|
||||
- !ruby/object:Gem::Dependency
|
||||
name: flexmock
|
||||
prerelease: false
|
||||
requirement: &id005 !ruby/object:Gem::Requirement
|
||||
none: false
|
||||
requirements:
|
||||
- - ~>
|
||||
- !ruby/object:Gem::Version
|
||||
hash: 59
|
||||
segments:
|
||||
- 0
|
||||
- 9
|
||||
- 0
|
||||
version: 0.9.0
|
||||
type: :development
|
||||
version_requirements: *id005
|
||||
- !ruby/object:Gem::Dependency
|
||||
name: rspec
|
||||
prerelease: false
|
||||
requirement: &id006 !ruby/object:Gem::Requirement
|
||||
none: false
|
||||
requirements:
|
||||
- - ~>
|
||||
- !ruby/object:Gem::Version
|
||||
hash: 3
|
||||
segments:
|
||||
- 2
|
||||
- 0
|
||||
version: "2.0"
|
||||
type: :development
|
||||
version_requirements: *id006
|
||||
- !ruby/object:Gem::Dependency
|
||||
name: hoe
|
||||
prerelease: false
|
||||
requirement: &id007 !ruby/object:Gem::Requirement
|
||||
none: false
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
hash: 41
|
||||
segments:
|
||||
- 2
|
||||
- 9
|
||||
- 1
|
||||
version: 2.9.1
|
||||
type: :development
|
||||
version_requirements: *id007
|
||||
description: "Net::LDAP for Ruby (also called net-ldap) implements client access for the\n\
|
||||
Lightweight Directory Access Protocol (LDAP), an IETF standard protocol for\n\
|
||||
accessing distributed directory services. Net::LDAP is written completely in\n\
|
||||
Ruby with no external dependencies. It supports most LDAP client features and a\n\
|
||||
subset of server features as well.\n\n\
|
||||
Net::LDAP has been tested against modern popular LDAP servers including\n\
|
||||
OpenLDAP and Active Directory. The current release is mostly compliant with\n\
|
||||
earlier versions of the IETF LDAP RFCs (2251\xE2\x80\x932256, 2829\xE2\x80\x932830, 3377, and 3771).\n\
|
||||
Our roadmap for Net::LDAP 1.0 is to gain full <em>client</em> compliance with\n\
|
||||
the most recent LDAP RFCs (4510\xE2\x80\x934519, plus portions of 4520\xE2\x80\x934532)."
|
||||
email:
|
||||
- blackhedd@rubyforge.org
|
||||
- gemiel@gmail.com
|
||||
- rory.ocon@gmail.com
|
||||
- kaspar.schiess@absurd.li
|
||||
- austin@rubyforge.org
|
||||
executables: []
|
||||
|
||||
extensions: []
|
||||
|
||||
extra_rdoc_files:
|
||||
- Manifest.txt
|
||||
- Contributors.rdoc
|
||||
- Hacking.rdoc
|
||||
- History.rdoc
|
||||
- License.rdoc
|
||||
- README.rdoc
|
||||
files:
|
||||
- Manifest.txt
|
||||
- Contributors.rdoc
|
||||
- Hacking.rdoc
|
||||
- History.rdoc
|
||||
- License.rdoc
|
||||
- README.rdoc
|
||||
homepage: http://net-ldap.rubyforge.org/
|
||||
licenses: []
|
||||
|
||||
post_install_message:
|
||||
rdoc_options:
|
||||
- --main
|
||||
- README.rdoc
|
||||
require_paths:
|
||||
- lib
|
||||
required_ruby_version: !ruby/object:Gem::Requirement
|
||||
none: false
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
hash: 57
|
||||
segments:
|
||||
- 1
|
||||
- 8
|
||||
- 7
|
||||
version: 1.8.7
|
||||
required_rubygems_version: !ruby/object:Gem::Requirement
|
||||
none: false
|
||||
requirements:
|
||||
- - ">="
|
||||
- !ruby/object:Gem::Version
|
||||
hash: 3
|
||||
segments:
|
||||
- 0
|
||||
version: "0"
|
||||
requirements: []
|
||||
|
||||
rubyforge_project: net-ldap
|
||||
rubygems_version: 1.7.2
|
||||
signing_key:
|
||||
specification_version: 3
|
||||
summary: Net::LDAP for Ruby (also called net-ldap) implements client access for the Lightweight Directory Access Protocol (LDAP), an IETF standard protocol for accessing distributed directory services
|
||||
test_files: []
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
== Contributors
|
||||
|
||||
Net::LDAP was originally developed by:
|
||||
|
||||
* Francis Cianfrocca (garbagecat)
|
||||
|
||||
Contributions since:
|
||||
|
||||
* Emiel van de Laar (emiel)
|
||||
* Rory O'Connell (roryo)
|
||||
* Kaspar Schiess (kschiess)
|
||||
* Austin Ziegler (halostatue)
|
||||
* Dimitrij Denissenko (dim)
|
||||
* James Hewitt (jamstah)
|
||||
* Kouhei Sutou (kou)
|
||||
* Lars Tobias Skjong-Børsting (larstobi)
|
||||
* Rory O'Connell (roryo)
|
||||
* Tony Headford (tonyheadford)
|
||||
* Derek Harmel (derekharmel)
|
||||
* Erik Hetzner (egh)
|
||||
* nowhereman
|
|
@ -0,0 +1,68 @@
|
|||
= Hacking on Net::LDAP
|
||||
|
||||
We welcome your contributions to Net::LDAP. We accept most contributions, but
|
||||
there are ways to increase the chance of your patch being accepted quickly.
|
||||
|
||||
== Licensing
|
||||
|
||||
Net::LDAP 0.2 and later are be licensed under an MIT-style license; any
|
||||
contributions after 2010-04-20 must be under this license to be accepted.
|
||||
|
||||
== Formatting
|
||||
|
||||
* Your patches should be formatted like the rest of Net::LDAP.
|
||||
* We use a text wrap of 76–78 characters, especially for documentation
|
||||
contents.
|
||||
* Operators should have spaces around them.
|
||||
* Method definitions should have parentheses around arguments (and no
|
||||
parentheses if there are no arguments).
|
||||
* Indentation should be kept as flat as possible; this may mean being more
|
||||
explicit with constants.
|
||||
|
||||
|
||||
We welcome your contributions to Net::LDAP. To increase the chances of your
|
||||
patches being accepted, we recommend that you follow the guidelines below:
|
||||
|
||||
== Documentation
|
||||
|
||||
* Documentation: {net-ldap}[http://net-ldap.rubyforge.org/]
|
||||
|
||||
It is very important that, if you add new methods or objects, your code is
|
||||
well-documented. The purpose of the changes should be clearly described so that
|
||||
even if this is a feature we do not use, we can understand its purpose.
|
||||
|
||||
We also encourage documentation-only contributions that improve the
|
||||
documentation of Net::LDAP.
|
||||
|
||||
We encourage you to provide a good summary of your as a modification to
|
||||
+History.rdoc+, and if you're not yet named as a contributor, include a
|
||||
modification to +Contributors.rdoc+ to add yourself.
|
||||
|
||||
== Tests
|
||||
|
||||
The Net::LDAP team uses RSpec for unit testing; all changes must have rspec
|
||||
tests for any new or changed features.
|
||||
|
||||
Your changes should have been tested against at least one real LDAP server; the
|
||||
current tests are not sufficient to find all possible bugs. It's unlikely that
|
||||
they will ever be sufficient given the variations in LDAP server behaviour.
|
||||
|
||||
If you're introducing a new feature, it would be preferred for you to provide
|
||||
us with a sample LDIF data file for importing into LDAP servers for testing.
|
||||
|
||||
== Development Dependencies
|
||||
|
||||
Net::LDAP uses several libraries during development, all of which can be
|
||||
installed using RubyGems.
|
||||
|
||||
* *hoe*
|
||||
* *hoe-git*
|
||||
* *metaid*
|
||||
* *rspec*
|
||||
* *flexmock*
|
||||
|
||||
== Participation
|
||||
|
||||
* RubyForge: {net-ldap}[http://rubyforge.org/projects/net-ldap]
|
||||
* GitHub: {ruby-ldap/ruby-net-ldap}[https://github.com/ruby-ldap/ruby-net-ldap/]
|
||||
* Group: {ruby-ldap}[http://groups.google.com/group/ruby-ldap]
|
|
@ -0,0 +1,172 @@
|
|||
=== Net::LDAP 0.2.2 / 2011-03-26
|
||||
* Bug Fixes:
|
||||
* Fixed the call to Net::LDAP.modify_ops from Net::LDAP#modify.
|
||||
|
||||
=== Net::LDAP 0.2.1 / 2011-03-23
|
||||
* Bug Fixes:
|
||||
* Net::LDAP.modify_ops was broken and is now fixed.
|
||||
|
||||
=== Net::LDAP 0.2 / 2011-03-22
|
||||
* Major Enhancements:
|
||||
* Net::LDAP::Filter changes:
|
||||
* Filters can only be constructed using our custom constructors (eq, ge,
|
||||
etc.). Cleaned up the code to reflect the private new.
|
||||
* Fixed #to_ber to output a BER representation for :ne filters. Simplified
|
||||
the BER construction for substring matching.
|
||||
* Added Filter.join(left, right), Filter.intersect(left, right), and
|
||||
Filter.negate(filter) to match Filter#&, Filter#|, and Filter#~@ to
|
||||
prevent those operators from having problems with the private new.
|
||||
* Added Filter.present and Filter.present? aliases for the method
|
||||
previously only known as Filter.pres.
|
||||
* Added Filter.escape to escape strings for use in filters, based on
|
||||
rfc4515.
|
||||
* Added Filter.equals, Filter.begins, Filter.ends and Filter.contains,
|
||||
which automatically escape input for use in a filter string.
|
||||
* Cleaned up Net::LDAP::Filter::FilterParser to handle branches better.
|
||||
Fixed some of the regular expressions to be more canonically defined.
|
||||
* Correctly handles single-branch branches.
|
||||
* Cleaned up the string representation of Filter objects.
|
||||
* Added experimental support for RFC4515 extensible matching (e.g.,
|
||||
"(cn:caseExactMatch:=Fred Flintstone)"); provided by "nowhereman".
|
||||
* Net::LDAP::DN class representing an automatically escaping/unescaping
|
||||
distinguished name for LDAP queries.
|
||||
* Minor Enhancements:
|
||||
* SSL capabilities will be enabled or disabled based on whether we can load
|
||||
OpenSSL successfully or not.
|
||||
* Moved the core class extensions extensions from being in the Net::LDAP
|
||||
hierarchy to the Net::BER hierarchy as most of the methods therein are
|
||||
related to BER-encoding values. This will make extracting Net::BER from
|
||||
Net::LDAP easier in the future.
|
||||
* Added some unit tests for the BER core extensions.
|
||||
* Paging controls are only sent where they are supported.
|
||||
* Documentation Changes:
|
||||
* Core class extension methods under Net::BER.
|
||||
* Extensive changes to Net::BER documentation.
|
||||
* Cleaned up some rdoc oddities, suppressed empty documentation sections
|
||||
where possible.
|
||||
* Added a document describing how to contribute to Net::LDAP most
|
||||
effectively.
|
||||
* Added a document recognizing contributors to Net::LDAP.
|
||||
* Extended unit testing:
|
||||
* Added some unit tests for the BER core extensions.
|
||||
* The LDIF test data file was split for Ruby 1.9 regexp support.
|
||||
* Added a cruisecontrol.rb task.
|
||||
* Converted some test/unit tests to specs.
|
||||
* Code clean-up:
|
||||
* Made the formatting of code consistent across all files.
|
||||
* Removed Net::BER::BERParser::TagClasses as it does not appear to be used.
|
||||
* Replaced calls to #to_a with calls to Kernel#Array; since Ruby 1.8.3, the
|
||||
default #to_a implementation has been deprecated and should be replaced
|
||||
either with calls to Kernel#Array or [value].flatten(1).
|
||||
* Modified #add and #modify to return a Pdu#result_code instead of a
|
||||
Pdu#result. This may be changed in Net::LDAP 1.0 to return the full
|
||||
Pdu#result, but if we do so, it will be that way for all LDAP calls
|
||||
involving Pdu objects.
|
||||
* Renamed Net::LDAP::Psw to Net::LDAP::Password with a corresponding filename
|
||||
change.
|
||||
* Removed the stub file lib/net/ldif.rb and class Net::LDIF.
|
||||
* Project Management:
|
||||
* Changed the license from Ruby + GPL to MIT with the agreement of the
|
||||
original author (Francis Cianfrocca) and the named contributors. Versions
|
||||
prior to 0.2.0 are still available under the Ruby + GPL license.
|
||||
|
||||
=== Net::LDAP 0.1.1 / 2010-03-18
|
||||
* Fixing a critical problem with sockets.
|
||||
|
||||
=== Net::LDAP 0.1 / 2010-03-17
|
||||
* Small fixes throughout, more to come.
|
||||
* Ruby 1.9 support added.
|
||||
* Ruby 1.8.6 and below support removed. If we can figure out a compatible way
|
||||
to reintroduce this, we will.
|
||||
* New maintainers, new project repository location. Please see the README.txt.
|
||||
|
||||
=== Net::LDAP 0.0.5 / 2009-03-xx
|
||||
* 13 minor enhancements:
|
||||
* Added Net::LDAP::Entry#to_ldif
|
||||
* Supported rootDSE searches with a new API.
|
||||
* Added [preliminary (still undocumented) support for SASL authentication.
|
||||
* Supported several constructs from the server side of the LDAP protocol.
|
||||
* Added a "consuming" String#read_ber! method.
|
||||
* Added some support for SNMP data-handling.
|
||||
* Belatedly added a patch contributed by Kouhei Sutou last October.
|
||||
The patch adds start_tls support.
|
||||
* Added Net::LDAP#search_subschema_entry
|
||||
* Added Net::LDAP::Filter#parse_ber, which constructs Net::LDAP::Filter
|
||||
objects directly from BER objects that represent search filters in
|
||||
LDAP SearchRequest packets.
|
||||
* Added Net::LDAP::Filter#execute, which allows arbitrary processing
|
||||
based on LDAP filters.
|
||||
* Changed Net::LDAP::Entry so it can be marshalled and unmarshalled.
|
||||
Thanks to an anonymous feature requester who only left the name
|
||||
"Jammy."
|
||||
* Added support for binary values in Net::LDAP::Entry LDIF conversions
|
||||
and marshalling.
|
||||
* Migrated to 'hoe' as the new project droid.
|
||||
* 14 bugs fixed:
|
||||
* Silenced some annoying warnings in filter.rb. Thanks to "barjunk"
|
||||
for pointing this out.
|
||||
* Some fairly extensive performance optimizations in the BER parser.
|
||||
* Fixed a bug in Net::LDAP::Entry::from_single_ldif_string noticed by
|
||||
Matthias Tarasiewicz.
|
||||
* Removed an erroneous LdapError value, noticed by Kouhei Sutou.
|
||||
* Supported attributes containing blanks (cn=Babs Jensen) to
|
||||
Filter#construct. Suggested by an anonymous Rubyforge user.
|
||||
* Added missing syntactic support for Filter ANDs, NOTs and a few other
|
||||
things.
|
||||
* Extended support for server-reported error messages. This was provisionally
|
||||
added to Net::LDAP#add, and eventually will be added to other methods.
|
||||
* Fixed bug in Net::LDAP#bind. We were ignoring the passed-in auth parm.
|
||||
Thanks to Kouhei Sutou for spotting it.
|
||||
* Patched filter syntax to support octal \XX codes. Thanks to Kouhei Sutou
|
||||
for the patch.
|
||||
* Applied an additional patch from Kouhei.
|
||||
* Allowed comma in filter strings, suggested by Kouhei.
|
||||
* 04Sep07, Changed four error classes to inherit from StandardError rather
|
||||
Exception, in order to be friendlier to irb. Suggested by Kouhei.
|
||||
* Ensure connections are closed. Thanks to Kristian Meier.
|
||||
* Minor bug fixes here and there.
|
||||
|
||||
=== Net::LDAP 0.0.4 / 2006-08-15
|
||||
* 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 / 2006-07-26
|
||||
* Added simple TLS encryption.
|
||||
Thanks to Garett Shulman for suggestions and for helping test.
|
||||
|
||||
=== Net::LDAP 0.0.2 / 2006-07-12
|
||||
* 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 / 2006-05-01
|
||||
* 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.
|
|
@ -0,0 +1,29 @@
|
|||
== License
|
||||
|
||||
This software is available under the terms of the MIT license.
|
||||
|
||||
Copyright 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 in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
=== Notice of License Change
|
||||
|
||||
Versions prior to 0.2 were under Ruby's dual license with the GNU GPL. With
|
||||
this release (0.2), Net::LDAP is now under the MIT license.
|
|
@ -0,0 +1,49 @@
|
|||
.autotest
|
||||
.rspec
|
||||
Contributors.rdoc
|
||||
Hacking.rdoc
|
||||
History.rdoc
|
||||
License.rdoc
|
||||
Manifest.txt
|
||||
README.rdoc
|
||||
Rakefile
|
||||
autotest/discover.rb
|
||||
lib/net-ldap.rb
|
||||
lib/net/ber.rb
|
||||
lib/net/ber/ber_parser.rb
|
||||
lib/net/ber/core_ext.rb
|
||||
lib/net/ber/core_ext/array.rb
|
||||
lib/net/ber/core_ext/bignum.rb
|
||||
lib/net/ber/core_ext/false_class.rb
|
||||
lib/net/ber/core_ext/fixnum.rb
|
||||
lib/net/ber/core_ext/string.rb
|
||||
lib/net/ber/core_ext/true_class.rb
|
||||
lib/net/ldap.rb
|
||||
lib/net/ldap/dataset.rb
|
||||
lib/net/ldap/dn.rb
|
||||
lib/net/ldap/entry.rb
|
||||
lib/net/ldap/filter.rb
|
||||
lib/net/ldap/password.rb
|
||||
lib/net/ldap/pdu.rb
|
||||
lib/net/snmp.rb
|
||||
net-ldap.gemspec
|
||||
spec/integration/ssl_ber_spec.rb
|
||||
spec/spec.opts
|
||||
spec/spec_helper.rb
|
||||
spec/unit/ber/ber_spec.rb
|
||||
spec/unit/ber/core_ext/string_spec.rb
|
||||
spec/unit/ldap/dn_spec.rb
|
||||
spec/unit/ldap/entry_spec.rb
|
||||
spec/unit/ldap/filter_spec.rb
|
||||
spec/unit/ldap_spec.rb
|
||||
test/common.rb
|
||||
test/test_entry.rb
|
||||
test/test_filter.rb
|
||||
test/test_ldap_connection.rb
|
||||
test/test_ldif.rb
|
||||
test/test_password.rb
|
||||
test/test_rename.rb
|
||||
test/test_snmp.rb
|
||||
test/testdata.ldif
|
||||
testserver/ldapserver.rb
|
||||
testserver/testdata.ldif
|
|
@ -0,0 +1,52 @@
|
|||
= Net::LDAP for Ruby
|
||||
|
||||
== Description
|
||||
|
||||
Net::LDAP for Ruby (also called net-ldap) implements client access for the
|
||||
Lightweight Directory Access Protocol (LDAP), an IETF standard protocol for
|
||||
accessing distributed directory services. Net::LDAP is written completely in
|
||||
Ruby with no external dependencies. It supports most LDAP client features and a
|
||||
subset of server features as well.
|
||||
|
||||
Net::LDAP has been tested against modern popular LDAP servers including
|
||||
OpenLDAP and Active Directory. The current release is mostly compliant with
|
||||
earlier versions of the IETF LDAP RFCs (2251–2256, 2829–2830, 3377, and 3771).
|
||||
Our roadmap for Net::LDAP 1.0 is to gain full <em>client</em> compliance with
|
||||
the most recent LDAP RFCs (4510–4519, plus portions of 4520–4532).
|
||||
|
||||
== Where
|
||||
|
||||
* {RubyForge}[http://rubyforge.org/projects/net-ldap]
|
||||
* {GitHub}[https://github.com/ruby-ldap/ruby-net-ldap]
|
||||
* {ruby-ldap@googlegroups.com}[http://groups.google.com/group/ruby-ldap]
|
||||
* {Documentation}[http://net-ldap.rubyforge.org/]
|
||||
|
||||
The Net::LDAP for Ruby documentation, project description, and main downloads
|
||||
can currently be found on {RubyForge}[http://rubyforge.org/projects/net-ldap].
|
||||
|
||||
== Synopsis
|
||||
|
||||
See Net::LDAP for documentation and usage samples.
|
||||
|
||||
== Requirements
|
||||
|
||||
Net::LDAP requires a Ruby 1.8.7 interpreter or better.
|
||||
|
||||
== Install
|
||||
|
||||
Net::LDAP is a pure Ruby library. It does not require any external libraries.
|
||||
You can install the RubyGems version of Net::LDAP available from the usual
|
||||
sources.
|
||||
|
||||
gem install net-ldap
|
||||
|
||||
Simply require either 'net-ldap' or 'net/ldap'.
|
||||
|
||||
For non-RubyGems installations of Net::LDAP, you can use Minero Aoki's
|
||||
{setup.rb}[http://i.loveruby.net/en/projects/setup/] as the layout of
|
||||
Net::LDAP is compliant. The setup installer is not included in the
|
||||
Net::LDAP repository.
|
||||
|
||||
:include: Contributors.rdoc
|
||||
|
||||
:include: License.rdoc
|
|
@ -0,0 +1,75 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
|
||||
require "rubygems"
|
||||
require 'hoe'
|
||||
|
||||
Hoe.plugin :doofus
|
||||
Hoe.plugin :git
|
||||
Hoe.plugin :gemspec
|
||||
Hoe.plugin :rubyforge
|
||||
|
||||
Hoe.spec 'net-ldap' do |spec|
|
||||
spec.rubyforge_name = spec.name
|
||||
|
||||
spec.developer("Francis Cianfrocca", "blackhedd@rubyforge.org")
|
||||
spec.developer("Emiel van de Laar", "gemiel@gmail.com")
|
||||
spec.developer("Rory O'Connell", "rory.ocon@gmail.com")
|
||||
spec.developer("Kaspar Schiess", "kaspar.schiess@absurd.li")
|
||||
spec.developer("Austin Ziegler", "austin@rubyforge.org")
|
||||
|
||||
spec.remote_rdoc_dir = ''
|
||||
spec.rsync_args << ' --exclude=statsvn/'
|
||||
|
||||
spec.url = %W(http://net-ldap.rubyforge.org/ https://github.com/ruby-ldap/ruby-net-ldap)
|
||||
|
||||
spec.history_file = 'History.rdoc'
|
||||
spec.readme_file = 'README.rdoc'
|
||||
|
||||
spec.extra_rdoc_files = FileList["*.rdoc"].to_a
|
||||
|
||||
spec.extra_dev_deps << [ "hoe-git", "~> 1" ]
|
||||
spec.extra_dev_deps << [ "hoe-gemspec", "~> 1" ]
|
||||
spec.extra_dev_deps << [ "metaid", "~> 1" ]
|
||||
spec.extra_dev_deps << [ "flexmock", "~> 0.9.0" ]
|
||||
spec.extra_dev_deps << [ "rspec", "~> 2.0" ]
|
||||
|
||||
spec.clean_globs << "coverage"
|
||||
|
||||
spec.spec_extras[:required_ruby_version] = ">= 1.8.7"
|
||||
spec.multiruby_skip << "1.8.6"
|
||||
spec.multiruby_skip << "1_8_6"
|
||||
|
||||
spec.need_tar = true
|
||||
end
|
||||
|
||||
# I'm not quite ready to get rid of this, but I think "rake git:manifest" is
|
||||
# sufficient.
|
||||
namespace :old do
|
||||
desc "Build the manifest file from the current set of files."
|
||||
task :build_manifest do |t|
|
||||
require 'find'
|
||||
|
||||
paths = []
|
||||
Find.find(".") do |path|
|
||||
next if File.directory?(path)
|
||||
next if path =~ /\.svn/
|
||||
next if path =~ /\.git/
|
||||
next if path =~ /\.hoerc/
|
||||
next if path =~ /\.swp$/
|
||||
next if path =~ %r{coverage/}
|
||||
next if path =~ /~$/
|
||||
paths << path.sub(%r{^\./}, '')
|
||||
end
|
||||
|
||||
File.open("Manifest.txt", "w") do |f|
|
||||
f.puts paths.sort.join("\n")
|
||||
end
|
||||
|
||||
puts paths.sort.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
desc "Run a full set of integration and unit tests"
|
||||
task :cruise => [:test, :spec]
|
||||
|
||||
# vim: syntax=ruby
|
|
@ -0,0 +1 @@
|
|||
Autotest.add_discovery { "rspec2" }
|
|
@ -0,0 +1,2 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
require 'net/ldap'
|
|
@ -0,0 +1,316 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
module Net # :nodoc:
|
||||
##
|
||||
# == Basic Encoding Rules (BER) Support Module
|
||||
#
|
||||
# Much of the text below is cribbed from Wikipedia:
|
||||
# http://en.wikipedia.org/wiki/Basic_Encoding_Rules
|
||||
#
|
||||
# The ITU Specification is also worthwhile reading:
|
||||
# http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf
|
||||
#
|
||||
# The Basic Encoding Rules were the original rules laid out by the ASN.1
|
||||
# standard for encoding abstract information into a concrete data stream.
|
||||
# The rules, collectively referred to as a transfer syntax in ASN.1
|
||||
# parlance, specify the exact octet sequences which are used to encode a
|
||||
# given data item. The syntax defines such elements as: the
|
||||
# representations for basic data types, the structure of length
|
||||
# information, and the means for defining complex or compound types based
|
||||
# on more primitive types. The BER syntax, along with two subsets of BER
|
||||
# (the Canonical Encoding Rules and the Distinguished Encoding Rules), are
|
||||
# defined by the ITU-T's X.690 standards document, which is part of the
|
||||
# ASN.1 document series.
|
||||
#
|
||||
# == Encoding
|
||||
# The BER format specifies a self-describing and self-delimiting format
|
||||
# for encoding ASN.1 data structures. Each data element is encoded as a
|
||||
# type identifier, a length description, the actual data elements, and
|
||||
# where necessary, an end-of-content marker. This format allows a receiver
|
||||
# to decode the ASN.1 information from an incomplete stream, without
|
||||
# requiring any pre-knowledge of the size, content, or semantic meaning of
|
||||
# the data.
|
||||
#
|
||||
# <Type | Length | Value [| End-of-Content]>
|
||||
#
|
||||
# == Protocol Data Units (PDU)
|
||||
# Protocols are defined with schema represented in BER, such that a PDU
|
||||
# consists of cascaded type-length-value encodings.
|
||||
#
|
||||
# === Type Tags
|
||||
# BER type tags are represented as single octets (bytes). The lower five
|
||||
# bits of the octet are tag identifier numbers and the upper three bits of
|
||||
# the octet are used to distinguish the type as native to ASN.1,
|
||||
# application-specific, context-specific, or private. See
|
||||
# Net::BER::TAG_CLASS and Net::BER::ENCODING_TYPE for more information.
|
||||
#
|
||||
# If Class is set to Universal (0b00______), the value is of a type native
|
||||
# to ASN.1 (e.g. INTEGER). The Application class (0b01______) is only
|
||||
# valid for one specific application. Context_specific (0b10______)
|
||||
# depends on the context and private (0b11_______) can be defined in
|
||||
# private specifications
|
||||
#
|
||||
# If the primitive/constructed bit is zero (0b__0_____), it specifies that
|
||||
# the value is primitive like an INTEGER. If it is one (0b__1_____), the
|
||||
# value is a constructed value that contains type-length-value encoded
|
||||
# types like a SET or a SEQUENCE.
|
||||
#
|
||||
# === Defined Universal (ASN.1 Native) Types
|
||||
# There are a number of pre-defined universal (native) types.
|
||||
#
|
||||
# <table>
|
||||
# <tr><th>Name</th><th>Primitive<br />Constructed</th><th>Number</th></tr>
|
||||
# <tr><th>EOC (End-of-Content)</th><th>P</th><td>0: 0 (0x0, 0b00000000)</td></tr>
|
||||
# <tr><th>BOOLEAN</th><th>P</th><td>1: 1 (0x01, 0b00000001)</td></tr>
|
||||
# <tr><th>INTEGER</th><th>P</th><td>2: 2 (0x02, 0b00000010)</td></tr>
|
||||
# <tr><th>BIT STRING</th><th>P</th><td>3: 3 (0x03, 0b00000011)</td></tr>
|
||||
# <tr><th>BIT STRING</th><th>C</th><td>3: 35 (0x23, 0b00100011)</td></tr>
|
||||
# <tr><th>OCTET STRING</th><th>P</th><td>4: 4 (0x04, 0b00000100)</td></tr>
|
||||
# <tr><th>OCTET STRING</th><th>C</th><td>4: 36 (0x24, 0b00100100)</td></tr>
|
||||
# <tr><th>NULL</th><th>P</th><td>5: 5 (0x05, 0b00000101)</td></tr>
|
||||
# <tr><th>OBJECT IDENTIFIER</th><th>P</th><td>6: 6 (0x06, 0b00000110)</td></tr>
|
||||
# <tr><th>Object Descriptor</th><th>P</th><td>7: 7 (0x07, 0b00000111)</td></tr>
|
||||
# <tr><th>EXTERNAL</th><th>C</th><td>8: 40 (0x28, 0b00101000)</td></tr>
|
||||
# <tr><th>REAL (float)</th><th>P</th><td>9: 9 (0x09, 0b00001001)</td></tr>
|
||||
# <tr><th>ENUMERATED</th><th>P</th><td>10: 10 (0x0a, 0b00001010)</td></tr>
|
||||
# <tr><th>EMBEDDED PDV</th><th>C</th><td>11: 43 (0x2b, 0b00101011)</td></tr>
|
||||
# <tr><th>UTF8String</th><th>P</th><td>12: 12 (0x0c, 0b00001100)</td></tr>
|
||||
# <tr><th>UTF8String</th><th>C</th><td>12: 44 (0x2c, 0b00101100)</td></tr>
|
||||
# <tr><th>RELATIVE-OID</th><th>P</th><td>13: 13 (0x0d, 0b00001101)</td></tr>
|
||||
# <tr><th>SEQUENCE and SEQUENCE OF</th><th>C</th><td>16: 48 (0x30, 0b00110000)</td></tr>
|
||||
# <tr><th>SET and SET OF</th><th>C</th><td>17: 49 (0x31, 0b00110001)</td></tr>
|
||||
# <tr><th>NumericString</th><th>P</th><td>18: 18 (0x12, 0b00010010)</td></tr>
|
||||
# <tr><th>NumericString</th><th>C</th><td>18: 50 (0x32, 0b00110010)</td></tr>
|
||||
# <tr><th>PrintableString</th><th>P</th><td>19: 19 (0x13, 0b00010011)</td></tr>
|
||||
# <tr><th>PrintableString</th><th>C</th><td>19: 51 (0x33, 0b00110011)</td></tr>
|
||||
# <tr><th>T61String</th><th>P</th><td>20: 20 (0x14, 0b00010100)</td></tr>
|
||||
# <tr><th>T61String</th><th>C</th><td>20: 52 (0x34, 0b00110100)</td></tr>
|
||||
# <tr><th>VideotexString</th><th>P</th><td>21: 21 (0x15, 0b00010101)</td></tr>
|
||||
# <tr><th>VideotexString</th><th>C</th><td>21: 53 (0x35, 0b00110101)</td></tr>
|
||||
# <tr><th>IA5String</th><th>P</th><td>22: 22 (0x16, 0b00010110)</td></tr>
|
||||
# <tr><th>IA5String</th><th>C</th><td>22: 54 (0x36, 0b00110110)</td></tr>
|
||||
# <tr><th>UTCTime</th><th>P</th><td>23: 23 (0x17, 0b00010111)</td></tr>
|
||||
# <tr><th>UTCTime</th><th>C</th><td>23: 55 (0x37, 0b00110111)</td></tr>
|
||||
# <tr><th>GeneralizedTime</th><th>P</th><td>24: 24 (0x18, 0b00011000)</td></tr>
|
||||
# <tr><th>GeneralizedTime</th><th>C</th><td>24: 56 (0x38, 0b00111000)</td></tr>
|
||||
# <tr><th>GraphicString</th><th>P</th><td>25: 25 (0x19, 0b00011001)</td></tr>
|
||||
# <tr><th>GraphicString</th><th>C</th><td>25: 57 (0x39, 0b00111001)</td></tr>
|
||||
# <tr><th>VisibleString</th><th>P</th><td>26: 26 (0x1a, 0b00011010)</td></tr>
|
||||
# <tr><th>VisibleString</th><th>C</th><td>26: 58 (0x3a, 0b00111010)</td></tr>
|
||||
# <tr><th>GeneralString</th><th>P</th><td>27: 27 (0x1b, 0b00011011)</td></tr>
|
||||
# <tr><th>GeneralString</th><th>C</th><td>27: 59 (0x3b, 0b00111011)</td></tr>
|
||||
# <tr><th>UniversalString</th><th>P</th><td>28: 28 (0x1c, 0b00011100)</td></tr>
|
||||
# <tr><th>UniversalString</th><th>C</th><td>28: 60 (0x3c, 0b00111100)</td></tr>
|
||||
# <tr><th>CHARACTER STRING</th><th>P</th><td>29: 29 (0x1d, 0b00011101)</td></tr>
|
||||
# <tr><th>CHARACTER STRING</th><th>C</th><td>29: 61 (0x3d, 0b00111101)</td></tr>
|
||||
# <tr><th>BMPString</th><th>P</th><td>30: 30 (0x1e, 0b00011110)</td></tr>
|
||||
# <tr><th>BMPString</th><th>C</th><td>30: 62 (0x3e, 0b00111110)</td></tr>
|
||||
# </table>
|
||||
module BER
|
||||
VERSION = '0.2.2'
|
||||
|
||||
##
|
||||
# Used for BER-encoding the length and content bytes of a Fixnum integer
|
||||
# values.
|
||||
MAX_FIXNUM_SIZE = 0.size
|
||||
|
||||
##
|
||||
# BER tag classes are kept in bits seven and eight of the tag type
|
||||
# octet.
|
||||
#
|
||||
# <table>
|
||||
# <tr><th>Bitmask</th><th>Definition</th></tr>
|
||||
# <tr><th><tt>0b00______</tt></th><td>Universal (ASN.1 Native) Types</td></tr>
|
||||
# <tr><th><tt>0b01______</tt></th><td>Application Types</td></tr>
|
||||
# <tr><th><tt>0b10______</tt></th><td>Context-Specific Types</td></tr>
|
||||
# <tr><th><tt>0b11______</tt></th><td>Private Types</td></tr>
|
||||
# </table>
|
||||
TAG_CLASS = {
|
||||
:universal => 0b00000000, # 0
|
||||
:application => 0b01000000, # 64
|
||||
:context_specific => 0b10000000, # 128
|
||||
:private => 0b11000000, # 192
|
||||
}
|
||||
|
||||
##
|
||||
# BER encoding type is kept in bit 6 of the tag type octet.
|
||||
#
|
||||
# <table>
|
||||
# <tr><th>Bitmask</th><th>Definition</th></tr>
|
||||
# <tr><th><tt>0b__0_____</tt></th><td>Primitive</td></tr>
|
||||
# <tr><th><tt>0b__1_____</tt></th><td>Constructed</td></tr>
|
||||
# </table>
|
||||
ENCODING_TYPE = {
|
||||
:primitive => 0b00000000, # 0
|
||||
:constructed => 0b00100000, # 32
|
||||
}
|
||||
|
||||
##
|
||||
# Accepts a hash of hashes describing a BER syntax and converts it into
|
||||
# a byte-keyed object for fast BER conversion lookup. The resulting
|
||||
# "compiled" syntax is used by Net::BER::BERParser.
|
||||
#
|
||||
# This method should be called only by client classes of Net::BER (e.g.,
|
||||
# Net::LDAP and Net::SNMP) and not by clients of those classes.
|
||||
#
|
||||
# The hash-based syntax uses TAG_CLASS keys that contain hashes of
|
||||
# ENCODING_TYPE keys that contain tag numbers with object type markers.
|
||||
#
|
||||
# :<TAG_CLASS> => {
|
||||
# :<ENCODING_TYPE> => {
|
||||
# <number> => <object-type>
|
||||
# },
|
||||
# },
|
||||
#
|
||||
# === Permitted Object Types
|
||||
# <tt>:string</tt>:: A string value, represented as BerIdentifiedString.
|
||||
# <tt>:integer</tt>:: An integer value, represented with Fixnum.
|
||||
# <tt>:oid</tt>:: An Object Identifier value; see X.690 section
|
||||
# 8.19. Currently represented with a standard array,
|
||||
# but may be better represented as a
|
||||
# BerIdentifiedOID object.
|
||||
# <tt>:array</tt>:: A sequence, represented as BerIdentifiedArray.
|
||||
# <tt>:boolean</tt>:: A boolean value, represented as +true+ or +false+.
|
||||
# <tt>:null</tt>:: A null value, represented as BerIdentifiedNull.
|
||||
#
|
||||
# === Example
|
||||
# Net::LDAP defines its ASN.1 BER syntax something like this:
|
||||
#
|
||||
# class Net::LDAP
|
||||
# AsnSyntax = Net::BER.compile_syntax({
|
||||
# :application => {
|
||||
# :primitive => {
|
||||
# 2 => :null,
|
||||
# },
|
||||
# :constructed => {
|
||||
# 0 => :array,
|
||||
# # ...
|
||||
# },
|
||||
# },
|
||||
# :context_specific => {
|
||||
# :primitive => {
|
||||
# 0 => :string,
|
||||
# # ...
|
||||
# },
|
||||
# :constructed => {
|
||||
# 0 => :array,
|
||||
# # ...
|
||||
# },
|
||||
# }
|
||||
# })
|
||||
# end
|
||||
#
|
||||
# NOTE:: For readability and formatting purposes, Net::LDAP and its
|
||||
# siblings actually construct their syntaxes more deliberately,
|
||||
# as shown below. Since a hash is passed in the end in any case,
|
||||
# the format does not matter.
|
||||
#
|
||||
# primitive = { 2 => :null }
|
||||
# constructed = {
|
||||
# 0 => :array,
|
||||
# # ...
|
||||
# }
|
||||
# application = {
|
||||
# :primitive => primitive,
|
||||
# :constructed => constructed
|
||||
# }
|
||||
#
|
||||
# primitive = {
|
||||
# 0 => :string,
|
||||
# # ...
|
||||
# }
|
||||
# constructed = {
|
||||
# 0 => :array,
|
||||
# # ...
|
||||
# }
|
||||
# context_specific = {
|
||||
# :primitive => primitive,
|
||||
# :constructed => constructed
|
||||
# }
|
||||
# AsnSyntax = Net::BER.compile_syntax(:application => application,
|
||||
# :context_specific => context_specific)
|
||||
def self.compile_syntax(syntax)
|
||||
# TODO 20100327 AZ: Should we be allocating an array of 256 values
|
||||
# that will either be +nil+ or an object type symbol, or should we
|
||||
# allocate an empty Hash since unknown values return +nil+ anyway?
|
||||
out = [ nil ] * 256
|
||||
syntax.each do |tag_class_id, encodings|
|
||||
tag_class = TAG_CLASS[tag_class_id]
|
||||
encodings.each do |encoding_id, classes|
|
||||
encoding = ENCODING_TYPE[encoding_id]
|
||||
object_class = tag_class + encoding
|
||||
classes.each do |number, object_type|
|
||||
out[object_class + number] = object_type
|
||||
end
|
||||
end
|
||||
end
|
||||
out
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Net::BER::BerError < RuntimeError; end
|
||||
|
||||
##
|
||||
# An Array object with a BER identifier attached.
|
||||
class Net::BER::BerIdentifiedArray < Array
|
||||
attr_accessor :ber_identifier
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# A BER object identifier.
|
||||
class Net::BER::BerIdentifiedOid
|
||||
attr_accessor :ber_identifier
|
||||
|
||||
def initialize(oid)
|
||||
if oid.is_a?(String)
|
||||
oid = oid.split(/\./).map {|s| s.to_i }
|
||||
end
|
||||
@value = oid
|
||||
end
|
||||
|
||||
def to_ber
|
||||
to_ber_oid
|
||||
end
|
||||
|
||||
def to_ber_oid
|
||||
@value.to_ber_oid
|
||||
end
|
||||
|
||||
def to_s
|
||||
@value.join(".")
|
||||
end
|
||||
|
||||
def to_arr
|
||||
@value.dup
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# A String object with a BER identifier attached.
|
||||
class Net::BER::BerIdentifiedString < String
|
||||
attr_accessor :ber_identifier
|
||||
def initialize args
|
||||
super args
|
||||
end
|
||||
end
|
||||
|
||||
module Net::BER
|
||||
##
|
||||
# A BER null object.
|
||||
class BerIdentifiedNull
|
||||
attr_accessor :ber_identifier
|
||||
def to_ber
|
||||
"\005\000"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# The default BerIdentifiedNull object.
|
||||
Null = Net::BER::BerIdentifiedNull.new
|
||||
end
|
||||
|
||||
require 'net/ber/core_ext'
|
|
@ -0,0 +1,168 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
require 'stringio'
|
||||
|
||||
# Implements Basic Encoding Rules parsing to be mixed into types as needed.
|
||||
module Net::BER::BERParser
|
||||
primitive = {
|
||||
1 => :boolean,
|
||||
2 => :integer,
|
||||
4 => :string,
|
||||
5 => :null,
|
||||
6 => :oid,
|
||||
10 => :integer,
|
||||
13 => :string # (relative OID)
|
||||
}
|
||||
constructed = {
|
||||
16 => :array,
|
||||
17 => :array
|
||||
}
|
||||
universal = { :primitive => primitive, :constructed => constructed }
|
||||
|
||||
primitive = { 10 => :integer }
|
||||
context = { :primitive => primitive }
|
||||
|
||||
# The universal, built-in ASN.1 BER syntax.
|
||||
BuiltinSyntax = Net::BER.compile_syntax(:universal => universal,
|
||||
:context_specific => context)
|
||||
|
||||
##
|
||||
# This is an extract of our BER object parsing to simplify our
|
||||
# understanding of how we parse basic BER object types.
|
||||
def parse_ber_object(syntax, id, data)
|
||||
# Find the object type from either the provided syntax lookup table or
|
||||
# the built-in syntax lookup table.
|
||||
#
|
||||
# This exceptionally clever bit of code is verrrry slow.
|
||||
object_type = (syntax && syntax[id]) || BuiltinSyntax[id]
|
||||
|
||||
# == is expensive so sort this so the common cases are at the top.
|
||||
if object_type == :string
|
||||
s = Net::BER::BerIdentifiedString.new(data || "")
|
||||
s.ber_identifier = id
|
||||
s
|
||||
elsif object_type == :integer
|
||||
j = 0
|
||||
data.each_byte { |b| j = (j << 8) + b }
|
||||
j
|
||||
elsif object_type == :oid
|
||||
# See X.690 pgh 8.19 for an explanation of this algorithm.
|
||||
# This is potentially not good enough. We may need a
|
||||
# BerIdentifiedOid as a subclass of BerIdentifiedArray, to
|
||||
# get the ber identifier and also a to_s method that produces
|
||||
# the familiar dotted notation.
|
||||
oid = data.unpack("w*")
|
||||
f = oid.shift
|
||||
g = if f < 40
|
||||
[0, f]
|
||||
elsif f < 80
|
||||
[1, f - 40]
|
||||
else
|
||||
# f - 80 can easily be > 80. What a weird optimization.
|
||||
[2, f - 80]
|
||||
end
|
||||
oid.unshift g.last
|
||||
oid.unshift g.first
|
||||
# Net::BER::BerIdentifiedOid.new(oid)
|
||||
oid
|
||||
elsif object_type == :array
|
||||
seq = Net::BER::BerIdentifiedArray.new
|
||||
seq.ber_identifier = id
|
||||
sio = StringIO.new(data || "")
|
||||
# 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
|
||||
elsif object_type == :boolean
|
||||
data != "\000"
|
||||
elsif object_type == :null
|
||||
n = Net::BER::BerIdentifiedNull.new
|
||||
n.ber_identifier = id
|
||||
n
|
||||
else
|
||||
raise Net::BER::BerError, "Unsupported object type: id=#{id}"
|
||||
end
|
||||
end
|
||||
private :parse_ber_object
|
||||
|
||||
##
|
||||
# This is an extract of how our BER object length parsing is done to
|
||||
# simplify the primary call. This is defined in X.690 section 8.1.3.
|
||||
#
|
||||
# The BER length will either be a single byte or up to 126 bytes in
|
||||
# length. There is a special case of a BER length indicating that the
|
||||
# content-length is undefined and will be identified by the presence of
|
||||
# two null values (0x00 0x00).
|
||||
#
|
||||
# <table>
|
||||
# <tr>
|
||||
# <th>Range</th>
|
||||
# <th>Length</th>
|
||||
# </tr>
|
||||
# <tr>
|
||||
# <th>0x00 -- 0x7f<br />0b00000000 -- 0b01111111</th>
|
||||
# <td>0 - 127 bytes</td>
|
||||
# </tr>
|
||||
# <tr>
|
||||
# <th>0x80<br />0b10000000</th>
|
||||
# <td>Indeterminate (end-of-content marker required)</td>
|
||||
# </tr>
|
||||
# <tr>
|
||||
# <th>0x81 -- 0xfe<br />0b10000001 -- 0b11111110</th>
|
||||
# <td>1 - 126 bytes of length as an integer value</td>
|
||||
# </tr>
|
||||
# <tr>
|
||||
# <th>0xff<br />0b11111111</th>
|
||||
# <td>Illegal (reserved for future expansion)</td>
|
||||
# </tr>
|
||||
# </table>
|
||||
#
|
||||
#--
|
||||
# This has been modified from the version that was previously inside
|
||||
# #read_ber to handle both the indeterminate terminator case and the
|
||||
# invalid BER length case. Because the "lengthlength" value was not used
|
||||
# inside of #read_ber, we no longer return it.
|
||||
def read_ber_length
|
||||
n = getbyte
|
||||
|
||||
if n <= 0x7f
|
||||
n
|
||||
elsif n == 0x80
|
||||
-1
|
||||
elsif n == 0xff
|
||||
raise Net::BER::BerError, "Invalid BER length 0xFF detected."
|
||||
else
|
||||
v = 0
|
||||
read(n & 0x7f).each_byte do |b|
|
||||
v = (v << 8) + b
|
||||
end
|
||||
|
||||
v
|
||||
end
|
||||
end
|
||||
private :read_ber_length
|
||||
|
||||
##
|
||||
# Reads a BER object from the including object. Requires that #getbyte is
|
||||
# implemented on the including object and that it returns a Fixnum value.
|
||||
# Also requires #read(bytes) to work.
|
||||
#
|
||||
# This does not work with non-blocking I/O.
|
||||
def read_ber(syntax = nil)
|
||||
# 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.
|
||||
|
||||
id = getbyte or return nil # don't trash this value, we'll use it later
|
||||
content_length = read_ber_length
|
||||
|
||||
if -1 == content_length
|
||||
raise Net::BER::BerError, "Indeterminite BER content length not implemented."
|
||||
else
|
||||
data = read(content_length)
|
||||
end
|
||||
|
||||
parse_ber_object(syntax, id, data)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,62 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
require 'net/ber/ber_parser'
|
||||
# :stopdoc:
|
||||
class IO
|
||||
include Net::BER::BERParser
|
||||
end
|
||||
|
||||
class StringIO
|
||||
include Net::BER::BERParser
|
||||
end
|
||||
|
||||
if defined? ::OpenSSL
|
||||
class OpenSSL::SSL::SSLSocket
|
||||
include Net::BER::BERParser
|
||||
end
|
||||
end
|
||||
# :startdoc:
|
||||
|
||||
module Net::BER::Extensions # :nodoc:
|
||||
end
|
||||
|
||||
require 'net/ber/core_ext/string'
|
||||
# :stopdoc:
|
||||
class String
|
||||
include Net::BER::BERParser
|
||||
include Net::BER::Extensions::String
|
||||
end
|
||||
|
||||
require 'net/ber/core_ext/array'
|
||||
# :stopdoc:
|
||||
class Array
|
||||
include Net::BER::Extensions::Array
|
||||
end
|
||||
# :startdoc:
|
||||
|
||||
require 'net/ber/core_ext/bignum'
|
||||
# :stopdoc:
|
||||
class Bignum
|
||||
include Net::BER::Extensions::Bignum
|
||||
end
|
||||
# :startdoc:
|
||||
|
||||
require 'net/ber/core_ext/fixnum'
|
||||
# :stopdoc:
|
||||
class Fixnum
|
||||
include Net::BER::Extensions::Fixnum
|
||||
end
|
||||
# :startdoc:
|
||||
|
||||
require 'net/ber/core_ext/true_class'
|
||||
# :stopdoc:
|
||||
class TrueClass
|
||||
include Net::BER::Extensions::TrueClass
|
||||
end
|
||||
# :startdoc:
|
||||
|
||||
require 'net/ber/core_ext/false_class'
|
||||
# :stopdoc:
|
||||
class FalseClass
|
||||
include Net::BER::Extensions::FalseClass
|
||||
end
|
||||
# :startdoc:
|
|
@ -0,0 +1,82 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
##
|
||||
# BER extensions to the Array class.
|
||||
module Net::BER::Extensions::Array
|
||||
##
|
||||
# Converts an Array to a BER sequence. All values in the Array are
|
||||
# expected to be in BER format prior to calling this method.
|
||||
def to_ber(id = 0)
|
||||
# The universal sequence tag 0x30 is composed of the base tag value
|
||||
# (0x10) and the constructed flag (0x20).
|
||||
to_ber_seq_internal(0x30 + id)
|
||||
end
|
||||
alias_method :to_ber_sequence, :to_ber
|
||||
|
||||
##
|
||||
# Converts an Array to a BER set. All values in the Array are expected to
|
||||
# be in BER format prior to calling this method.
|
||||
def to_ber_set(id = 0)
|
||||
# The universal set tag 0x31 is composed of the base tag value (0x11)
|
||||
# and the constructed flag (0x20).
|
||||
to_ber_seq_internal(0x31 + id)
|
||||
end
|
||||
|
||||
##
|
||||
# Converts an Array to an application-specific sequence, assigned a tag
|
||||
# value that is meaningful to the particular protocol being used. All
|
||||
# values in the Array are expected to be in BER format pr prior to calling
|
||||
# this method.
|
||||
#--
|
||||
# Implementor's note 20100320(AZ): RFC 4511 (the LDAPv3 protocol) as well
|
||||
# as earlier RFCs 1777 and 2559 seem to indicate that LDAP only has
|
||||
# application constructed sequences (0x60). However, ldapsearch sends some
|
||||
# context-specific constructed sequences (0xA0); other clients may do the
|
||||
# same. This behaviour appears to violate the RFCs. In real-world
|
||||
# practice, we may need to change calls of #to_ber_appsequence to
|
||||
# #to_ber_contextspecific for full LDAP server compatibility.
|
||||
#
|
||||
# This note probably belongs elsewhere.
|
||||
#++
|
||||
def to_ber_appsequence(id = 0)
|
||||
# The application sequence tag always starts from the application flag
|
||||
# (0x40) and the constructed flag (0x20).
|
||||
to_ber_seq_internal(0x60 + id)
|
||||
end
|
||||
|
||||
##
|
||||
# Converts an Array to a context-specific sequence, assigned a tag value
|
||||
# that is meaningful to the particular context of the particular protocol
|
||||
# being used. All values in the Array are expected to be in BER format
|
||||
# prior to calling this method.
|
||||
def to_ber_contextspecific(id = 0)
|
||||
# The application sequence tag always starts from the context flag
|
||||
# (0x80) and the constructed flag (0x20).
|
||||
to_ber_seq_internal(0xa0 + id)
|
||||
end
|
||||
|
||||
##
|
||||
# The internal sequence packing routine. All values in the Array are
|
||||
# expected to be in BER format prior to calling this method.
|
||||
def to_ber_seq_internal(code)
|
||||
s = self.join
|
||||
[code].pack('C') + s.length.to_ber_length_encoding + s
|
||||
end
|
||||
private :to_ber_seq_internal
|
||||
|
||||
##
|
||||
# SNMP Object Identifiers (OID) are special arrays
|
||||
#--
|
||||
# 20100320 AZ: I do not think that this method should be in BER, since
|
||||
# this appears to be SNMP-specific. This should probably be subsumed by a
|
||||
# proper SNMP OID object.
|
||||
#++
|
||||
def to_ber_oid
|
||||
ary = self.dup
|
||||
first = ary.shift
|
||||
raise Net::BER::BerError, "Invalid OID" unless [0, 1, 2].include?(first)
|
||||
first = first * 40 + ary.shift
|
||||
ary.unshift first
|
||||
oid = ary.pack("w*")
|
||||
[6, oid.length].pack("CC") + oid
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
##
|
||||
# BER extensions to the Bignum class.
|
||||
module Net::BER::Extensions::Bignum
|
||||
##
|
||||
# Converts a Bignum to an uncompressed BER integer.
|
||||
def to_ber
|
||||
result = []
|
||||
|
||||
# NOTE: Array#pack's 'w' is a BER _compressed_ integer. We need
|
||||
# uncompressed BER integers, so we're not using that. See also:
|
||||
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/228864
|
||||
n = self
|
||||
while n > 0
|
||||
b = n & 0xff
|
||||
result << b
|
||||
n = n >> 8
|
||||
end
|
||||
|
||||
"\002" + ([result.size] + result.reverse).pack('C*')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
##
|
||||
# BER extensions to +false+.
|
||||
module Net::BER::Extensions::FalseClass
|
||||
##
|
||||
# Converts +false+ to the BER wireline representation of +false+.
|
||||
def to_ber
|
||||
"\001\001\000"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,66 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
##
|
||||
# Ber extensions to the Fixnum class.
|
||||
module Net::BER::Extensions::Fixnum
|
||||
##
|
||||
# Converts the fixnum to BER format.
|
||||
def to_ber
|
||||
"\002#{to_ber_internal}"
|
||||
end
|
||||
|
||||
##
|
||||
# Converts the fixnum to BER enumerated format.
|
||||
def to_ber_enumerated
|
||||
"\012#{to_ber_internal}"
|
||||
end
|
||||
|
||||
##
|
||||
# Converts the fixnum to BER length encodining format.
|
||||
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
|
||||
|
||||
##
|
||||
# Generate a BER-encoding for an application-defined INTEGER. Examples of
|
||||
# such integers are SNMP's Counter, Gauge, and TimeTick types.
|
||||
def to_ber_application(tag)
|
||||
[0x40 + tag].pack("C") + to_ber_internal
|
||||
end
|
||||
|
||||
##
|
||||
# Used to BER-encode the length and content bytes of a Fixnum. Callers
|
||||
# must prepend the tag byte for the contained value.
|
||||
def to_ber_internal
|
||||
# CAUTION: Bit twiddling ahead. You might want to shield your eyes or
|
||||
# something.
|
||||
|
||||
# Looks for the first byte in the fixnum that is not all zeroes. It does
|
||||
# this by masking one byte after another, checking the result for bits
|
||||
# that are left on.
|
||||
size = Net::BER::MAX_FIXNUM_SIZE
|
||||
while size > 1
|
||||
break if (self & (0xff << (size - 1) * 8)) > 0
|
||||
size -= 1
|
||||
end
|
||||
|
||||
# Store the size of the fixnum in the result
|
||||
result = [size]
|
||||
|
||||
# Appends bytes to result, starting with higher orders first. Extraction
|
||||
# of bytes is done by right shifting the original fixnum by an amount
|
||||
# and then masking that with 0xff.
|
||||
while size > 0
|
||||
# right shift size - 1 bytes, mask with 0xff
|
||||
result << ((self >> ((size - 1) * 8)) & 0xff)
|
||||
size -= 1
|
||||
end
|
||||
|
||||
result.pack('C*')
|
||||
end
|
||||
private :to_ber_internal
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
require 'stringio'
|
||||
|
||||
##
|
||||
# BER extensions to the String class.
|
||||
module Net::BER::Extensions::String
|
||||
##
|
||||
# Converts a string to a BER string. Universal octet-strings are tagged
|
||||
# with 0x04, but other values are possible depending on the context, so we
|
||||
# let the caller give us one.
|
||||
#
|
||||
# User code should call either #to_ber_application_string or
|
||||
# #to_ber_contextspecific.
|
||||
def to_ber(code = 0x04)
|
||||
[code].pack('C') + length.to_ber_length_encoding + self
|
||||
end
|
||||
|
||||
##
|
||||
# Creates an application-specific BER string encoded value with the
|
||||
# provided syntax code value.
|
||||
def to_ber_application_string(code)
|
||||
to_ber(0x40 + code)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a context-specific BER string encoded value with the provided
|
||||
# syntax code value.
|
||||
def to_ber_contextspecific(code)
|
||||
to_ber(0x80 + code)
|
||||
end
|
||||
|
||||
##
|
||||
# Nondestructively reads a BER object from this string.
|
||||
def read_ber(syntax = nil)
|
||||
StringIO.new(self).read_ber(syntax)
|
||||
end
|
||||
|
||||
##
|
||||
# Destructively reads a BER object from the string.
|
||||
def read_ber!(syntax = nil)
|
||||
io = StringIO.new(self)
|
||||
|
||||
result = io.read_ber(syntax)
|
||||
self.slice!(0...io.pos)
|
||||
|
||||
return result
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
##
|
||||
# BER extensions to +true+.
|
||||
module Net::BER::Extensions::TrueClass
|
||||
##
|
||||
# Converts +true+ to the BER wireline representation of +true+.
|
||||
def to_ber
|
||||
# 20100319 AZ: Note that this may not be the completely correct value,
|
||||
# per some test documentation. We need to determine the truth of this.
|
||||
"\001\001\001"
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,154 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
##
|
||||
# An LDAP Dataset. Used primarily as an intermediate format for converting
|
||||
# to and from LDIF strings and Net::LDAP::Entry objects.
|
||||
class Net::LDAP::Dataset < Hash
|
||||
##
|
||||
# Dataset object comments.
|
||||
attr_reader :comments
|
||||
|
||||
def initialize(*args, &block) # :nodoc:
|
||||
super
|
||||
@comments = []
|
||||
end
|
||||
|
||||
##
|
||||
# Outputs an LDAP Dataset as an array of strings representing LDIF
|
||||
# entries.
|
||||
def to_ldif
|
||||
ary = []
|
||||
ary += @comments unless @comments.empty?
|
||||
keys.sort.each do |dn|
|
||||
ary << "dn: #{dn}"
|
||||
|
||||
attributes = self[dn].keys.map { |attr| attr.to_s }.sort
|
||||
attributes.each do |attr|
|
||||
self[dn][attr.to_sym].each do |value|
|
||||
if attr == "userpassword" or value_is_binary?(value)
|
||||
value = [value].pack("m").chomp.gsub(/\n/m, "\n ")
|
||||
ary << "#{attr}:: #{value}"
|
||||
else
|
||||
ary << "#{attr}: #{value}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ary << ""
|
||||
end
|
||||
block_given? and ary.each { |line| yield line}
|
||||
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# Outputs an LDAP Dataset as an LDIF string.
|
||||
def to_ldif_string
|
||||
to_ldif.join("\n")
|
||||
end
|
||||
|
||||
##
|
||||
# Convert the parsed LDIF objects to Net::LDAP::Entry objects.
|
||||
def to_entries
|
||||
ary = []
|
||||
keys.each do |dn|
|
||||
entry = Net::LDAP::Entry.new(dn)
|
||||
self[dn].each do |attr, value|
|
||||
entry[attr] = value
|
||||
end
|
||||
ary << entry
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# This is an internal convenience method to determine if a value requires
|
||||
# base64-encoding before conversion to LDIF output. The standard approach
|
||||
# in most LDAP tools is to check whether the value is a password, or if
|
||||
# the first or last bytes are non-printable. Microsoft Active Directory,
|
||||
# on the other hand, sometimes sends values that are binary in the middle.
|
||||
#
|
||||
# In the worst cases, this could be a nasty performance killer, which is
|
||||
# why we handle the simplest cases first. Ideally, we would also test the
|
||||
# first/last byte, but it's a bit harder to do this in a way that's
|
||||
# compatible with both 1.8.6 and 1.8.7.
|
||||
def value_is_binary?(value) # :nodoc:
|
||||
value = value.to_s
|
||||
return true if value[0] == ?: or value[0] == ?<
|
||||
value.each_byte { |byte| return true if (byte < 32) || (byte > 126) }
|
||||
false
|
||||
end
|
||||
private :value_is_binary?
|
||||
|
||||
class << self
|
||||
class ChompedIO # :nodoc:
|
||||
def initialize(io)
|
||||
@io = io
|
||||
end
|
||||
def gets
|
||||
s = @io.gets
|
||||
s.chomp if s
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Dataset object from an Entry object. Used mostly to assist
|
||||
# with the conversion of
|
||||
def from_entry(entry)
|
||||
dataset = Net::LDAP::Dataset.new
|
||||
hash = { }
|
||||
entry.each_attribute do |attribute, value|
|
||||
next if attribute == :dn
|
||||
hash[attribute] = value
|
||||
end
|
||||
dataset[entry.dn] = hash
|
||||
dataset
|
||||
end
|
||||
|
||||
##
|
||||
# Reads an object that returns data line-wise (using #gets) and parses
|
||||
# LDIF data into a Dataset object.
|
||||
def read_ldif(io)
|
||||
ds = Net::LDAP::Dataset.new
|
||||
io = ChompedIO.new(io)
|
||||
|
||||
line = io.gets
|
||||
dn = nil
|
||||
|
||||
while line
|
||||
new_line = io.gets
|
||||
|
||||
if new_line =~ /^[\s]+/
|
||||
line << " " << $'
|
||||
else
|
||||
nextline = new_line
|
||||
|
||||
if line =~ /^#/
|
||||
ds.comments << line
|
||||
yield :comment, line if block_given?
|
||||
elsif line =~ /^dn:[\s]*/i
|
||||
dn = $'
|
||||
ds[dn] = Hash.new { |k,v| k[v] = [] }
|
||||
yield :dn, dn if block_given?
|
||||
elsif line.empty?
|
||||
dn = nil
|
||||
yield :end, nil if block_given?
|
||||
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.to_sym] << attrvalue
|
||||
yield :attr, [$1.downcase.to_sym, attrvalue] if block_given?
|
||||
end
|
||||
|
||||
line = nextline
|
||||
end
|
||||
end
|
||||
|
||||
ds
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'net/ldap/entry' unless defined? Net::LDAP::Entry
|
|
@ -0,0 +1,225 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
|
||||
##
|
||||
# Objects of this class represent an LDAP DN ("Distinguished Name"). A DN
|
||||
# ("Distinguished Name") is a unique identifier for an entry within an LDAP
|
||||
# directory. It is made up of a number of other attributes strung together,
|
||||
# to identify the entry in the tree.
|
||||
#
|
||||
# Each attribute that makes up a DN needs to have its value escaped so that
|
||||
# the DN is valid. This class helps take care of that.
|
||||
#
|
||||
# A fully escaped DN needs to be unescaped when analysing its contents. This
|
||||
# class also helps take care of that.
|
||||
class Net::LDAP::DN
|
||||
##
|
||||
# Initialize a DN, escaping as required. Pass in attributes in name/value
|
||||
# pairs. If there is a left over argument, it will be appended to the dn
|
||||
# without escaping (useful for a base string).
|
||||
#
|
||||
# Most uses of this class will be to escape a DN, rather than to parse it,
|
||||
# so storing the dn as an escaped String and parsing parts as required
|
||||
# with a state machine seems sensible.
|
||||
def initialize(*args)
|
||||
buffer = StringIO.new
|
||||
|
||||
args.each_index do |index|
|
||||
buffer << "=" if index % 2 == 1
|
||||
buffer << "," if index % 2 == 0 && index != 0
|
||||
|
||||
if index < args.length - 1 || index % 2 == 1
|
||||
buffer << Net::LDAP::DN.escape(args[index])
|
||||
else
|
||||
buffer << args[index]
|
||||
end
|
||||
end
|
||||
|
||||
@dn = buffer.string
|
||||
end
|
||||
|
||||
##
|
||||
# Parse a DN into key value pairs using ASN from
|
||||
# http://tools.ietf.org/html/rfc2253 section 3.
|
||||
def each_pair
|
||||
state = :key
|
||||
key = StringIO.new
|
||||
value = StringIO.new
|
||||
hex_buffer = ""
|
||||
|
||||
@dn.each_char do |char|
|
||||
case state
|
||||
when :key then
|
||||
case char
|
||||
when 'a'..'z', 'A'..'Z' then
|
||||
state = :key_normal
|
||||
key << char
|
||||
when '0'..'9' then
|
||||
state = :key_oid
|
||||
key << char
|
||||
when ' ' then state = :key
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :key_normal then
|
||||
case char
|
||||
when '=' then state = :value
|
||||
when 'a'..'z', 'A'..'Z', '0'..'9', '-', ' ' then key << char
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :key_oid then
|
||||
case char
|
||||
when '=' then state = :value
|
||||
when '0'..'9', '.', ' ' then key << char
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value then
|
||||
case char
|
||||
when '\\' then state = :value_normal_escape
|
||||
when '"' then state = :value_quoted
|
||||
when ' ' then state = :value
|
||||
when '#' then
|
||||
state = :value_hexstring
|
||||
value << char
|
||||
when ',' then
|
||||
state = :key
|
||||
yield key.string.strip, value.string.rstrip
|
||||
key = StringIO.new
|
||||
value = StringIO.new;
|
||||
else
|
||||
state = :value_normal
|
||||
value << char
|
||||
end
|
||||
when :value_normal then
|
||||
case char
|
||||
when '\\' then state = :value_normal_escape
|
||||
when ',' then
|
||||
state = :key
|
||||
yield key.string.strip, value.string.rstrip
|
||||
key = StringIO.new
|
||||
value = StringIO.new;
|
||||
else value << char
|
||||
end
|
||||
when :value_normal_escape then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_normal_escape_hex
|
||||
hex_buffer = char
|
||||
else state = :value_normal; value << char
|
||||
end
|
||||
when :value_normal_escape_hex then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_normal
|
||||
value << "#{hex_buffer}#{char}".to_i(16).chr
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value_quoted then
|
||||
case char
|
||||
when '\\' then state = :value_quoted_escape
|
||||
when '"' then state = :value_end
|
||||
else value << char
|
||||
end
|
||||
when :value_quoted_escape then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_quoted_escape_hex
|
||||
hex_buffer = char
|
||||
else
|
||||
state = :value_quoted;
|
||||
value << char
|
||||
end
|
||||
when :value_quoted_escape_hex then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_quoted
|
||||
value << "#{hex_buffer}#{char}".to_i(16).chr
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value_hexstring then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_hexstring_hex
|
||||
value << char
|
||||
when ' ' then state = :value_end
|
||||
when ',' then
|
||||
state = :key
|
||||
yield key.string.strip, value.string.rstrip
|
||||
key = StringIO.new
|
||||
value = StringIO.new;
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value_hexstring_hex then
|
||||
case char
|
||||
when '0'..'9', 'a'..'f', 'A'..'F' then
|
||||
state = :value_hexstring
|
||||
value << char
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
when :value_end then
|
||||
case char
|
||||
when ' ' then state = :value_end
|
||||
when ',' then
|
||||
state = :key
|
||||
yield key.string.strip, value.string.rstrip
|
||||
key = StringIO.new
|
||||
value = StringIO.new;
|
||||
else raise "DN badly formed"
|
||||
end
|
||||
else raise "Fell out of state machine"
|
||||
end
|
||||
end
|
||||
|
||||
# Last pair
|
||||
if [:value, :value_normal, :value_hexstring, :value_end].include? state
|
||||
yield key.string.strip, value.string.rstrip
|
||||
else
|
||||
raise "DN badly formed"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the DN as an array in the form expected by the constructor.
|
||||
def to_a
|
||||
a = []
|
||||
self.each_pair { |key, value| a << key << value }
|
||||
a
|
||||
end
|
||||
|
||||
##
|
||||
# Return the DN as an escaped string.
|
||||
def to_s
|
||||
@dn
|
||||
end
|
||||
|
||||
# http://tools.ietf.org/html/rfc2253 section 2.4 lists these exceptions
|
||||
# for dn values. All of the following must be escaped in any normal string
|
||||
# using a single backslash ('\') as escape.
|
||||
ESCAPES = {
|
||||
',' => ',',
|
||||
'+' => '+',
|
||||
'"' => '"',
|
||||
'\\' => '\\',
|
||||
'<' => '<',
|
||||
'>' => '>',
|
||||
';' => ';',
|
||||
}
|
||||
|
||||
# Compiled character class regexp using the keys from the above hash, and
|
||||
# checking for a space or # at the start, or space at the end, of the
|
||||
# string.
|
||||
ESCAPE_RE = Regexp.new("(^ |^#| $|[" +
|
||||
ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
|
||||
"])")
|
||||
|
||||
##
|
||||
# Escape a string for use in a DN value
|
||||
def self.escape(string)
|
||||
string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] }
|
||||
end
|
||||
|
||||
##
|
||||
# Proxy all other requests to the string object, because a DN is mainly
|
||||
# used within the library as a string
|
||||
def method_missing(method, *args, &block)
|
||||
@dn.send(method, *args, &block)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,185 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
##
|
||||
# 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 Net::LDAP::Entry
|
||||
##
|
||||
# This constructor is not generally called by user code.
|
||||
def initialize(dn = nil) #:nodoc:
|
||||
@myhash = {}
|
||||
@myhash[:dn] = [dn]
|
||||
end
|
||||
|
||||
##
|
||||
# Use the LDIF format for Marshal serialization.
|
||||
def _dump(depth) #:nodoc:
|
||||
to_ldif
|
||||
end
|
||||
|
||||
##
|
||||
# Use the LDIF format for Marshal serialization.
|
||||
def self._load(entry) #:nodoc:
|
||||
from_single_ldif_string(entry)
|
||||
end
|
||||
|
||||
class << self
|
||||
##
|
||||
# Converts a single LDIF entry string into an Entry object. Useful for
|
||||
# Marshal serialization. If a string with multiple LDIF entries is
|
||||
# provided, an exception will be raised.
|
||||
def from_single_ldif_string(ldif)
|
||||
ds = Net::LDAP::Dataset.read_ldif(::StringIO.new(ldif))
|
||||
|
||||
return nil if ds.empty?
|
||||
|
||||
raise Net::LDAP::LdapError, "Too many LDIF entries" unless ds.size == 1
|
||||
|
||||
entry = ds.to_entries.first
|
||||
|
||||
return nil if entry.dn.nil?
|
||||
entry
|
||||
end
|
||||
|
||||
##
|
||||
# Canonicalizes an LDAP attribute name as a \Symbol. The name is
|
||||
# lowercased and, if present, a trailing equals sign is removed.
|
||||
def attribute_name(name)
|
||||
name = name.to_s.downcase
|
||||
name = name[0..-2] if name[-1] == ?=
|
||||
name.to_sym
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Sets or replaces the array of values for the provided attribute. The
|
||||
# attribute name is canonicalized prior to assignment.
|
||||
#
|
||||
# When an attribute is set using this, that attribute is now made
|
||||
# accessible through methods as well.
|
||||
#
|
||||
# entry = Net::LDAP::Entry.new("dc=com")
|
||||
# entry.foo # => NoMethodError
|
||||
# entry["foo"] = 12345 # => [12345]
|
||||
# entry.foo # => [12345]
|
||||
def []=(name, value)
|
||||
@myhash[self.class.attribute_name(name)] = Kernel::Array(value)
|
||||
end
|
||||
|
||||
##
|
||||
# Reads the array of values for the provided attribute. The attribute name
|
||||
# is canonicalized prior to reading. Returns an empty array if the
|
||||
# attribute does not exist.
|
||||
def [](name)
|
||||
name = self.class.attribute_name(name)
|
||||
@myhash[name] || []
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the first distinguished name (dn) of the Entry as a \String.
|
||||
def dn
|
||||
self[:dn].first.to_s
|
||||
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 # :yields: attribute-name, data-values-array
|
||||
if block_given?
|
||||
attribute_names.each {|a|
|
||||
attr_name,values = a,self[a]
|
||||
yield attr_name, values
|
||||
}
|
||||
end
|
||||
end
|
||||
alias_method :each_attribute, :each
|
||||
|
||||
##
|
||||
# Converts the Entry to an LDIF-formatted String
|
||||
def to_ldif
|
||||
Net::LDAP::Dataset.from_entry(self).to_ldif_string
|
||||
end
|
||||
|
||||
def respond_to?(sym) #:nodoc:
|
||||
return true if valid_attribute?(self.class.attribute_name(sym))
|
||||
return super
|
||||
end
|
||||
|
||||
def method_missing(sym, *args, &block) #:nodoc:
|
||||
name = self.class.attribute_name(sym)
|
||||
|
||||
if valid_attribute?(name )
|
||||
if setter?(sym) && args.size == 1
|
||||
value = args.first
|
||||
value = Array(value)
|
||||
self[name]= value
|
||||
return value
|
||||
elsif args.empty?
|
||||
return self[name]
|
||||
end
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# Given a valid attribute symbol, returns true.
|
||||
def valid_attribute?(attr_name)
|
||||
attribute_names.include?(attr_name)
|
||||
end
|
||||
private :valid_attribute?
|
||||
|
||||
# Returns true if the symbol ends with an equal sign.
|
||||
def setter?(sym)
|
||||
sym.to_s[-1] == ?=
|
||||
end
|
||||
private :setter?
|
||||
end # class Entry
|
||||
|
||||
require 'net/ldap/dataset' unless defined? Net::LDAP::Dataset
|
|
@ -0,0 +1,759 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
|
||||
##
|
||||
# Class Net::LDAP::Filter is used to constrain LDAP searches. An object of
|
||||
# this class is passed to Net::LDAP#search in the parameter :filter.
|
||||
#
|
||||
# Net::LDAP::Filter supports the complete set of search filters available in
|
||||
# LDAP, including conjunction, disjunction and negation (AND, OR, and NOT).
|
||||
# This class supplants the (infamous) RFC 2254 standard notation for
|
||||
# specifying LDAP search filters.
|
||||
#--
|
||||
# NOTE: This wording needs to change as we will be supporting LDAPv3 search
|
||||
# filter strings (RFC 4515).
|
||||
#++
|
||||
#
|
||||
# Here's how to code the familiar "objectclass is present" filter:
|
||||
# f = Net::LDAP::Filter.present("objectclass")
|
||||
#
|
||||
# The object returned by this code can be passed directly to the
|
||||
# <tt>:filter</tt> parameter of Net::LDAP#search.
|
||||
#
|
||||
# See the individual class and instance methods below for more examples.
|
||||
class Net::LDAP::Filter
|
||||
##
|
||||
# Known filter types.
|
||||
FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex ]
|
||||
|
||||
def initialize(op, left, right) #:nodoc:
|
||||
unless FilterTypes.include?(op)
|
||||
raise Net::LDAP::LdapError, "Invalid or unsupported operator #{op.inspect} in LDAP Filter."
|
||||
end
|
||||
@op = op
|
||||
@left = left
|
||||
@right = right
|
||||
end
|
||||
|
||||
class << self
|
||||
# We don't want filters created except using our custom constructors.
|
||||
private :new
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that the value of a particular
|
||||
# attribute must either be present or match a particular string.
|
||||
#
|
||||
# Specifying that an attribute is 'present' means only directory entries
|
||||
# which contain a value for the particular attribute will be selected by
|
||||
# the filter. This is useful in case of optional attributes such as
|
||||
# <tt>mail.</tt> Presence is indicated by giving the value "*" in the
|
||||
# second parameter to #eq. This example selects only entries that have
|
||||
# one or more values for <tt>sAMAccountName:</tt>
|
||||
#
|
||||
# f = Net::LDAP::Filter.eq("sAMAccountName", "*")
|
||||
#
|
||||
# To match a particular range of values, pass a string as the second
|
||||
# parameter to #eq. The string may contain one or more "*" characters as
|
||||
# wildcards: these match zero or more occurrences of any character. Full
|
||||
# regular-expressions are <i>not</i> supported due to limitations in the
|
||||
# underlying LDAP protocol. This example selects any entry with a
|
||||
# <tt>mail</tt> value containing the substring "anderson":
|
||||
#
|
||||
# f = Net::LDAP::Filter.eq("mail", "*anderson*")
|
||||
#
|
||||
# This filter does not perform any escaping
|
||||
def eq(attribute, value)
|
||||
new(:eq, attribute, value)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating extensible comparison. This Filter
|
||||
# object is currently considered EXPERIMENTAL.
|
||||
#
|
||||
# sample_attributes = ['cn:fr', 'cn:fr.eq',
|
||||
# 'cn:1.3.6.1.4.1.42.2.27.9.4.49.1.3', 'cn:dn:fr', 'cn:dn:fr.eq']
|
||||
# attr = sample_attributes.first # Pick an extensible attribute
|
||||
# value = 'roberts'
|
||||
#
|
||||
# filter = "#{attr}:=#{value}" # Basic String Filter
|
||||
# filter = Net::LDAP::Filter.ex(attr, value) # Net::LDAP::Filter
|
||||
#
|
||||
# # Perform a search with the Extensible Match Filter
|
||||
# Net::LDAP.search(:filter => filter)
|
||||
#--
|
||||
# The LDIF required to support the above examples on the OpenDS LDAP
|
||||
# server:
|
||||
#
|
||||
# version: 1
|
||||
#
|
||||
# dn: dc=example,dc=com
|
||||
# objectClass: domain
|
||||
# objectClass: top
|
||||
# dc: example
|
||||
#
|
||||
# dn: ou=People,dc=example,dc=com
|
||||
# objectClass: organizationalUnit
|
||||
# objectClass: top
|
||||
# ou: People
|
||||
#
|
||||
# dn: uid=1,ou=People,dc=example,dc=com
|
||||
# objectClass: person
|
||||
# objectClass: organizationalPerson
|
||||
# objectClass: inetOrgPerson
|
||||
# objectClass: top
|
||||
# cn:: csO0YsOpcnRz
|
||||
# sn:: YsO0YiByw7Riw6lydHM=
|
||||
# givenName:: YsO0Yg==
|
||||
# uid: 1
|
||||
#
|
||||
# =Refs:
|
||||
# * http://www.ietf.org/rfc/rfc2251.txt
|
||||
# * http://www.novell.com/documentation/edir88/edir88/?page=/documentation/edir88/edir88/data/agazepd.html
|
||||
# * https://docs.opends.org/2.0/page/SearchingUsingInternationalCollationRules
|
||||
#++
|
||||
def ex(attribute, value)
|
||||
new(:ex, attribute, value)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that a particular attribute value
|
||||
# is either not present or does not match a particular string; see
|
||||
# Filter::eq for more information.
|
||||
#
|
||||
# This filter does not perform any escaping
|
||||
def ne(attribute, value)
|
||||
new(:ne, attribute, value)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that the value of a particular
|
||||
# attribute must match a particular string. The attribute value is
|
||||
# escaped, so the "*" character is interpreted literally.
|
||||
def equals(attribute, value)
|
||||
new(:eq, attribute, escape(value))
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that the value of a particular
|
||||
# attribute must begin with a particular string. The attribute value is
|
||||
# escaped, so the "*" character is interpreted literally.
|
||||
def begins(attribute, value)
|
||||
new(:eq, attribute, escape(value) + "*")
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that the value of a particular
|
||||
# attribute must end with a particular string. The attribute value is
|
||||
# escaped, so the "*" character is interpreted literally.
|
||||
def ends(attribute, value)
|
||||
new(:eq, attribute, "*" + escape(value))
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that the value of a particular
|
||||
# attribute must contain a particular string. The attribute value is
|
||||
# escaped, so the "*" character is interpreted literally.
|
||||
def contains(attribute, value)
|
||||
new(:eq, attribute, "*" + escape(value) + "*")
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that a particular attribute value
|
||||
# is greater than or equal to the specified value.
|
||||
def ge(attribute, value)
|
||||
new(:ge, attribute, value)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Filter object indicating that a particular attribute value
|
||||
# is less than or equal to the specified value.
|
||||
def le(attribute, value)
|
||||
new(:le, attribute, value)
|
||||
end
|
||||
|
||||
##
|
||||
# Joins two or more filters so that all conditions must be true. Calling
|
||||
# <tt>Filter.join(left, right)</tt> is the same as <tt>left &
|
||||
# right</tt>.
|
||||
#
|
||||
# # Selects only entries that have an <tt>objectclass</tt> attribute.
|
||||
# x = Net::LDAP::Filter.present("objectclass")
|
||||
# # Selects only entries that have a <tt>mail</tt> attribute that begins
|
||||
# # with "George".
|
||||
# y = Net::LDAP::Filter.eq("mail", "George*")
|
||||
# # Selects only entries that meet both conditions above.
|
||||
# z = Net::LDAP::Filter.join(x, y)
|
||||
def join(left, right)
|
||||
new(:and, left, right)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a disjoint comparison between two or more filters. Selects
|
||||
# entries where either the left or right side are true. Calling
|
||||
# <tt>Filter.intersect(left, right)</tt> is the same as <tt>left |
|
||||
# right</tt>.
|
||||
#
|
||||
# # Selects only entries that have an <tt>objectclass</tt> attribute.
|
||||
# x = Net::LDAP::Filter.present("objectclass")
|
||||
# # Selects only entries that have a <tt>mail</tt> attribute that begins
|
||||
# # with "George".
|
||||
# y = Net::LDAP::Filter.eq("mail", "George*")
|
||||
# # Selects only entries that meet either condition above.
|
||||
# z = x | y
|
||||
def intersect(left, right)
|
||||
new(:or, left, right)
|
||||
end
|
||||
|
||||
##
|
||||
# Negates a filter. Calling <tt>Fitler.negate(filter)</tt> i s the same
|
||||
# as <tt>~filter</tt>.
|
||||
#
|
||||
# # Selects only entries that do not have an <tt>objectclass</tt>
|
||||
# # attribute.
|
||||
# x = ~Net::LDAP::Filter.present("objectclass")
|
||||
def negate(filter)
|
||||
new(:not, filter, nil)
|
||||
end
|
||||
|
||||
##
|
||||
# This is a synonym for #eq(attribute, "*"). Also known as #present and
|
||||
# #pres.
|
||||
def present?(attribute)
|
||||
eq(attribute, "*")
|
||||
end
|
||||
alias_method :present, :present?
|
||||
alias_method :pres, :present?
|
||||
|
||||
# http://tools.ietf.org/html/rfc4515 lists these exceptions from UTF1
|
||||
# charset for filters. All of the following must be escaped in any normal
|
||||
# string using a single backslash ('\') as escape.
|
||||
#
|
||||
ESCAPES = {
|
||||
"\0" => '00', # NUL = %x00 ; null character
|
||||
'*' => '2A', # ASTERISK = %x2A ; asterisk ("*")
|
||||
'(' => '28', # LPARENS = %x28 ; left parenthesis ("(")
|
||||
')' => '29', # RPARENS = %x29 ; right parenthesis (")")
|
||||
'\\' => '5C', # ESC = %x5C ; esc (or backslash) ("\")
|
||||
}
|
||||
# Compiled character class regexp using the keys from the above hash.
|
||||
ESCAPE_RE = Regexp.new(
|
||||
"[" +
|
||||
ESCAPES.keys.map { |e| Regexp.escape(e) }.join +
|
||||
"]")
|
||||
|
||||
##
|
||||
# Escape a string for use in an LDAP filter
|
||||
def escape(string)
|
||||
string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] }
|
||||
end
|
||||
|
||||
##
|
||||
# Converts an LDAP search filter in BER format to an Net::LDAP::Filter
|
||||
# object. The incoming BER object most likely came to us by parsing an
|
||||
# LDAP searchRequest PDU. See also the comments under #to_ber, including
|
||||
# the grammar snippet from the RFC.
|
||||
#--
|
||||
# We're hardcoding the BER constants from the RFC. These should be
|
||||
# broken out insto constants.
|
||||
def parse_ber(ber)
|
||||
case ber.ber_identifier
|
||||
when 0xa0 # context-specific constructed 0, "and"
|
||||
ber.map { |b| parse_ber(b) }.inject { |memo, obj| memo & obj }
|
||||
when 0xa1 # context-specific constructed 1, "or"
|
||||
ber.map { |b| parse_ber(b) }.inject { |memo, obj| memo | obj }
|
||||
when 0xa2 # context-specific constructed 2, "not"
|
||||
~parse_ber(ber.first)
|
||||
when 0xa3 # context-specific constructed 3, "equalityMatch"
|
||||
if ber.last == "*"
|
||||
else
|
||||
eq(ber.first, ber.last)
|
||||
end
|
||||
when 0xa4 # context-specific constructed 4, "substring"
|
||||
str = ""
|
||||
final = false
|
||||
ber.last.each { |b|
|
||||
case b.ber_identifier
|
||||
when 0x80 # context-specific primitive 0, SubstringFilter "initial"
|
||||
raise Net::LDAP::LdapError, "Unrecognized substring filter; bad initial value." if str.length > 0
|
||||
str += b
|
||||
when 0x81 # context-specific primitive 0, SubstringFilter "any"
|
||||
str += "*#{b}"
|
||||
when 0x82 # context-specific primitive 0, SubstringFilter "final"
|
||||
str += "*#{b}"
|
||||
final = true
|
||||
end
|
||||
}
|
||||
str += "*" unless final
|
||||
eq(ber.first.to_s, str)
|
||||
when 0xa5 # context-specific constructed 5, "greaterOrEqual"
|
||||
ge(ber.first.to_s, ber.last.to_s)
|
||||
when 0xa6 # context-specific constructed 6, "lessOrEqual"
|
||||
le(ber.first.to_s, ber.last.to_s)
|
||||
when 0x87 # context-specific primitive 7, "present"
|
||||
# call to_s to get rid of the BER-identifiedness of the incoming string.
|
||||
present?(ber.to_s)
|
||||
when 0xa9 # context-specific constructed 9, "extensible comparison"
|
||||
raise Net::LDAP::LdapError, "Invalid extensible search filter, should be at least two elements" if ber.size<2
|
||||
|
||||
# Reassembles the extensible filter parts
|
||||
# (["sn", "2.4.6.8.10", "Barbara Jones", '1'])
|
||||
type = value = dn = rule = nil
|
||||
ber.each do |element|
|
||||
case element.ber_identifier
|
||||
when 0x81 then rule=element
|
||||
when 0x82 then type=element
|
||||
when 0x83 then value=element
|
||||
when 0x84 then dn='dn'
|
||||
end
|
||||
end
|
||||
|
||||
attribute = ''
|
||||
attribute << type if type
|
||||
attribute << ":#{dn}" if dn
|
||||
attribute << ":#{rule}" if rule
|
||||
|
||||
ex(attribute, value)
|
||||
else
|
||||
raise Net::LDAP::LdapError, "Invalid BER tag-value (#{ber.ber_identifier}) in search filter."
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Converts an LDAP filter-string (in the prefix syntax specified in RFC-2254)
|
||||
# to a Net::LDAP::Filter.
|
||||
def construct(ldap_filter_string)
|
||||
FilterParser.parse(ldap_filter_string)
|
||||
end
|
||||
alias_method :from_rfc2254, :construct
|
||||
alias_method :from_rfc4515, :construct
|
||||
|
||||
##
|
||||
# Convert an RFC-1777 LDAP/BER "Filter" object to a Net::LDAP::Filter
|
||||
# object.
|
||||
#--
|
||||
# TODO, we're hardcoding the RFC-1777 BER-encodings of the various
|
||||
# filter types. Could pull them out into a constant.
|
||||
#++
|
||||
def parse_ldap_filter(obj)
|
||||
case obj.ber_identifier
|
||||
when 0x87 # present. context-specific primitive 7.
|
||||
eq(obj.to_s, "*")
|
||||
when 0xa3 # equalityMatch. context-specific constructed 3.
|
||||
eq(obj[0], obj[1])
|
||||
else
|
||||
raise Net::LDAP::LdapError, "Unknown LDAP search-filter type: #{obj.ber_identifier}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Joins two or more filters so that all conditions must be true.
|
||||
#
|
||||
# # Selects only entries that have an <tt>objectclass</tt> attribute.
|
||||
# x = Net::LDAP::Filter.present("objectclass")
|
||||
# # Selects only entries that have a <tt>mail</tt> attribute that begins
|
||||
# # with "George".
|
||||
# y = Net::LDAP::Filter.eq("mail", "George*")
|
||||
# # Selects only entries that meet both conditions above.
|
||||
# z = x & y
|
||||
def &(filter)
|
||||
self.class.join(self, filter)
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a disjoint comparison between two or more filters. Selects
|
||||
# entries where either the left or right side are true.
|
||||
#
|
||||
# # Selects only entries that have an <tt>objectclass</tt> attribute.
|
||||
# x = Net::LDAP::Filter.present("objectclass")
|
||||
# # Selects only entries that have a <tt>mail</tt> attribute that begins
|
||||
# # with "George".
|
||||
# y = Net::LDAP::Filter.eq("mail", "George*")
|
||||
# # Selects only entries that meet either condition above.
|
||||
# z = x | y
|
||||
def |(filter)
|
||||
self.class.intersect(self, filter)
|
||||
end
|
||||
|
||||
##
|
||||
# Negates a filter.
|
||||
#
|
||||
# # Selects only entries that do not have an <tt>objectclass</tt>
|
||||
# # attribute.
|
||||
# x = ~Net::LDAP::Filter.present("objectclass")
|
||||
def ~@
|
||||
self.class.negate(self)
|
||||
end
|
||||
|
||||
##
|
||||
# Equality operator for filters, useful primarily for constructing unit tests.
|
||||
def ==(filter)
|
||||
# 20100320 AZ: We need to come up with a better way of doing this. This
|
||||
# is just nasty.
|
||||
str = "[@op,@left,@right]"
|
||||
self.instance_eval(str) == filter.instance_eval(str)
|
||||
end
|
||||
|
||||
def to_raw_rfc2254
|
||||
case @op
|
||||
when :ne
|
||||
"!(#{@left}=#{@right})"
|
||||
when :eq
|
||||
"#{@left}=#{@right}"
|
||||
when :ex
|
||||
"#{@left}:=#{@right}"
|
||||
when :ge
|
||||
"#{@left}>=#{@right}"
|
||||
when :le
|
||||
"#{@left}<=#{@right}"
|
||||
when :and
|
||||
"&(#{@left.to_raw_rfc2254})(#{@right.to_raw_rfc2254})"
|
||||
when :or
|
||||
"|(#{@left.to_raw_rfc2254})(#{@right.to_raw_rfc2254})"
|
||||
when :not
|
||||
"!(#{@left.to_raw_rfc2254})"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Converts the Filter object to an RFC 2254-compatible text format.
|
||||
def to_rfc2254
|
||||
"(#{to_raw_rfc2254})"
|
||||
end
|
||||
|
||||
def to_s
|
||||
to_rfc2254
|
||||
end
|
||||
|
||||
##
|
||||
# Converts the filter to BER format.
|
||||
#--
|
||||
# Filter ::=
|
||||
# CHOICE {
|
||||
# and [0] SET OF Filter,
|
||||
# or [1] SET OF Filter,
|
||||
# not [2] Filter,
|
||||
# equalityMatch [3] AttributeValueAssertion,
|
||||
# substrings [4] SubstringFilter,
|
||||
# greaterOrEqual [5] AttributeValueAssertion,
|
||||
# lessOrEqual [6] AttributeValueAssertion,
|
||||
# present [7] AttributeType,
|
||||
# approxMatch [8] AttributeValueAssertion,
|
||||
# extensibleMatch [9] MatchingRuleAssertion
|
||||
# }
|
||||
#
|
||||
# SubstringFilter ::=
|
||||
# SEQUENCE {
|
||||
# type AttributeType,
|
||||
# SEQUENCE OF CHOICE {
|
||||
# initial [0] LDAPString,
|
||||
# any [1] LDAPString,
|
||||
# final [2] LDAPString
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# MatchingRuleAssertion ::=
|
||||
# SEQUENCE {
|
||||
# matchingRule [1] MatchingRuleId OPTIONAL,
|
||||
# type [2] AttributeDescription OPTIONAL,
|
||||
# matchValue [3] AssertionValue,
|
||||
# dnAttributes [4] BOOLEAN DEFAULT FALSE
|
||||
# }
|
||||
#
|
||||
# Matching Rule Suffixes
|
||||
# Less than [.1] or .[lt]
|
||||
# Less than or equal to [.2] or [.lte]
|
||||
# Equality [.3] or [.eq] (default)
|
||||
# Greater than or equal to [.4] or [.gte]
|
||||
# Greater than [.5] or [.gt]
|
||||
# Substring [.6] or [.sub]
|
||||
#
|
||||
#++
|
||||
def to_ber
|
||||
case @op
|
||||
when :eq
|
||||
if @right == "*" # presence test
|
||||
@left.to_s.to_ber_contextspecific(7)
|
||||
elsif @right =~ /[*]/ # substring
|
||||
# Parsing substrings is a little tricky. We use String#split to
|
||||
# break a string into substrings delimited by the * (star)
|
||||
# character. But we also need to know whether there is a star at the
|
||||
# head and tail of the string, so we use a limit parameter value of
|
||||
# -1: "If negative, there is no limit to the number of fields
|
||||
# returned, and trailing null fields are not suppressed."
|
||||
#
|
||||
# 20100320 AZ: This is much simpler than the previous verison. Also,
|
||||
# unnecessary regex escaping has been removed.
|
||||
|
||||
ary = @right.split(/[*]+/, -1)
|
||||
|
||||
if ary.first.empty?
|
||||
first = nil
|
||||
ary.shift
|
||||
else
|
||||
first = ary.shift.to_ber_contextspecific(0)
|
||||
end
|
||||
|
||||
if ary.last.empty?
|
||||
last = nil
|
||||
ary.pop
|
||||
else
|
||||
last = ary.pop.to_ber_contextspecific(2)
|
||||
end
|
||||
|
||||
seq = ary.map { |e| e.to_ber_contextspecific(1) }
|
||||
seq.unshift first if first
|
||||
seq.push last if last
|
||||
|
||||
[@left.to_s.to_ber, seq.to_ber].to_ber_contextspecific(4)
|
||||
else # equality
|
||||
[@left.to_s.to_ber, unescape(@right).to_ber].to_ber_contextspecific(3)
|
||||
end
|
||||
when :ex
|
||||
seq = []
|
||||
|
||||
unless @left =~ /^([-;\w]*)(:dn)?(:(\w+|[.\w]+))?$/
|
||||
raise Net::LDAP::LdapError, "Bad attribute #{@left}"
|
||||
end
|
||||
type, dn, rule = $1, $2, $4
|
||||
|
||||
seq << rule.to_ber_contextspecific(1) unless rule.to_s.empty? # matchingRule
|
||||
seq << type.to_ber_contextspecific(2) unless type.to_s.empty? # type
|
||||
seq << unescape(@right).to_ber_contextspecific(3) # matchingValue
|
||||
seq << "1".to_ber_contextspecific(4) unless dn.to_s.empty? # dnAttributes
|
||||
|
||||
seq.to_ber_contextspecific(9)
|
||||
when :ge
|
||||
[@left.to_s.to_ber, unescape(@right).to_ber].to_ber_contextspecific(5)
|
||||
when :le
|
||||
[@left.to_s.to_ber, unescape(@right).to_ber].to_ber_contextspecific(6)
|
||||
when :ne
|
||||
[self.class.eq(@left, @right).to_ber].to_ber_contextspecific(2)
|
||||
when :and
|
||||
ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
|
||||
ary.map {|a| a.to_ber}.to_ber_contextspecific(0)
|
||||
when :or
|
||||
ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
|
||||
ary.map {|a| a.to_ber}.to_ber_contextspecific(1)
|
||||
when :not
|
||||
[@left.to_ber].to_ber_contextspecific(2)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Perform filter operations against a user-supplied block. This is useful
|
||||
# when implementing an LDAP directory server. The caller's block will be
|
||||
# called with two arguments: first, a symbol denoting the "operation" of
|
||||
# the filter; and second, an array consisting of arguments to the
|
||||
# operation. The user-supplied block (which is MANDATORY) should perform
|
||||
# some desired application-defined processing, and may return a
|
||||
# locally-meaningful object that will appear as a parameter in the :and,
|
||||
# :or and :not operations detailed below.
|
||||
#
|
||||
# A typical object to return from the user-supplied block is an array of
|
||||
# Net::LDAP::Filter objects.
|
||||
#
|
||||
# These are the possible values that may be passed to the user-supplied
|
||||
# block:
|
||||
# * :equalityMatch (the arguments will be an attribute name and a value
|
||||
# to be matched);
|
||||
# * :substrings (two arguments: an attribute name and a value containing
|
||||
# one or more "*" characters);
|
||||
# * :present (one argument: an attribute name);
|
||||
# * :greaterOrEqual (two arguments: an attribute name and a value to be
|
||||
# compared against);
|
||||
# * :lessOrEqual (two arguments: an attribute name and a value to be
|
||||
# compared against);
|
||||
# * :and (two or more arguments, each of which is an object returned
|
||||
# from a recursive call to #execute, with the same block;
|
||||
# * :or (two or more arguments, each of which is an object returned from
|
||||
# a recursive call to #execute, with the same block; and
|
||||
# * :not (one argument, which is an object returned from a recursive
|
||||
# call to #execute with the the same block.
|
||||
def execute(&block)
|
||||
case @op
|
||||
when :eq
|
||||
if @right == "*"
|
||||
yield :present, @left
|
||||
elsif @right.index '*'
|
||||
yield :substrings, @left, @right
|
||||
else
|
||||
yield :equalityMatch, @left, @right
|
||||
end
|
||||
when :ge
|
||||
yield :greaterOrEqual, @left, @right
|
||||
when :le
|
||||
yield :lessOrEqual, @left, @right
|
||||
when :or, :and
|
||||
yield @op, (@left.execute(&block)), (@right.execute(&block))
|
||||
when :not
|
||||
yield @op, (@left.execute(&block))
|
||||
end || []
|
||||
end
|
||||
|
||||
##
|
||||
# This is a private helper method for dealing with chains of ANDs and ORs
|
||||
# that are longer than two. If BOTH of our branches are of the specified
|
||||
# type of joining operator, then return both of them as an array (calling
|
||||
# coalesce recursively). If they're not, then return an array consisting
|
||||
# only of self.
|
||||
def coalesce(operator) #:nodoc:
|
||||
if @op == operator
|
||||
[@left.coalesce(operator), @right.coalesce(operator)]
|
||||
else
|
||||
[self]
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
#--
|
||||
# We got a hash of attribute values.
|
||||
# Do we match the attributes?
|
||||
# Return T/F, and call match recursively as necessary.
|
||||
#++
|
||||
def match(entry)
|
||||
case @op
|
||||
when :eq
|
||||
if @right == "*"
|
||||
l = entry[@left] and l.length > 0
|
||||
else
|
||||
l = entry[@left] and l = Array(l) and l.index(@right)
|
||||
end
|
||||
else
|
||||
raise Net::LDAP::LdapError, "Unknown filter type in match: #{@op}"
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Converts escaped characters (e.g., "\\28") to unescaped characters
|
||||
# ("(").
|
||||
def unescape(right)
|
||||
right.gsub(/\\([a-fA-F\d]{2})/) { [$1.hex].pack("U") }
|
||||
end
|
||||
private :unescape
|
||||
|
||||
##
|
||||
# Parses RFC 2254-style string representations of LDAP filters into Filter
|
||||
# object hierarchies.
|
||||
class FilterParser #:nodoc:
|
||||
##
|
||||
# The constructed filter.
|
||||
attr_reader :filter
|
||||
|
||||
class << self
|
||||
private :new
|
||||
|
||||
##
|
||||
# Construct a filter tree from the provided string and return it.
|
||||
def parse(ldap_filter_string)
|
||||
new(ldap_filter_string).filter
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(str)
|
||||
require 'strscan' # Don't load strscan until we need it.
|
||||
@filter = parse(StringScanner.new(str))
|
||||
raise Net::LDAP::LdapError, "Invalid filter syntax." unless @filter
|
||||
end
|
||||
|
||||
##
|
||||
# Parse the string contained in the StringScanner provided. Parsing
|
||||
# tries to parse a standalone expression first. If that fails, it tries
|
||||
# to parse a parenthesized expression.
|
||||
def parse(scanner)
|
||||
parse_filter_branch(scanner) or parse_paren_expression(scanner)
|
||||
end
|
||||
private :parse
|
||||
|
||||
##
|
||||
# Join ("&") and intersect ("|") operations are presented in branches.
|
||||
# That is, the expression <tt>(&(test1)(test2)</tt> has two branches:
|
||||
# test1 and test2. Each of these is parsed separately and then pushed
|
||||
# into a branch array for filter merging using the parent operation.
|
||||
#
|
||||
# This method parses the branch text out into an array of filter
|
||||
# objects.
|
||||
def parse_branches(scanner)
|
||||
branches = []
|
||||
while branch = parse_paren_expression(scanner)
|
||||
branches << branch
|
||||
end
|
||||
branches
|
||||
end
|
||||
private :parse_branches
|
||||
|
||||
##
|
||||
# Join ("&") and intersect ("|") operations are presented in branches.
|
||||
# That is, the expression <tt>(&(test1)(test2)</tt> has two branches:
|
||||
# test1 and test2. Each of these is parsed separately and then pushed
|
||||
# into a branch array for filter merging using the parent operation.
|
||||
#
|
||||
# This method calls #parse_branches to generate the branch list and then
|
||||
# merges them into a single Filter tree by calling the provided
|
||||
# operation.
|
||||
def merge_branches(op, scanner)
|
||||
filter = nil
|
||||
branches = parse_branches(scanner)
|
||||
|
||||
if branches.size >= 1
|
||||
filter = branches.shift
|
||||
while not branches.empty?
|
||||
filter = filter.__send__(op, branches.shift)
|
||||
end
|
||||
end
|
||||
|
||||
filter
|
||||
end
|
||||
private :merge_branches
|
||||
|
||||
def parse_paren_expression(scanner)
|
||||
if scanner.scan(/\s*\(\s*/)
|
||||
expr = if scanner.scan(/\s*\&\s*/)
|
||||
merge_branches(:&, scanner)
|
||||
elsif scanner.scan(/\s*\|\s*/)
|
||||
merge_branches(:|, scanner)
|
||||
elsif scanner.scan(/\s*\!\s*/)
|
||||
br = parse_paren_expression(scanner)
|
||||
~br if br
|
||||
else
|
||||
parse_filter_branch(scanner)
|
||||
end
|
||||
|
||||
if expr and scanner.scan(/\s*\)\s*/)
|
||||
expr
|
||||
end
|
||||
end
|
||||
end
|
||||
private :parse_paren_expression
|
||||
|
||||
##
|
||||
# This parses a given expression inside of parentheses.
|
||||
def parse_filter_branch(scanner)
|
||||
scanner.scan(/\s*/)
|
||||
if token = scanner.scan(/[-\w:.]*[\w]/)
|
||||
scanner.scan(/\s*/)
|
||||
if op = scanner.scan(/<=|>=|!=|:=|=/)
|
||||
scanner.scan(/\s*/)
|
||||
if value = scanner.scan(/(?:[-\w*.+@=,#\$%&!'\s]|\\[a-fA-F\d]{2})+/)
|
||||
# 20100313 AZ: Assumes that "(uid=george*)" is the same as
|
||||
# "(uid=george* )". The standard doesn't specify, but I can find
|
||||
# no examples that suggest otherwise.
|
||||
value.strip!
|
||||
case op
|
||||
when "="
|
||||
Net::LDAP::Filter.eq(token, value)
|
||||
when "!="
|
||||
Net::LDAP::Filter.ne(token, value)
|
||||
when "<="
|
||||
Net::LDAP::Filter.le(token, value)
|
||||
when ">="
|
||||
Net::LDAP::Filter.ge(token, value)
|
||||
when ":="
|
||||
Net::LDAP::Filter.ex(token, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
private :parse_filter_branch
|
||||
end # class Net::LDAP::FilterParser
|
||||
end # class Net::LDAP::Filter
|
|
@ -0,0 +1,31 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
require 'digest/sha1'
|
||||
require 'digest/md5'
|
||||
|
||||
class Net::LDAP::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)
|
||||
digest, digest_name = case type
|
||||
when :md5
|
||||
[Digest::MD5.new, 'MD5']
|
||||
when :sha
|
||||
[Digest::SHA1.new, 'SHA']
|
||||
else
|
||||
raise Net::LDAP::LdapError, "Unsupported password-hash type (#{type})"
|
||||
end
|
||||
digest << str.to_s
|
||||
return "{#{digest_name}}#{[digest.digest].pack('m').chomp }"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,256 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
require 'ostruct'
|
||||
|
||||
##
|
||||
# Defines the Protocol Data Unit (PDU) for LDAP. An LDAP PDU always looks
|
||||
# like a BER SEQUENCE with at least two elements: an INTEGER message ID
|
||||
# number and an application-specific SEQUENCE. Some LDAPv3 packets also
|
||||
# include an optional third element, a sequence of "controls" (see RFC 2251
|
||||
# section 4.1.12 for more information).
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Currently, we only support controls on SearchResult.
|
||||
class Net::LDAP::PDU
|
||||
class Error < RuntimeError; end
|
||||
|
||||
##
|
||||
# This message packet is a bind request.
|
||||
BindRequest = 0
|
||||
BindResult = 1
|
||||
UnbindRequest = 2
|
||||
SearchRequest = 3
|
||||
SearchReturnedData = 4
|
||||
SearchResult = 5
|
||||
ModifyResponse = 7
|
||||
AddResponse = 9
|
||||
DeleteResponse = 11
|
||||
ModifyRDNResponse = 13
|
||||
SearchResultReferral = 19
|
||||
ExtendedRequest = 23
|
||||
ExtendedResponse = 24
|
||||
|
||||
##
|
||||
# The LDAP packet message ID.
|
||||
attr_reader :message_id
|
||||
alias_method :msg_id, :message_id
|
||||
|
||||
##
|
||||
# The application protocol format tag.
|
||||
attr_reader :app_tag
|
||||
|
||||
attr_reader :search_entry
|
||||
attr_reader :search_referrals
|
||||
attr_reader :search_parameters
|
||||
attr_reader :bind_parameters
|
||||
|
||||
##
|
||||
# Returns RFC-2251 Controls if any.
|
||||
attr_reader :ldap_controls
|
||||
alias_method :result_controls, :ldap_controls
|
||||
# Messy. Does this functionality belong somewhere else?
|
||||
|
||||
def initialize(ber_object)
|
||||
begin
|
||||
@message_id = ber_object[0].to_i
|
||||
# Grab the bottom five bits of the identifier so we know which type of
|
||||
# PDU this is.
|
||||
#
|
||||
# This is safe enough in LDAP-land, but it is recommended that other
|
||||
# approaches be taken for other protocols in the case that there's an
|
||||
# app-specific tag that has both primitive and constructed forms.
|
||||
@app_tag = ber_object[1].ber_identifier & 0x1f
|
||||
@ldap_controls = []
|
||||
rescue Exception => ex
|
||||
raise Net::LDAP::PDU::Error, "LDAP PDU Format Error: #{ex.message}"
|
||||
end
|
||||
|
||||
case @app_tag
|
||||
when BindResult
|
||||
parse_bind_response(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])
|
||||
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])
|
||||
when SearchRequest
|
||||
parse_ldap_search_request(ber_object[1])
|
||||
when BindRequest
|
||||
parse_bind_request(ber_object[1])
|
||||
when UnbindRequest
|
||||
parse_unbind_request(ber_object[1])
|
||||
when ExtendedResponse
|
||||
parse_ldap_result(ber_object[1])
|
||||
else
|
||||
raise LdapPduError.new("unknown pdu-type: #{@app_tag}")
|
||||
end
|
||||
|
||||
parse_controls(ber_object[2]) if ber_object[2]
|
||||
end
|
||||
|
||||
##
|
||||
# Returns a hash which (usually) defines the members :resultCode,
|
||||
# :errorMessage, and :matchedDN. These values come directly from an LDAP
|
||||
# response packet returned by the remote peer. Also see #result_code.
|
||||
def result
|
||||
@ldap_result || {}
|
||||
end
|
||||
|
||||
##
|
||||
# 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 serverSaslCreds, which are only present in BindResponse packets.
|
||||
#--
|
||||
# Messy. Does this functionality belong somewhere else? We ought to
|
||||
# refactor the accessors of this class before they get any kludgier.
|
||||
def result_server_sasl_creds
|
||||
@ldap_result && @ldap_result[:serverSaslCreds]
|
||||
end
|
||||
|
||||
def parse_ldap_result(sequence)
|
||||
sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP result length."
|
||||
@ldap_result = {
|
||||
:resultCode => sequence[0],
|
||||
:matchedDN => sequence[1],
|
||||
:errorMessage => sequence[2]
|
||||
}
|
||||
end
|
||||
private :parse_ldap_result
|
||||
|
||||
##
|
||||
# A Bind Response may have an additional field, ID [7], serverSaslCreds,
|
||||
# per RFC 2251 pgh 4.2.3.
|
||||
def parse_bind_response(sequence)
|
||||
sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP Bind Response length."
|
||||
parse_ldap_result(sequence)
|
||||
@ldap_result[:serverSaslCreds] = sequence[3] if sequence.length >= 4
|
||||
@ldap_result
|
||||
end
|
||||
private :parse_bind_response
|
||||
|
||||
# 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.
|
||||
def parse_search_return(sequence)
|
||||
sequence.length >= 2 or raise Net::LDAP::PDU::Error, "Invalid Search Response length."
|
||||
@search_entry = Net::LDAP::Entry.new(sequence[0])
|
||||
sequence[1].each { |seq| @search_entry[seq[0]] = seq[1] }
|
||||
end
|
||||
private :parse_search_return
|
||||
|
||||
##
|
||||
# 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
|
||||
private :parse_search_referral
|
||||
|
||||
##
|
||||
# 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
|
||||
|
||||
# (provisional, must document)
|
||||
def parse_ldap_search_request(sequence)
|
||||
s = OpenStruct.new
|
||||
s.base_object, s.scope, s.deref_aliases, s.size_limit, s.time_limit,
|
||||
s.types_only, s.filter, s.attributes = sequence
|
||||
@search_parameters = s
|
||||
end
|
||||
private :parse_ldap_search_request
|
||||
|
||||
# (provisional, must document)
|
||||
def parse_bind_request sequence
|
||||
s = OpenStruct.new
|
||||
s.version, s.name, s.authentication = sequence
|
||||
@bind_parameters = s
|
||||
end
|
||||
private :parse_bind_request
|
||||
|
||||
# (provisional, must document)
|
||||
# UnbindRequest has no content so this is a no-op.
|
||||
def parse_unbind_request(sequence)
|
||||
nil
|
||||
end
|
||||
private :parse_unbind_request
|
||||
end
|
||||
|
||||
module Net
|
||||
##
|
||||
# Handle renamed constants Net::LdapPdu (Net::LDAP::PDU) and
|
||||
# Net::LdapPduError (Net::LDAP::PDU::Error).
|
||||
def self.const_missing(name) #:nodoc:
|
||||
case name.to_s
|
||||
when "LdapPdu"
|
||||
warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU instead."
|
||||
Net::LDAP::PDU
|
||||
when "LdapPduError"
|
||||
warn "Net::#{name} has been deprecated. Use Net::LDAP::PDU::Error instead."
|
||||
Net::LDAP::PDU::Error
|
||||
when 'LDAP'
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end # module Net
|
|
@ -0,0 +1,268 @@
|
|||
# -*- ruby encoding: utf-8 -*-
|
||||
# :stopdoc:
|
||||
module Net
|
||||
class SNMP
|
||||
VERSION = '0.2.2'
|
||||
|
||||
AsnSyntax = Net::BER.compile_syntax({
|
||||
:application => {
|
||||
:primitive => {
|
||||
1 => :integer, # Counter32, (RFC2578 sec 2)
|
||||
2 => :integer, # Gauge32 or Unsigned32, (RFC2578 sec 2)
|
||||
3 => :integer # TimeTicks32, (RFC2578 sec 2)
|
||||
},
|
||||
:constructed => {
|
||||
}
|
||||
},
|
||||
:context_specific => {
|
||||
:primitive => {
|
||||
},
|
||||
:constructed => {
|
||||
0 => :array, # GetRequest PDU (RFC1157 pgh 4.1.2)
|
||||
1 => :array, # GetNextRequest PDU (RFC1157 pgh 4.1.3)
|
||||
2 => :array # GetResponse PDU (RFC1157 pgh 4.1.4)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
# SNMP 32-bit counter.
|
||||
# Defined in RFC1155 (Structure of Mangement Information), section 6.
|
||||
# A 32-bit counter is an ASN.1 application [1] implicit unsigned integer
|
||||
# with a range from 0 to 2^^32 - 1.
|
||||
class Counter32
|
||||
def initialize value
|
||||
@value = value
|
||||
end
|
||||
def to_ber
|
||||
@value.to_ber_application(1)
|
||||
end
|
||||
end
|
||||
|
||||
# SNMP 32-bit gauge.
|
||||
# Defined in RFC1155 (Structure of Mangement Information), section 6.
|
||||
# A 32-bit counter is an ASN.1 application [2] implicit unsigned integer.
|
||||
# This is also indistinguishable from Unsigned32. (Need to alias them.)
|
||||
class Gauge32
|
||||
def initialize value
|
||||
@value = value
|
||||
end
|
||||
def to_ber
|
||||
@value.to_ber_application(2)
|
||||
end
|
||||
end
|
||||
|
||||
# SNMP 32-bit timer-ticks.
|
||||
# Defined in RFC1155 (Structure of Mangement Information), section 6.
|
||||
# A 32-bit counter is an ASN.1 application [3] implicit unsigned integer.
|
||||
class TimeTicks32
|
||||
def initialize value
|
||||
@value = value
|
||||
end
|
||||
def to_ber
|
||||
@value.to_ber_application(3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class SnmpPdu
|
||||
class Error < StandardError; end
|
||||
|
||||
PduTypes = [
|
||||
:get_request,
|
||||
:get_next_request,
|
||||
:get_response,
|
||||
:set_request,
|
||||
:trap
|
||||
]
|
||||
ErrorStatusCodes = { # Per RFC1157, pgh 4.1.1
|
||||
0 => "noError",
|
||||
1 => "tooBig",
|
||||
2 => "noSuchName",
|
||||
3 => "badValue",
|
||||
4 => "readOnly",
|
||||
5 => "genErr"
|
||||
}
|
||||
|
||||
class << self
|
||||
def parse ber_object
|
||||
n = new
|
||||
n.send :parse, ber_object
|
||||
n
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :version, :community, :pdu_type, :variables, :error_status
|
||||
attr_accessor :request_id, :error_index
|
||||
|
||||
|
||||
def initialize args={}
|
||||
@version = args[:version] || 0
|
||||
@community = args[:community] || "public"
|
||||
@pdu_type = args[:pdu_type] # leave nil unless specified; there's no reasonable default value.
|
||||
@error_status = args[:error_status] || 0
|
||||
@error_index = args[:error_index] || 0
|
||||
@variables = args[:variables] || []
|
||||
end
|
||||
|
||||
#--
|
||||
def parse ber_object
|
||||
begin
|
||||
parse_ber_object ber_object
|
||||
rescue Error
|
||||
# Pass through any SnmpPdu::Error instances
|
||||
raise $!
|
||||
rescue
|
||||
# Wrap any basic parsing error so it becomes a PDU-format error
|
||||
raise Error.new( "snmp-pdu format error" )
|
||||
end
|
||||
end
|
||||
private :parse
|
||||
|
||||
def parse_ber_object ber_object
|
||||
send :version=, ber_object[0].to_i
|
||||
send :community=, ber_object[1].to_s
|
||||
|
||||
data = ber_object[2]
|
||||
case (app_tag = data.ber_identifier & 31)
|
||||
when 0
|
||||
send :pdu_type=, :get_request
|
||||
parse_get_request data
|
||||
when 1
|
||||
send :pdu_type=, :get_next_request
|
||||
# This PDU is identical to get-request except for the type.
|
||||
parse_get_request data
|
||||
when 2
|
||||
send :pdu_type=, :get_response
|
||||
# This PDU is identical to get-request except for the type,
|
||||
# the error_status and error_index values are meaningful,
|
||||
# and the fact that the variable bindings will be non-null.
|
||||
parse_get_response data
|
||||
else
|
||||
raise Error.new( "unknown snmp-pdu type: #{app_tag}" )
|
||||
end
|
||||
end
|
||||
private :parse_ber_object
|
||||
|
||||
#--
|
||||
# Defined in RFC1157, pgh 4.1.2.
|
||||
def parse_get_request data
|
||||
send :request_id=, data[0].to_i
|
||||
# data[1] is error_status, always zero.
|
||||
# data[2] is error_index, always zero.
|
||||
send :error_status=, 0
|
||||
send :error_index=, 0
|
||||
data[3].each {|n,v|
|
||||
# A variable-binding, of which there may be several,
|
||||
# consists of an OID and a BER null.
|
||||
# We're ignoring the null, we might want to verify it instead.
|
||||
unless v.is_a?(Net::BER::BerIdentifiedNull)
|
||||
raise Error.new(" invalid variable-binding in get-request" )
|
||||
end
|
||||
add_variable_binding n, nil
|
||||
}
|
||||
end
|
||||
private :parse_get_request
|
||||
|
||||
#--
|
||||
# Defined in RFC1157, pgh 4.1.4
|
||||
def parse_get_response data
|
||||
send :request_id=, data[0].to_i
|
||||
send :error_status=, data[1].to_i
|
||||
send :error_index=, data[2].to_i
|
||||
data[3].each {|n,v|
|
||||
# A variable-binding, of which there may be several,
|
||||
# consists of an OID and a BER null.
|
||||
# We're ignoring the null, we might want to verify it instead.
|
||||
add_variable_binding n, v
|
||||
}
|
||||
end
|
||||
private :parse_get_response
|
||||
|
||||
|
||||
def version= ver
|
||||
unless [0,2].include?(ver)
|
||||
raise Error.new("unknown snmp-version: #{ver}")
|
||||
end
|
||||
@version = ver
|
||||
end
|
||||
|
||||
def pdu_type= t
|
||||
unless PduTypes.include?(t)
|
||||
raise Error.new("unknown pdu-type: #{t}")
|
||||
end
|
||||
@pdu_type = t
|
||||
end
|
||||
|
||||
def error_status= es
|
||||
unless ErrorStatusCodes.has_key?(es)
|
||||
raise Error.new("unknown error-status: #{es}")
|
||||
end
|
||||
@error_status = es
|
||||
end
|
||||
|
||||
def community= c
|
||||
@community = c.to_s
|
||||
end
|
||||
|
||||
#--
|
||||
# Syntactic sugar
|
||||
def add_variable_binding name, value=nil
|
||||
@variables ||= []
|
||||
@variables << [name, value]
|
||||
end
|
||||
|
||||
def to_ber_string
|
||||
[
|
||||
version.to_ber,
|
||||
community.to_ber,
|
||||
pdu_to_ber_string
|
||||
].to_ber_sequence
|
||||
end
|
||||
|
||||
#--
|
||||
# Helper method that returns a PDU payload in BER form,
|
||||
# depending on the PDU type.
|
||||
def pdu_to_ber_string
|
||||
case pdu_type
|
||||
when :get_request
|
||||
[
|
||||
request_id.to_ber,
|
||||
error_status.to_ber,
|
||||
error_index.to_ber,
|
||||
[
|
||||
@variables.map {|n,v|
|
||||
[n.to_ber_oid, Net::BER::BerIdentifiedNull.new.to_ber].to_ber_sequence
|
||||
}
|
||||
].to_ber_sequence
|
||||
].to_ber_contextspecific(0)
|
||||
when :get_next_request
|
||||
[
|
||||
request_id.to_ber,
|
||||
error_status.to_ber,
|
||||
error_index.to_ber,
|
||||
[
|
||||
@variables.map {|n,v|
|
||||
[n.to_ber_oid, Net::BER::BerIdentifiedNull.new.to_ber].to_ber_sequence
|
||||
}
|
||||
].to_ber_sequence
|
||||
].to_ber_contextspecific(1)
|
||||
when :get_response
|
||||
[
|
||||
request_id.to_ber,
|
||||
error_status.to_ber,
|
||||
error_index.to_ber,
|
||||
[
|
||||
@variables.map {|n,v|
|
||||
[n.to_ber_oid, v.to_ber].to_ber_sequence
|
||||
}
|
||||
].to_ber_sequence
|
||||
].to_ber_contextspecific(2)
|
||||
else
|
||||
raise Error.new( "unknown pdu-type: #{pdu_type}" )
|
||||
end
|
||||
end
|
||||
private :pdu_to_ber_string
|
||||
|
||||
end
|
||||
end
|
||||
# :startdoc:
|
|
@ -0,0 +1,59 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = %q{net-ldap}
|
||||
s.version = "0.2.20110317223538"
|
||||
|
||||
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
||||
s.authors = ["Francis Cianfrocca", "Emiel van de Laar", "Rory O'Connell", "Kaspar Schiess", "Austin Ziegler"]
|
||||
s.date = %q{2011-03-17}
|
||||
s.description = %q{Net::LDAP for Ruby (also called net-ldap) implements client access for the
|
||||
Lightweight Directory Access Protocol (LDAP), an IETF standard protocol for
|
||||
accessing distributed directory services. Net::LDAP is written completely in
|
||||
Ruby with no external dependencies. It supports most LDAP client features and a
|
||||
subset of server features as well.
|
||||
|
||||
Net::LDAP has been tested against modern popular LDAP servers including
|
||||
OpenLDAP and Active Directory. The current release is mostly compliant with
|
||||
earlier versions of the IETF LDAP RFCs (2251–2256, 2829–2830, 3377, and 3771).
|
||||
Our roadmap for Net::LDAP 1.0 is to gain full <em>client</em> compliance with
|
||||
the most recent LDAP RFCs (4510–4519, plutions of 4520–4532).}
|
||||
s.email = ["blackhedd@rubyforge.org", "gemiel@gmail.com", "rory.ocon@gmail.com", "kaspar.schiess@absurd.li", "austin@rubyforge.org"]
|
||||
s.extra_rdoc_files = ["Manifest.txt", "Contributors.rdoc", "Hacking.rdoc", "History.rdoc", "License.rdoc", "README.rdoc"]
|
||||
s.files = [".autotest", ".rspec", "Contributors.rdoc", "Hacking.rdoc", "History.rdoc", "License.rdoc", "Manifest.txt", "README.rdoc", "Rakefile", "autotest/discover.rb", "lib/net-ldap.rb", "lib/net/ber.rb", "lib/net/ber/ber_parser.rb", "lib/net/ber/core_ext.rb", "lib/net/ber/core_ext/array.rb", "lib/net/ber/core_ext/bignum.rb", "lib/net/ber/core_ext/false_class.rb", "lib/net/ber/core_ext/fixnum.rb", "lib/net/ber/core_ext/string.rb", "lib/net/ber/core_ext/true_class.rb", "lib/net/ldap.rb", "lib/net/ldap/dataset.rb", "lib/net/ldap/dn.rb", "lib/net/ldap/entry.rb", "lib/net/ldap/filter.rb", "lib/net/ldap/password.rb", "lib/net/ldap/pdu.rb", "lib/net/snmp.rb", "net-ldap.gemspec", "spec/integration/ssl_ber_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/unit/ber/ber_spec.rb", "spec/unit/ber/core_ext/string_spec.rb", "spec/unit/ldap/dn_spec.rb", "spec/unit/ldap/entry_spec.rb", "spec/unit/ldap/filter_spec.rb", "spec/unit/ldap_spec.rb", "test/common.rb", "test/test_entry.rb", "test/test_filter.rb", "test/test_ldap_connection.rb", "test/test_ldif.rb", "test/test_password.rb", "test/test_rename.rb", "test/test_snmp.rb", "test/testdata.ldif", "testserver/ldapserver.rb", "testserver/testdata.ldif", ".gemtest"]
|
||||
s.homepage = %q{http://net-ldap.rubyforge.org/}
|
||||
s.rdoc_options = ["--main", "README.rdoc"]
|
||||
s.require_paths = ["lib"]
|
||||
s.required_ruby_version = Gem::Requirement.new(">= 1.8.7")
|
||||
s.rubyforge_project = %q{net-ldap}
|
||||
s.rubygems_version = %q{1.5.2}
|
||||
s.summary = %q{Net::LDAP for Ruby (also called net-ldap) implements client access for the Lightweight Directory Access Protocol (LDAP), an IETF standard protocol for accessing distributed directory services}
|
||||
s.test_files = ["test/test_entry.rb", "test/test_filter.rb", "test/test_ldap_connection.rb", "test/test_ldif.rb", "test/test_password.rb", "test/test_rename.rb", "test/test_snmp.rb"]
|
||||
|
||||
if s.respond_to? :specification_version then
|
||||
s.specification_version = 3
|
||||
|
||||
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
||||
s.add_development_dependency(%q<hoe-git>, ["~> 1"])
|
||||
s.add_development_dependency(%q<hoe-gemspec>, ["~> 1"])
|
||||
s.add_development_dependency(%q<metaid>, ["~> 1"])
|
||||
s.add_development_dependency(%q<flexmock>, ["~> 0.9.0"])
|
||||
s.add_development_dependency(%q<rspec>, ["~> 2.0"])
|
||||
s.add_development_dependency(%q<hoe>, [">= 2.9.1"])
|
||||
else
|
||||
s.add_dependency(%q<hoe-git>, ["~> 1"])
|
||||
s.add_dependency(%q<hoe-gemspec>, ["~> 1"])
|
||||
s.add_dependency(%q<metaid>, ["~> 1"])
|
||||
s.add_dependency(%q<flexmock>, ["~> 0.9.0"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.0"])
|
||||
s.add_dependency(%q<hoe>, [">= 2.9.1"])
|
||||
end
|
||||
else
|
||||
s.add_dependency(%q<hoe-git>, ["~> 1"])
|
||||
s.add_dependency(%q<hoe-gemspec>, ["~> 1"])
|
||||
s.add_dependency(%q<metaid>, ["~> 1"])
|
||||
s.add_dependency(%q<flexmock>, ["~> 0.9.0"])
|
||||
s.add_dependency(%q<rspec>, ["~> 2.0"])
|
||||
s.add_dependency(%q<hoe>, [">= 2.9.1"])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
require 'spec_helper'
|
||||
|
||||
require 'net/ldap'
|
||||
|
||||
describe "BER serialisation (SSL)" do
|
||||
# Transmits str to #to and reads it back from #from.
|
||||
#
|
||||
def transmit(str)
|
||||
to.write(str)
|
||||
to.close
|
||||
|
||||
from.read
|
||||
end
|
||||
|
||||
attr_reader :to, :from
|
||||
before(:each) do
|
||||
@from, @to = IO.pipe
|
||||
|
||||
# The production code operates on sockets, which do need #connect called
|
||||
# on them to work. Pipes are more robust for this test, so we'll skip
|
||||
# the #connect call since it fails.
|
||||
flexmock(OpenSSL::SSL::SSLSocket).
|
||||
new_instances.should_receive(:connect => nil)
|
||||
|
||||
@to = Net::LDAP::Connection.wrap_with_ssl(to)
|
||||
@from = Net::LDAP::Connection.wrap_with_ssl(from)
|
||||
end
|
||||
|
||||
it "should transmit strings" do
|
||||
transmit('foo').should == 'foo'
|
||||
end
|
||||
it "should correctly transmit numbers" do
|
||||
to.write 1234.to_ber
|
||||
from.read_ber.should == 1234
|
||||
end
|
||||
end
|
|
@ -0,0 +1,2 @@
|
|||
--format specdoc
|
||||
--colour
|
|
@ -0,0 +1,5 @@
|
|||
require 'net/ldap'
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.mock_with :flexmock
|
||||
end
|
|
@ -0,0 +1,94 @@
|
|||
require 'spec_helper'
|
||||
|
||||
require 'net/ber'
|
||||
require 'net/ldap'
|
||||
|
||||
describe "BER encoding of" do
|
||||
|
||||
RSpec::Matchers.define :properly_encode_and_decode do
|
||||
match do |given|
|
||||
given.to_ber.read_ber.should == given
|
||||
end
|
||||
end
|
||||
|
||||
context "arrays" do
|
||||
it "should properly encode/decode []" do
|
||||
[].should properly_encode_and_decode
|
||||
end
|
||||
it "should properly encode/decode [1,2,3]" do
|
||||
ary = [1,2,3]
|
||||
encoded_ary = ary.map { |el| el.to_ber }.to_ber
|
||||
|
||||
encoded_ary.read_ber.should == ary
|
||||
end
|
||||
end
|
||||
context "booleans" do
|
||||
it "should encode true" do
|
||||
true.to_ber.should == "\x01\x01\x01"
|
||||
end
|
||||
it "should encode false" do
|
||||
false.to_ber.should == "\x01\x01\x00"
|
||||
end
|
||||
end
|
||||
context "numbers" do
|
||||
# Sample based
|
||||
{
|
||||
0 => "\x02\x01\x00",
|
||||
1 => "\x02\x01\x01",
|
||||
127 => "\x02\x01\x7F",
|
||||
128 => "\x02\x01\x80",
|
||||
255 => "\x02\x01\xFF",
|
||||
256 => "\x02\x02\x01\x00",
|
||||
65535 => "\x02\x02\xFF\xFF",
|
||||
65536 => "\x02\x03\x01\x00\x00",
|
||||
16_777_215 => "\x02\x03\xFF\xFF\xFF",
|
||||
0x01000000 => "\x02\x04\x01\x00\x00\x00",
|
||||
0x3FFFFFFF => "\x02\x04\x3F\xFF\xFF\xFF",
|
||||
0x4FFFFFFF => "\x02\x04\x4F\xFF\xFF\xFF",
|
||||
|
||||
# Some odd samples...
|
||||
5 => "\002\001\005",
|
||||
500 => "\002\002\001\364",
|
||||
50_000 => "\x02\x02\xC3P",
|
||||
5_000_000_000 => "\002\005\001*\005\362\000"
|
||||
}.each do |number, expected_encoding|
|
||||
it "should encode #{number} as #{expected_encoding.inspect}" do
|
||||
number.to_ber.should == expected_encoding
|
||||
end
|
||||
end
|
||||
|
||||
# Round-trip encoding: This is mostly to be sure to cover Bignums well.
|
||||
context "when decoding with #read_ber" do
|
||||
it "should correctly handle powers of two" do
|
||||
100.times do |p|
|
||||
n = 2 << p
|
||||
|
||||
n.should properly_encode_and_decode
|
||||
end
|
||||
end
|
||||
it "should correctly handle powers of ten" do
|
||||
100.times do |p|
|
||||
n = 5 * 10**p
|
||||
|
||||
n.should properly_encode_and_decode
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "BER decoding of" do
|
||||
context "numbers" do
|
||||
it "should decode #{"\002\001\006".inspect} (6)" do
|
||||
"\002\001\006".read_ber(Net::LDAP::AsnSyntax).should == 6
|
||||
end
|
||||
it "should decode #{"\004\007testing".inspect} ('testing')" do
|
||||
"\004\007testing".read_ber(Net::LDAP::AsnSyntax).should == 'testing'
|
||||
end
|
||||
it "should decode an ldap bind request" do
|
||||
"0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus".
|
||||
read_ber(Net::LDAP::AsnSyntax).should ==
|
||||
[1, [3, "Administrator", "ad_is_bogus"]]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
require 'spec_helper'
|
||||
require 'metaid'
|
||||
|
||||
describe String, "when extended with BER core extensions" do
|
||||
describe "<- #read_ber! (consuming read_ber method)" do
|
||||
context "when passed an ldap bind request and some extra data" do
|
||||
attr_reader :str, :result
|
||||
before(:each) do
|
||||
@str = "0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus UNCONSUMED"
|
||||
@result = str.read_ber!(Net::LDAP::AsnSyntax)
|
||||
end
|
||||
|
||||
it "should correctly parse the ber message" do
|
||||
result.should == [1, [3, "Administrator", "ad_is_bogus"]]
|
||||
end
|
||||
it "should leave unconsumed part of message in place" do
|
||||
str.should == " UNCONSUMED"
|
||||
end
|
||||
|
||||
context "if an exception occurs during #read_ber" do
|
||||
attr_reader :initial_value
|
||||
before(:each) do
|
||||
stub_exception_class = Class.new(StandardError)
|
||||
|
||||
@initial_value = "0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus"
|
||||
@str = initial_value.dup
|
||||
|
||||
# Defines a string
|
||||
io = StringIO.new(initial_value)
|
||||
io.meta_def :read_ber do |syntax|
|
||||
read
|
||||
raise stub_exception_class
|
||||
end
|
||||
flexmock(StringIO).should_receive(:new).and_return(io)
|
||||
|
||||
begin
|
||||
str.read_ber!(Net::LDAP::AsnSyntax)
|
||||
rescue stub_exception_class
|
||||
# EMPTY ON PURPOSE
|
||||
else
|
||||
raise "The stub code should raise an exception!"
|
||||
end
|
||||
end
|
||||
|
||||
it "should not modify string" do
|
||||
str.should == initial_value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,80 @@
|
|||
require 'spec_helper'
|
||||
require 'net/ldap/dn'
|
||||
|
||||
describe Net::LDAP::DN do
|
||||
describe "<- .construct" do
|
||||
attr_reader :dn
|
||||
|
||||
before(:each) do
|
||||
@dn = Net::LDAP::DN.new('cn', ',+"\\<>;', 'ou=company')
|
||||
end
|
||||
|
||||
it "should construct a Net::LDAP::DN" do
|
||||
dn.should be_an_instance_of(Net::LDAP::DN)
|
||||
end
|
||||
|
||||
it "should escape all the required characters" do
|
||||
dn.to_s.should == 'cn=\\,\\+\\"\\\\\\<\\>\\;,ou=company'
|
||||
end
|
||||
end
|
||||
|
||||
describe "<- .to_a" do
|
||||
context "parsing" do
|
||||
{
|
||||
'cn=James, ou=Company\\,\\20LLC' => ['cn','James','ou','Company, LLC'],
|
||||
'cn = \ James , ou = "Comp\28ny" ' => ['cn',' James','ou','Comp(ny'],
|
||||
'1.23.4= #A3B4D5 ,ou=Company' => ['1.23.4','#A3B4D5','ou','Company'],
|
||||
}.each do |key, value|
|
||||
context "(#{key})" do
|
||||
attr_reader :dn
|
||||
|
||||
before(:each) do
|
||||
@dn = Net::LDAP::DN.new(key)
|
||||
end
|
||||
|
||||
it "should decode into a Net::LDAP::DN" do
|
||||
dn.should be_an_instance_of(Net::LDAP::DN)
|
||||
end
|
||||
|
||||
it "should return the correct array" do
|
||||
dn.to_a.should == value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "parsing bad input" do
|
||||
[
|
||||
'cn=James,',
|
||||
'cn=#aa aa',
|
||||
'cn="James',
|
||||
'cn=J\ames',
|
||||
'cn=\\',
|
||||
'1.2.d=Value',
|
||||
'd1.2=Value',
|
||||
].each do |value|
|
||||
context "(#{value})" do
|
||||
attr_reader :dn
|
||||
|
||||
before(:each) do
|
||||
@dn = Net::LDAP::DN.new(value)
|
||||
end
|
||||
|
||||
it "should decode into a Net::LDAP::DN" do
|
||||
dn.should be_an_instance_of(Net::LDAP::DN)
|
||||
end
|
||||
|
||||
it "should raise an error on parsing" do
|
||||
lambda { dn.to_a }.should raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "<- .escape(str)" do
|
||||
it "should escape ,, +, \", \\, <, >, and ;" do
|
||||
Net::LDAP::DN.escape(',+"\\<>;').should == '\\,\\+\\"\\\\\\<\\>\\;'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,51 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Net::LDAP::Entry do
|
||||
attr_reader :entry
|
||||
before(:each) do
|
||||
@entry = Net::LDAP::Entry.from_single_ldif_string(
|
||||
%Q{dn: something
|
||||
foo: foo
|
||||
barAttribute: bar
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
describe "entry access" do
|
||||
it "should always respond to #dn" do
|
||||
entry.should respond_to(:dn)
|
||||
end
|
||||
|
||||
context "<- #foo" do
|
||||
it "should respond_to?" do
|
||||
entry.should respond_to(:foo)
|
||||
end
|
||||
it "should return 'foo'" do
|
||||
entry.foo.should == ['foo']
|
||||
end
|
||||
end
|
||||
context "<- #Foo" do
|
||||
it "should respond_to?" do
|
||||
entry.should respond_to(:Foo)
|
||||
end
|
||||
it "should return 'foo'" do
|
||||
entry.foo.should == ['foo']
|
||||
end
|
||||
end
|
||||
context "<- #foo=" do
|
||||
it "should respond_to?" do
|
||||
entry.should respond_to(:foo=)
|
||||
end
|
||||
it "should set 'foo'" do
|
||||
entry.foo= 'bar'
|
||||
entry.foo.should == ['bar']
|
||||
end
|
||||
end
|
||||
context "<- #fOo=" do
|
||||
it "should return 'foo'" do
|
||||
entry.fOo= 'bar'
|
||||
entry.fOo.should == ['bar']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,84 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Net::LDAP::Filter do
|
||||
describe "<- .ex(attr, value)" do
|
||||
context "('foo', 'bar')" do
|
||||
attr_reader :filter
|
||||
before(:each) do
|
||||
@filter = Net::LDAP::Filter.ex('foo', 'bar')
|
||||
end
|
||||
it "should convert to 'foo:=bar'" do
|
||||
filter.to_s.should == '(foo:=bar)'
|
||||
end
|
||||
it "should survive roundtrip via to_s/from_rfc2254" do
|
||||
Net::LDAP::Filter.from_rfc2254(filter.to_s).should == filter
|
||||
end
|
||||
it "should survive roundtrip conversion to/from ber" do
|
||||
ber = filter.to_ber
|
||||
Net::LDAP::Filter.parse_ber(ber.read_ber(Net::LDAP::AsnSyntax)).should ==
|
||||
filter
|
||||
end
|
||||
end
|
||||
context "various legal inputs" do
|
||||
[
|
||||
'(o:dn:=Ace Industry)',
|
||||
'(:dn:2.4.8.10:=Dino)',
|
||||
'(cn:dn:1.2.3.4.5:=John Smith)',
|
||||
'(sn:dn:2.4.6.8.10:=Barbara Jones)',
|
||||
'(&(sn:dn:2.4.6.8.10:=Barbara Jones))'
|
||||
].each do |filter_str|
|
||||
context "from_rfc2254(#{filter_str.inspect})" do
|
||||
attr_reader :filter
|
||||
before(:each) do
|
||||
@filter = Net::LDAP::Filter.from_rfc2254(filter_str)
|
||||
end
|
||||
|
||||
it "should decode into a Net::LDAP::Filter" do
|
||||
filter.should be_an_instance_of(Net::LDAP::Filter)
|
||||
end
|
||||
it "should survive roundtrip conversion to/from ber" do
|
||||
ber = filter.to_ber
|
||||
Net::LDAP::Filter.parse_ber(ber.read_ber(Net::LDAP::AsnSyntax)).should ==
|
||||
filter
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
describe "<- .construct" do
|
||||
it "should accept apostrophes in filters (regression)" do
|
||||
Net::LDAP::Filter.construct("uid=O'Keefe").to_rfc2254.should == "(uid=O'Keefe)"
|
||||
end
|
||||
end
|
||||
|
||||
describe "convenience filter constructors" do
|
||||
def eq(attribute, value)
|
||||
described_class.eq(attribute, value)
|
||||
end
|
||||
describe "<- .equals(attr, val)" do
|
||||
it "should delegate to .eq with escaping" do
|
||||
described_class.equals('dn', 'f*oo').should == eq('dn', 'f\2Aoo')
|
||||
end
|
||||
end
|
||||
describe "<- .begins(attr, val)" do
|
||||
it "should delegate to .eq with escaping" do
|
||||
described_class.begins('dn', 'f*oo').should == eq('dn', 'f\2Aoo*')
|
||||
end
|
||||
end
|
||||
describe "<- .ends(attr, val)" do
|
||||
it "should delegate to .eq with escaping" do
|
||||
described_class.ends('dn', 'f*oo').should == eq('dn', '*f\2Aoo')
|
||||
end
|
||||
end
|
||||
describe "<- .contains(attr, val)" do
|
||||
it "should delegate to .eq with escaping" do
|
||||
described_class.contains('dn', 'f*oo').should == eq('dn', '*f\2Aoo*')
|
||||
end
|
||||
end
|
||||
end
|
||||
describe "<- .escape(str)" do
|
||||
it "should escape nul, *, (, ) and \\" do
|
||||
Net::LDAP::Filter.escape("\0*()\\").should == "\\00\\2A\\28\\29\\5C"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Net::LDAP::Connection do
|
||||
describe "initialize" do
|
||||
context "when host is not responding" do
|
||||
before(:each) do
|
||||
flexmock(TCPSocket).
|
||||
should_receive(:new).and_raise(Errno::ECONNREFUSED)
|
||||
end
|
||||
|
||||
it "should raise LdapError" do
|
||||
lambda {
|
||||
Net::LDAP::Connection.new(
|
||||
:server => 'test.mocked.com',
|
||||
:port => 636)
|
||||
}.should raise_error(Net::LDAP::LdapError)
|
||||
end
|
||||
end
|
||||
context "when host is blocking the port" do
|
||||
before(:each) do
|
||||
flexmock(TCPSocket).
|
||||
should_receive(:new).and_raise(SocketError)
|
||||
end
|
||||
|
||||
it "should raise LdapError" do
|
||||
lambda {
|
||||
Net::LDAP::Connection.new(
|
||||
:server => 'test.mocked.com',
|
||||
:port => 636)
|
||||
}.should raise_error(Net::LDAP::LdapError)
|
||||
end
|
||||
end
|
||||
context "on other exceptions" do
|
||||
before(:each) do
|
||||
flexmock(TCPSocket).
|
||||
should_receive(:new).and_raise(NameError)
|
||||
end
|
||||
|
||||
it "should rethrow the exception" do
|
||||
lambda {
|
||||
Net::LDAP::Connection.new(
|
||||
:server => 'test.mocked.com',
|
||||
:port => 636)
|
||||
}.should raise_error(NameError)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
# Add 'lib' to load path.
|
||||
require 'test/unit'
|
||||
require 'net/ldap'
|
|
@ -0,0 +1,59 @@
|
|||
require 'common'
|
||||
|
||||
=begin
|
||||
class TestEntry < Test::Unit::TestCase
|
||||
Commented out until I can make it a spec.
|
||||
context "An instance of Entry" do
|
||||
setup do
|
||||
@entry = Net::LDAP::Entry.new 'cn=Barbara,o=corp'
|
||||
end
|
||||
|
||||
should "be initialized with the DN" do
|
||||
assert_equal 'cn=Barbara,o=corp', @entry.dn
|
||||
end
|
||||
|
||||
should 'return an empty array when accessing a nonexistent attribute (index lookup)' do
|
||||
assert_equal [], @entry['sn']
|
||||
end
|
||||
|
||||
should 'return an empty array when accessing a nonexistent attribute (method call)' do
|
||||
assert_equal [], @entry.sn
|
||||
end
|
||||
|
||||
should 'create an attribute on assignment (index lookup)' do
|
||||
@entry['sn'] = 'Jensen'
|
||||
assert_equal ['Jensen'], @entry['sn']
|
||||
end
|
||||
|
||||
should 'create an attribute on assignment (method call)' do
|
||||
@entry.sn = 'Jensen'
|
||||
assert_equal ['Jensen'], @entry.sn
|
||||
end
|
||||
|
||||
should 'have attributes accessible by index lookup' do
|
||||
@entry['sn'] = 'Jensen'
|
||||
assert_equal ['Jensen'], @entry['sn']
|
||||
end
|
||||
|
||||
should 'have attributes accessible using a Symbol as the index' do
|
||||
@entry[:sn] = 'Jensen'
|
||||
assert_equal ['Jensen'], @entry[:sn]
|
||||
end
|
||||
|
||||
should 'have attributes accessible by method call' do
|
||||
@entry['sn'] = 'Jensen'
|
||||
assert_equal ['Jensen'], @entry.sn
|
||||
end
|
||||
|
||||
should 'ignore case of attribute names' do
|
||||
@entry['sn'] = 'Jensen'
|
||||
assert_equal ['Jensen'], @entry.sn
|
||||
assert_equal ['Jensen'], @entry.Sn
|
||||
assert_equal ['Jensen'], @entry.SN
|
||||
assert_equal ['Jensen'], @entry['sn']
|
||||
assert_equal ['Jensen'], @entry['Sn']
|
||||
assert_equal ['Jensen'], @entry['SN']
|
||||
end
|
||||
end
|
||||
end
|
||||
=end
|
|
@ -0,0 +1,122 @@
|
|||
require 'common'
|
||||
|
||||
class TestFilter < Test::Unit::TestCase
|
||||
Filter = Net::LDAP::Filter
|
||||
|
||||
def test_bug_7534_rfc2254
|
||||
assert_equal("(cn=Tim Wizard)",
|
||||
Filter.from_rfc2254("(cn=Tim Wizard)").to_rfc2254)
|
||||
end
|
||||
|
||||
def test_invalid_filter_string
|
||||
assert_raises(Net::LDAP::LdapError) { Filter.from_rfc2254("") }
|
||||
end
|
||||
|
||||
def test_invalid_filter
|
||||
assert_raises(Net::LDAP::LdapError) {
|
||||
# This test exists to prove that our constructor blocks unknown filter
|
||||
# types. All filters must be constructed using helpers.
|
||||
Filter.__send__(:new, :xx, nil, nil)
|
||||
}
|
||||
end
|
||||
|
||||
def test_to_s
|
||||
assert_equal("(uid=george *)", Filter.eq("uid", "george *").to_s)
|
||||
end
|
||||
|
||||
def test_convenience_filters
|
||||
assert_equal("(uid=\\2A)", Filter.equals("uid", "*").to_s)
|
||||
assert_equal("(uid=\\28*)", Filter.begins("uid", "(").to_s)
|
||||
assert_equal("(uid=*\\29)", Filter.ends("uid", ")").to_s)
|
||||
assert_equal("(uid=*\\5C*)", Filter.contains("uid", "\\").to_s)
|
||||
end
|
||||
|
||||
def test_c2
|
||||
assert_equal("(uid=george *)",
|
||||
Filter.from_rfc2254("uid=george *").to_rfc2254)
|
||||
assert_equal("(uid:=george *)",
|
||||
Filter.from_rfc2254("uid:=george *").to_rfc2254)
|
||||
assert_equal("(uid=george*)",
|
||||
Filter.from_rfc2254(" ( uid = george* ) ").to_rfc2254)
|
||||
assert_equal("(!(uid=george*))",
|
||||
Filter.from_rfc2254("uid!=george*").to_rfc2254)
|
||||
assert_equal("(uid<=george*)",
|
||||
Filter.from_rfc2254("uid <= george*").to_rfc2254)
|
||||
assert_equal("(uid>=george*)",
|
||||
Filter.from_rfc2254("uid>=george*").to_rfc2254)
|
||||
assert_equal("(&(uid=george*)(mail=*))",
|
||||
Filter.from_rfc2254("(& (uid=george* ) (mail=*))").to_rfc2254)
|
||||
assert_equal("(|(uid=george*)(mail=*))",
|
||||
Filter.from_rfc2254("(| (uid=george* ) (mail=*))").to_rfc2254)
|
||||
assert_equal("(!(mail=*))",
|
||||
Filter.from_rfc2254("(! (mail=*))").to_rfc2254)
|
||||
end
|
||||
|
||||
def test_filter_with_single_clause
|
||||
assert_equal("(cn=name)", Net::LDAP::Filter.construct("(&(cn=name))").to_s)
|
||||
end
|
||||
|
||||
def test_filters_from_ber
|
||||
[
|
||||
Net::LDAP::Filter.eq("objectclass", "*"),
|
||||
Net::LDAP::Filter.pres("objectclass"),
|
||||
Net::LDAP::Filter.eq("objectclass", "ou"),
|
||||
Net::LDAP::Filter.ge("uid", "500"),
|
||||
Net::LDAP::Filter.le("uid", "500"),
|
||||
(~ Net::LDAP::Filter.pres("objectclass")),
|
||||
(Net::LDAP::Filter.pres("objectclass") & Net::LDAP::Filter.pres("ou")),
|
||||
(Net::LDAP::Filter.pres("objectclass") & Net::LDAP::Filter.pres("ou") & Net::LDAP::Filter.pres("sn")),
|
||||
(Net::LDAP::Filter.pres("objectclass") | Net::LDAP::Filter.pres("ou") | Net::LDAP::Filter.pres("sn")),
|
||||
|
||||
Net::LDAP::Filter.eq("objectclass", "*aaa"),
|
||||
Net::LDAP::Filter.eq("objectclass", "*aaa*bbb"),
|
||||
Net::LDAP::Filter.eq("objectclass", "*aaa*bbb*ccc"),
|
||||
Net::LDAP::Filter.eq("objectclass", "aaa*bbb"),
|
||||
Net::LDAP::Filter.eq("objectclass", "aaa*bbb*ccc"),
|
||||
Net::LDAP::Filter.eq("objectclass", "abc*def*1111*22*g"),
|
||||
Net::LDAP::Filter.eq("objectclass", "*aaa*"),
|
||||
Net::LDAP::Filter.eq("objectclass", "*aaa*bbb*"),
|
||||
Net::LDAP::Filter.eq("objectclass", "*aaa*bbb*ccc*"),
|
||||
Net::LDAP::Filter.eq("objectclass", "aaa*"),
|
||||
Net::LDAP::Filter.eq("objectclass", "aaa*bbb*"),
|
||||
Net::LDAP::Filter.eq("objectclass", "aaa*bbb*ccc*"),
|
||||
].each do |ber|
|
||||
f = Net::LDAP::Filter.parse_ber(ber.to_ber.read_ber(Net::LDAP::AsnSyntax))
|
||||
assert(f == ber)
|
||||
assert_equal(f.to_ber, ber.to_ber)
|
||||
end
|
||||
end
|
||||
|
||||
def test_ber_from_rfc2254_filter
|
||||
[
|
||||
Net::LDAP::Filter.construct("objectclass=*"),
|
||||
Net::LDAP::Filter.construct("objectclass=ou"),
|
||||
Net::LDAP::Filter.construct("uid >= 500"),
|
||||
Net::LDAP::Filter.construct("uid <= 500"),
|
||||
Net::LDAP::Filter.construct("(!(uid=*))"),
|
||||
Net::LDAP::Filter.construct("(&(uid=*)(objectclass=*))"),
|
||||
Net::LDAP::Filter.construct("(&(uid=*)(objectclass=*)(sn=*))"),
|
||||
Net::LDAP::Filter.construct("(|(uid=*)(objectclass=*))"),
|
||||
Net::LDAP::Filter.construct("(|(uid=*)(objectclass=*)(sn=*))"),
|
||||
|
||||
Net::LDAP::Filter.construct("objectclass=*aaa"),
|
||||
Net::LDAP::Filter.construct("objectclass=*aaa*bbb"),
|
||||
Net::LDAP::Filter.construct("objectclass=*aaa bbb"),
|
||||
Net::LDAP::Filter.construct("objectclass=*aaa bbb"),
|
||||
Net::LDAP::Filter.construct("objectclass=*aaa*bbb*ccc"),
|
||||
Net::LDAP::Filter.construct("objectclass=aaa*bbb"),
|
||||
Net::LDAP::Filter.construct("objectclass=aaa*bbb*ccc"),
|
||||
Net::LDAP::Filter.construct("objectclass=abc*def*1111*22*g"),
|
||||
Net::LDAP::Filter.construct("objectclass=*aaa*"),
|
||||
Net::LDAP::Filter.construct("objectclass=*aaa*bbb*"),
|
||||
Net::LDAP::Filter.construct("objectclass=*aaa*bbb*ccc*"),
|
||||
Net::LDAP::Filter.construct("objectclass=aaa*"),
|
||||
Net::LDAP::Filter.construct("objectclass=aaa*bbb*"),
|
||||
Net::LDAP::Filter.construct("objectclass=aaa*bbb*ccc*"),
|
||||
].each do |ber|
|
||||
f = Net::LDAP::Filter.parse_ber(ber.to_ber.read_ber(Net::LDAP::AsnSyntax))
|
||||
assert(f == ber)
|
||||
assert_equal(f.to_ber, ber.to_ber)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
require 'common'
|
||||
|
||||
class TestLDAP < Test::Unit::TestCase
|
||||
def test_modify_ops_delete
|
||||
args = { :operations => [ [ :delete, "mail" ] ] }
|
||||
result = Net::LDAP::Connection.modify_ops(args[:operations])
|
||||
expected = [ "0\r\n\x01\x010\b\x04\x04mail1\x00" ]
|
||||
assert_equal(expected, result)
|
||||
end
|
||||
|
||||
def test_modify_ops_add
|
||||
args = { :operations => [ [ :add, "mail", "testuser@example.com" ] ] }
|
||||
result = Net::LDAP::Connection.modify_ops(args[:operations])
|
||||
expected = [ "0#\n\x01\x000\x1E\x04\x04mail1\x16\x04\x14testuser@example.com" ]
|
||||
assert_equal(expected, result)
|
||||
end
|
||||
|
||||
def test_modify_ops_replace
|
||||
args = { :operations =>[ [ :replace, "mail", "testuser@example.com" ] ] }
|
||||
result = Net::LDAP::Connection.modify_ops(args[:operations])
|
||||
expected = [ "0#\n\x01\x020\x1E\x04\x04mail1\x16\x04\x14testuser@example.com" ]
|
||||
assert_equal(expected, result)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,67 @@
|
|||
# $Id: testldif.rb 61 2006-04-18 20:55:55Z blackhedd $
|
||||
|
||||
require 'common'
|
||||
|
||||
require 'digest/sha1'
|
||||
require 'base64'
|
||||
|
||||
class TestLdif < Test::Unit::TestCase
|
||||
TestLdifFilename = "#{File.dirname(__FILE__)}/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(Digest::SHA1.digest(psw)).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
|
||||
|
||||
# Must test folded lines and base64-encoded lines as well as normal ones.
|
||||
def test_to_ldif
|
||||
data = File.open(TestLdifFilename, "rb") { |f| f.read }
|
||||
io = StringIO.new(data)
|
||||
|
||||
# added .lines to turn to array because 1.9 doesn't have
|
||||
# .grep on basic strings
|
||||
entries = data.lines.grep(/^dn:\s*/) { $'.chomp }
|
||||
dn_entries = entries.dup
|
||||
|
||||
ds = Net::LDAP::Dataset::read_ldif(io) { |type, value|
|
||||
case type
|
||||
when :dn
|
||||
assert_equal(dn_entries.first, value)
|
||||
dn_entries.shift
|
||||
end
|
||||
}
|
||||
assert_equal(entries.size, ds.size)
|
||||
assert_equal(entries.sort, ds.to_ldif.grep(/^dn:\s*/) { $'.chomp })
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# $Id: testpsw.rb 72 2006-04-24 21:58:14Z blackhedd $
|
||||
|
||||
require 'common'
|
||||
|
||||
class TestPassword < Test::Unit::TestCase
|
||||
|
||||
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
|
|
@ -0,0 +1,77 @@
|
|||
require 'common'
|
||||
|
||||
# Commented out since it assumes you have a live LDAP server somewhere. This
|
||||
# will be migrated to the integration specs, as soon as they are ready.
|
||||
=begin
|
||||
class TestRename < Test::Unit::TestCase
|
||||
HOST= '10.10.10.71'
|
||||
PORT = 389
|
||||
BASE = "o=test"
|
||||
AUTH = { :method => :simple, :username => "cn=testadmin,#{BASE}", :password => 'password' }
|
||||
BASIC_USER = "cn=jsmith,ou=sales,#{BASE}"
|
||||
RENAMED_USER = "cn=jbrown,ou=sales,#{BASE}"
|
||||
MOVED_USER = "cn=jsmith,ou=marketing,#{BASE}"
|
||||
RENAMED_MOVED_USER = "cn=jjones,ou=marketing,#{BASE}"
|
||||
|
||||
def setup
|
||||
# create the entries we're going to manipulate
|
||||
Net::LDAP::open(:host => HOST, :port => PORT, :auth => AUTH) do |ldap|
|
||||
if ldap.add(:dn => "ou=sales,#{BASE}", :attributes => { :ou => "sales", :objectclass => "organizationalUnit" })
|
||||
puts "Add failed: #{ldap.get_operation_result.message} - code: #{ldap.get_operation_result.code}"
|
||||
end
|
||||
ldap.add(:dn => "ou=marketing,#{BASE}", :attributes => { :ou => "marketing", :objectclass => "organizationalUnit" })
|
||||
ldap.add(:dn => BASIC_USER, :attributes => { :cn => "jsmith", :objectclass => "inetOrgPerson", :sn => "Smith" })
|
||||
end
|
||||
end
|
||||
|
||||
def test_rename_entry
|
||||
dn = nil
|
||||
Net::LDAP::open(:host => HOST, :port => PORT, :auth => AUTH) do |ldap|
|
||||
ldap.rename(:olddn => BASIC_USER, :newrdn => "cn=jbrown")
|
||||
|
||||
ldap.search(:base => RENAMED_USER) do |entry|
|
||||
dn = entry.dn
|
||||
end
|
||||
end
|
||||
assert_equal(RENAMED_USER, dn)
|
||||
end
|
||||
|
||||
def test_move_entry
|
||||
dn = nil
|
||||
Net::LDAP::open(:host => HOST, :port => PORT, :auth => AUTH) do |ldap|
|
||||
ldap.rename(:olddn => BASIC_USER, :newrdn => "cn=jsmith", :new_superior => "ou=marketing,#{BASE}")
|
||||
|
||||
ldap.search(:base => MOVED_USER) do |entry|
|
||||
dn = entry.dn
|
||||
end
|
||||
end
|
||||
assert_equal(MOVED_USER, dn)
|
||||
end
|
||||
|
||||
def test_move_and_rename_entry
|
||||
dn = nil
|
||||
Net::LDAP::open(:host => HOST, :port => PORT, :auth => AUTH) do |ldap|
|
||||
ldap.rename(:olddn => BASIC_USER, :newrdn => "cn=jjones", :new_superior => "ou=marketing,#{BASE}")
|
||||
|
||||
ldap.search(:base => RENAMED_MOVED_USER) do |entry|
|
||||
dn = entry.dn
|
||||
end
|
||||
end
|
||||
assert_equal(RENAMED_MOVED_USER, dn)
|
||||
end
|
||||
|
||||
def teardown
|
||||
# delete the entries
|
||||
# note: this doesn't always completely clear up on eDirectory as objects get locked while
|
||||
# the rename/move is being completed on the server and this prevents the delete from happening
|
||||
Net::LDAP::open(:host => HOST, :port => PORT, :auth => AUTH) do |ldap|
|
||||
ldap.delete(:dn => BASIC_USER)
|
||||
ldap.delete(:dn => RENAMED_USER)
|
||||
ldap.delete(:dn => MOVED_USER)
|
||||
ldap.delete(:dn => RENAMED_MOVED_USER)
|
||||
ldap.delete(:dn => "ou=sales,#{BASE}")
|
||||
ldap.delete(:dn => "ou=marketing,#{BASE}")
|
||||
end
|
||||
end
|
||||
end
|
||||
=end
|
|
@ -0,0 +1,114 @@
|
|||
# $Id: testsnmp.rb 231 2006-12-21 15:09:29Z blackhedd $
|
||||
|
||||
require 'common'
|
||||
require 'net/snmp'
|
||||
|
||||
class TestSnmp < Test::Unit::TestCase
|
||||
SnmpGetRequest = "0'\002\001\000\004\006public\240\032\002\002?*\002\001\000\002\001\0000\0160\f\006\b+\006\001\002\001\001\001\000\005\000"
|
||||
SnmpGetResponse = "0+\002\001\000\004\006public\242\036\002\002'\017\002\001\000\002\001\0000\0220\020\006\b+\006\001\002\001\001\001\000\004\004test"
|
||||
|
||||
SnmpGetRequestXXX = "0'\002\001\000\004\006xxxxxx\240\032\002\002?*\002\001\000\002\001\0000\0160\f\006\b+\006\001\002\001\001\001\000\005\000"
|
||||
|
||||
def test_invalid_packet
|
||||
data = "xxxx"
|
||||
assert_raise(Net::BER::BerError) {
|
||||
ary = data.read_ber(Net::SNMP::AsnSyntax)
|
||||
}
|
||||
end
|
||||
|
||||
# The method String#read_ber! added by Net::BER consumes a well-formed BER
|
||||
# object from the head of a string. If it doesn't find a complete,
|
||||
# well-formed BER object, it returns nil and leaves the string unchanged.
|
||||
# If it finds an object, it returns the object and removes it from the
|
||||
# head of the string. This is good for handling partially-received data
|
||||
# streams, such as from network connections.
|
||||
def _test_consume_string
|
||||
data = "xxx"
|
||||
assert_equal(nil, data.read_ber!)
|
||||
assert_equal("xxx", data)
|
||||
|
||||
data = SnmpGetRequest + "!!!"
|
||||
ary = data.read_ber!(Net::SNMP::AsnSyntax)
|
||||
assert_equal("!!!", data)
|
||||
assert ary.is_a?(Array)
|
||||
assert ary.is_a?(Net::BER::BerIdentifiedArray)
|
||||
end
|
||||
|
||||
def test_weird_packet
|
||||
assert_raise(Net::SnmpPdu::Error) {
|
||||
Net::SnmpPdu.parse("aaaaaaaaaaaaaa")
|
||||
}
|
||||
end
|
||||
|
||||
def test_get_request
|
||||
data = SnmpGetRequest.dup
|
||||
pkt = data.read_ber(Net::SNMP::AsnSyntax)
|
||||
assert pkt.is_a?(Net::BER::BerIdentifiedArray)
|
||||
assert_equal(48, pkt.ber_identifier) # Constructed [0], signifies GetRequest
|
||||
|
||||
pdu = Net::SnmpPdu.parse(pkt)
|
||||
assert_equal(:get_request, pdu.pdu_type)
|
||||
assert_equal(16170, pdu.request_id) # whatever was in the test data. 16170 is not magic.
|
||||
assert_equal([[[1, 3, 6, 1, 2, 1, 1, 1, 0], nil]], pdu.variables)
|
||||
|
||||
assert_equal(pdu.to_ber_string, SnmpGetRequest)
|
||||
end
|
||||
|
||||
def test_empty_pdu
|
||||
pdu = Net::SnmpPdu.new
|
||||
assert_raise(Net::SnmpPdu::Error) { pdu.to_ber_string }
|
||||
end
|
||||
|
||||
def test_malformations
|
||||
pdu = Net::SnmpPdu.new
|
||||
pdu.version = 0
|
||||
pdu.version = 2
|
||||
assert_raise(Net::SnmpPdu::Error) { pdu.version = 100 }
|
||||
|
||||
pdu.pdu_type = :get_request
|
||||
pdu.pdu_type = :get_next_request
|
||||
pdu.pdu_type = :get_response
|
||||
pdu.pdu_type = :set_request
|
||||
pdu.pdu_type = :trap
|
||||
assert_raise(Net::SnmpPdu::Error) { pdu.pdu_type = :something_else }
|
||||
end
|
||||
|
||||
def test_make_response
|
||||
pdu = Net::SnmpPdu.new
|
||||
pdu.version = 0
|
||||
pdu.community = "public"
|
||||
pdu.pdu_type = :get_response
|
||||
pdu.request_id = 9999
|
||||
pdu.error_status = 0
|
||||
pdu.error_index = 0
|
||||
pdu.add_variable_binding [1, 3, 6, 1, 2, 1, 1, 1, 0], "test"
|
||||
|
||||
assert_equal(SnmpGetResponse, pdu.to_ber_string)
|
||||
end
|
||||
|
||||
def test_make_bad_response
|
||||
pdu = Net::SnmpPdu.new
|
||||
assert_raise(Net::SnmpPdu::Error) {pdu.to_ber_string}
|
||||
pdu.pdu_type = :get_response
|
||||
pdu.request_id = 999
|
||||
pdu.to_ber_string
|
||||
# Not specifying variables doesn't create an error. (Maybe it should?)
|
||||
end
|
||||
|
||||
def test_snmp_integers
|
||||
c32 = Net::SNMP::Counter32.new(100)
|
||||
assert_equal("A\001d", c32.to_ber)
|
||||
g32 = Net::SNMP::Gauge32.new(100)
|
||||
assert_equal("B\001d", g32.to_ber)
|
||||
t32 = Net::SNMP::TimeTicks32.new(100)
|
||||
assert_equal("C\001d", t32.to_ber)
|
||||
end
|
||||
|
||||
def test_community
|
||||
data = SnmpGetRequestXXX.dup
|
||||
ary = data.read_ber(Net::SNMP::AsnSyntax)
|
||||
pdu = Net::SnmpPdu.parse(ary)
|
||||
assert_equal("xxxxxx", pdu.community)
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,210 @@
|
|||
# $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
|
||||
|
||||
LdapServerAsnSyntax = {
|
||||
: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
|
||||
send_ldap_response 1, pdu[0].to_i, 2, "", "We only support version 3"
|
||||
elsif pdu[1][1] != "cn=bigshot,dc=bayshorenetworks,dc=com"
|
||||
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] != "opensesame"
|
||||
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 {|a| a.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 {|dn, entry|
|
||||
if filter.match( entry )
|
||||
attrs = []
|
||||
entry.each {|k, v|
|
||||
if requested_attrs == :all or requested_attrs.include?(k.downcase)
|
||||
attrvals = v.map {|v1| v1.to_ber}.to_ber_set
|
||||
attrs << [k.to_ber, attrvals].to_ber_sequence
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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 = File.readlines( "./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] ||= []
|
||||
hash[dn][$1.downcase] << $'
|
||||
end
|
||||
end
|
||||
end
|
||||
hash
|
||||
end
|
||||
|
||||
|
||||
#------------------------------------------------
|
||||
|
||||
if __FILE__ == $0
|
||||
|
||||
require 'rubygems'
|
||||
require 'eventmachine'
|
||||
|
||||
require 'logger'
|
||||
$logger = Logger.new $stderr
|
||||
|
||||
$logger.info "adding ../lib to loadpath, to pick up dev version of Net::LDAP."
|
||||
$:.unshift "../lib"
|
||||
|
||||
$ldif = load_test_data
|
||||
|
||||
require 'net/ldap'
|
||||
|
||||
EventMachine.run {
|
||||
$logger.info "starting LDAP server on 127.0.0.1 port 3890"
|
||||
EventMachine.start_server "127.0.0.1", 3890, LdapServer
|
||||
EventMachine.add_periodic_timer 60, proc {$logger.info "heartbeat"}
|
||||
}
|
||||
end
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
# $Id$
|
||||
#
|
||||
# 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
|
||||
|
|
@ -1,272 +0,0 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street,
|
||||
Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and
|
||||
distribute verbatim copies of this license document, but changing it is not
|
||||
allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your freedom to
|
||||
share and change it. By contrast, the GNU General Public License is
|
||||
intended to guarantee your freedom to share and change free software--to
|
||||
make sure the software is free for all its users. This General Public
|
||||
License applies to most of the Free Software Foundation's software and to
|
||||
any other program whose authors commit to using it. (Some other Free
|
||||
Software Foundation software is covered by the GNU Lesser General Public
|
||||
License instead.) You can apply it to your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our
|
||||
General Public Licenses are designed to make sure that you have the freedom
|
||||
to distribute copies of free software (and charge for this service if you
|
||||
wish), that you receive source code or can get it if you want it, that you
|
||||
can change the software or use pieces of it in new free programs; and that
|
||||
you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid anyone to
|
||||
deny you these rights or to ask you to surrender the rights. These
|
||||
restrictions translate to certain responsibilities for you if you distribute
|
||||
copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether gratis or
|
||||
for a fee, you must give the recipients all the rights that you have. You
|
||||
must make sure that they, too, receive or can get the source code. And you
|
||||
must show them these terms so they know their rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and (2)
|
||||
offer you this license which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain that
|
||||
everyone understands that there is no warranty for this free software. If
|
||||
the software is modified by someone else and passed on, we want its
|
||||
recipients to know that what they have is not the original, so that any
|
||||
problems introduced by others will not reflect on the original authors'
|
||||
reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software patents. We
|
||||
wish to avoid the danger that redistributors of a free program will
|
||||
individually obtain patent licenses, in effect making the program
|
||||
proprietary. To prevent this, we have made it clear that any patent must be
|
||||
licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification
|
||||
follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains a notice
|
||||
placed by the copyright holder saying it may be distributed under the
|
||||
terms of this General Public License. The "Program", below, refers to
|
||||
any such program or work, and a "work based on the Program" means either
|
||||
the Program or any derivative work under copyright law: that is to say, a
|
||||
work containing the Program or a portion of it, either verbatim or with
|
||||
modifications and/or translated into another language. (Hereinafter,
|
||||
translation is included without limitation in the term "modification".)
|
||||
Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of running
|
||||
the Program is not restricted, and the output from the Program is covered
|
||||
only if its contents constitute a work based on the Program (independent
|
||||
of having been made by running the Program). Whether that is true depends
|
||||
on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's source code
|
||||
as you receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice and
|
||||
disclaimer of warranty; keep intact all the notices that refer to this
|
||||
License and to the absence of any warranty; and give any other recipients
|
||||
of the Program a copy of this License along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and you
|
||||
may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion of it,
|
||||
thus forming a work based on the Program, and copy and distribute such
|
||||
modifications or work under the terms of Section 1 above, provided that
|
||||
you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices stating
|
||||
that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in whole
|
||||
or in part contains or is derived from the Program or any part
|
||||
thereof, to be licensed as a whole at no charge to all third parties
|
||||
under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively when
|
||||
run, you must cause it, when started running for such interactive use
|
||||
in the most ordinary way, to print or display an announcement
|
||||
including an appropriate copyright notice and a notice that there is
|
||||
no warranty (or else, saying that you provide a warranty) and that
|
||||
users may redistribute the program under these conditions, and telling
|
||||
the user how to view a copy of this License. (Exception: if the
|
||||
Program itself is interactive but does not normally print such an
|
||||
announcement, your work based on the Program is not required to print
|
||||
an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program, and
|
||||
can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based on
|
||||
the Program, the distribution of the whole must be on the terms of this
|
||||
License, whose permissions for other licensees extend to the entire
|
||||
whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of a
|
||||
storage or distribution medium does not bring the other work under the
|
||||
scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it, under
|
||||
Section 2) in object code or executable form under the terms of Sections
|
||||
1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable source
|
||||
code, which must be distributed under the terms of Sections 1 and 2
|
||||
above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three years, to
|
||||
give any third party, for a charge no more than your cost of
|
||||
physically performing source distribution, a complete machine-readable
|
||||
copy of the corresponding source code, to be distributed under the
|
||||
terms of Sections 1 and 2 above on a medium customarily used for
|
||||
software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer to
|
||||
distribute corresponding source code. (This alternative is allowed
|
||||
only for noncommercial distribution and only if you received the
|
||||
program in object code or executable form with such an offer, in
|
||||
accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source code
|
||||
means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to control
|
||||
compilation and installation of the executable. However, as a special
|
||||
exception, the source code distributed need not include anything that is
|
||||
normally distributed (in either source or binary form) with the major
|
||||
components (compiler, kernel, and so on) of the operating system on which
|
||||
the executable runs, unless that component itself accompanies the
|
||||
executable.
|
||||
|
||||
If distribution of executable or object code is made by offering access
|
||||
to copy from a designated place, then offering equivalent access to copy
|
||||
the source code from the same place counts as distribution of the source
|
||||
code, even though third parties are not compelled to copy the source
|
||||
along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program except as
|
||||
expressly provided under this License. Any attempt otherwise to copy,
|
||||
modify, sublicense or distribute the Program is void, and will
|
||||
automatically terminate your rights under this License. However, parties
|
||||
who have received copies, or rights, from you under this License will not
|
||||
have their licenses terminated so long as such parties remain in full
|
||||
compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not signed
|
||||
it. However, nothing else grants you permission to modify or distribute
|
||||
the Program or its derivative works. These actions are prohibited by law
|
||||
if you do not accept this License. Therefore, by modifying or
|
||||
distributing the Program (or any work based on the Program), you indicate
|
||||
your acceptance of this License to do so, and all its terms and
|
||||
conditions for copying, distributing or modifying the Program or works
|
||||
based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further restrictions
|
||||
on the recipients' exercise of the rights granted herein. You are not
|
||||
responsible for enforcing compliance by third parties to this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot distribute
|
||||
so as to satisfy simultaneously your obligations under this License and
|
||||
any other pertinent obligations, then as a consequence you may not
|
||||
distribute the Program at all. For example, if a patent license would
|
||||
not permit royalty-free redistribution of the Program by all those who
|
||||
receive copies directly or indirectly through you, then the only way you
|
||||
could satisfy both it and this License would be to refrain entirely from
|
||||
distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under any
|
||||
particular circumstance, the balance of the section is intended to apply
|
||||
and the section as a whole is intended to apply in other circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any such
|
||||
claims; this section has the sole purpose of protecting the integrity of
|
||||
the free software distribution system, which is implemented by public
|
||||
license practices. Many people have made generous contributions to the
|
||||
wide range of software distributed through that system in reliance on
|
||||
consistent application of that system; it is up to the author/donor to
|
||||
decide if he or she is willing to distribute software through any other
|
||||
system and a licensee cannot impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to be
|
||||
a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in certain
|
||||
countries either by patents or by copyrighted interfaces, the original
|
||||
copyright holder who places the Program under this License may add an
|
||||
explicit geographical distribution limitation excluding those countries,
|
||||
so that distribution is permitted only in or among countries not thus
|
||||
excluded. In such case, this License incorporates the limitation as if
|
||||
written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions of
|
||||
the General Public License from time to time. Such new versions will be
|
||||
similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free
|
||||
Software Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free programs
|
||||
whose distribution conditions are different, write to the author to ask
|
||||
for permission. For software which is copyrighted by the Free Software
|
||||
Foundation, write to the Free Software Foundation; we sometimes make
|
||||
exceptions for this. Our decision will be guided by the two goals of
|
||||
preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
|
||||
THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
|
||||
ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
|
||||
YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
|
||||
NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
|
||||
DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
|
||||
DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
|
||||
(INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
|
||||
INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
|
||||
THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR
|
||||
OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
|
@ -1,58 +0,0 @@
|
|||
= 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
|
|
@ -1,55 +0,0 @@
|
|||
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.
|
|
@ -1,32 +0,0 @@
|
|||
= 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
|
|
@ -1,294 +0,0 @@
|
|||
# $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
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -1,108 +0,0 @@
|
|||
# $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
|
||||
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
# $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
|
||||
|
||||
|
|
@ -1,387 +0,0 @@
|
|||
# $Id: filter.rb 151 2006-08-15 08:34:53Z 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 Net::LDAP::Filter is used to constrain
|
||||
# LDAP searches. An object of this class is
|
||||
# passed to Net::LDAP#search in the parameter :filter.
|
||||
#
|
||||
# Net::LDAP::Filter supports the complete set of search filters
|
||||
# available in LDAP, including conjunction, disjunction and negation
|
||||
# (AND, OR, and NOT). This class supplants the (infamous) RFC-2254
|
||||
# standard notation for specifying LDAP search filters.
|
||||
#
|
||||
# Here's how to code the familiar "objectclass is present" filter:
|
||||
# f = Net::LDAP::Filter.pres( "objectclass" )
|
||||
# The object returned by this code can be passed directly to
|
||||
# the <tt>:filter</tt> parameter of Net::LDAP#search.
|
||||
#
|
||||
# See the individual class and instance methods below for more examples.
|
||||
#
|
||||
class Filter
|
||||
|
||||
def initialize op, a, b
|
||||
@op = op
|
||||
@left = a
|
||||
@right = b
|
||||
end
|
||||
|
||||
# #eq creates a filter object indicating that the value of
|
||||
# a paticular attribute must be either <i>present</i> or must
|
||||
# match a particular string.
|
||||
#
|
||||
# To specify that an attribute is "present" means that only
|
||||
# directory entries which contain a value for the particular
|
||||
# attribute will be selected by the filter. This is useful
|
||||
# in case of optional attributes such as <tt>mail.</tt>
|
||||
# Presence is indicated by giving the value "*" in the second
|
||||
# parameter to #eq. This example selects only entries that have
|
||||
# one or more values for <tt>sAMAccountName:</tt>
|
||||
# f = Net::LDAP::Filter.eq( "sAMAccountName", "*" )
|
||||
#
|
||||
# To match a particular range of values, pass a string as the
|
||||
# second parameter to #eq. The string may contain one or more
|
||||
# "*" characters as wildcards: these match zero or more occurrences
|
||||
# of any character. Full regular-expressions are <i>not</i> supported
|
||||
# due to limitations in the underlying LDAP protocol.
|
||||
# This example selects any entry with a <tt>mail</tt> value containing
|
||||
# the substring "anderson":
|
||||
# f = Net::LDAP::Filter.eq( "mail", "*anderson*" )
|
||||
#--
|
||||
# Removed gt and lt. They ain't in the standard!
|
||||
#
|
||||
def Filter::eq attribute, value; Filter.new :eq, attribute, value; end
|
||||
def Filter::ne attribute, value; Filter.new :ne, attribute, value; end
|
||||
#def Filter::gt attribute, value; Filter.new :gt, attribute, value; end
|
||||
#def Filter::lt attribute, value; Filter.new :lt, attribute, value; end
|
||||
def Filter::ge attribute, value; Filter.new :ge, attribute, value; end
|
||||
def Filter::le attribute, value; Filter.new :le, attribute, value; end
|
||||
|
||||
# #pres( attribute ) is a synonym for #eq( attribute, "*" )
|
||||
#
|
||||
def Filter::pres attribute; Filter.eq attribute, "*"; end
|
||||
|
||||
# operator & ("AND") is used to conjoin two or more filters.
|
||||
# This expression will select only entries that have an <tt>objectclass</tt>
|
||||
# attribute AND have a <tt>mail</tt> attribute that begins with "George":
|
||||
# f = Net::LDAP::Filter.pres( "objectclass" ) & Net::LDAP::Filter.eq( "mail", "George*" )
|
||||
#
|
||||
def & filter; Filter.new :and, self, filter; end
|
||||
|
||||
# operator | ("OR") is used to disjoin two or more filters.
|
||||
# This expression will select entries that have either an <tt>objectclass</tt>
|
||||
# attribute OR a <tt>mail</tt> attribute that begins with "George":
|
||||
# f = Net::LDAP::Filter.pres( "objectclass" ) | Net::LDAP::Filter.eq( "mail", "George*" )
|
||||
#
|
||||
def | filter; Filter.new :or, self, filter; end
|
||||
|
||||
|
||||
#
|
||||
# operator ~ ("NOT") is used to negate a filter.
|
||||
# This expression will select only entries that <i>do not</i> have an <tt>objectclass</tt>
|
||||
# attribute:
|
||||
# f = ~ Net::LDAP::Filter.pres( "objectclass" )
|
||||
#
|
||||
#--
|
||||
# This operator can't be !, evidently. Try it.
|
||||
# Removed GT and LT. They're not in the RFC.
|
||||
def ~@; Filter.new :not, self, nil; end
|
||||
|
||||
|
||||
def to_s
|
||||
case @op
|
||||
when :ne
|
||||
"(!(#{@left}=#{@right}))"
|
||||
when :eq
|
||||
"(#{@left}=#{@right})"
|
||||
#when :gt
|
||||
# "#{@left}>#{@right}"
|
||||
#when :lt
|
||||
# "#{@left}<#{@right}"
|
||||
when :ge
|
||||
"#{@left}>=#{@right}"
|
||||
when :le
|
||||
"#{@left}<=#{@right}"
|
||||
when :and
|
||||
"(&(#{@left})(#{@right}))"
|
||||
when :or
|
||||
"(|(#{@left})(#{@right}))"
|
||||
when :not
|
||||
"(!(#{@left}))"
|
||||
else
|
||||
raise "invalid or unsupported operator in LDAP Filter"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#--
|
||||
# to_ber
|
||||
# Filter ::=
|
||||
# CHOICE {
|
||||
# and [0] SET OF Filter,
|
||||
# or [1] SET OF Filter,
|
||||
# not [2] Filter,
|
||||
# equalityMatch [3] AttributeValueAssertion,
|
||||
# substrings [4] SubstringFilter,
|
||||
# greaterOrEqual [5] AttributeValueAssertion,
|
||||
# lessOrEqual [6] AttributeValueAssertion,
|
||||
# present [7] AttributeType,
|
||||
# approxMatch [8] AttributeValueAssertion
|
||||
# }
|
||||
#
|
||||
# SubstringFilter
|
||||
# SEQUENCE {
|
||||
# type AttributeType,
|
||||
# SEQUENCE OF CHOICE {
|
||||
# initial [0] LDAPString,
|
||||
# any [1] LDAPString,
|
||||
# final [2] LDAPString
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# Parsing substrings is a little tricky.
|
||||
# We use the split method to break a string into substrings
|
||||
# delimited by the * (star) character. But we also need
|
||||
# to know whether there is a star at the head and tail
|
||||
# of the string. A Ruby particularity comes into play here:
|
||||
# if you split on * and the first character of the string is
|
||||
# a star, then split will return an array whose first element
|
||||
# is an _empty_ string. But if the _last_ character of the
|
||||
# string is star, then split will return an array that does
|
||||
# _not_ add an empty string at the end. So we have to deal
|
||||
# with all that specifically.
|
||||
#
|
||||
def to_ber
|
||||
case @op
|
||||
when :eq
|
||||
if @right == "*" # present
|
||||
@left.to_s.to_ber_contextspecific 7
|
||||
elsif @right =~ /[\*]/ #substring
|
||||
ary = @right.split( /[\*]+/ )
|
||||
final_star = @right =~ /[\*]$/
|
||||
initial_star = ary.first == "" and ary.shift
|
||||
|
||||
seq = []
|
||||
unless initial_star
|
||||
seq << ary.shift.to_ber_contextspecific(0)
|
||||
end
|
||||
n_any_strings = ary.length - (final_star ? 0 : 1)
|
||||
#p n_any_strings
|
||||
n_any_strings.times {
|
||||
seq << ary.shift.to_ber_contextspecific(1)
|
||||
}
|
||||
unless final_star
|
||||
seq << ary.shift.to_ber_contextspecific(2)
|
||||
end
|
||||
[@left.to_s.to_ber, seq.to_ber].to_ber_contextspecific 4
|
||||
else #equality
|
||||
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 3
|
||||
end
|
||||
when :ge
|
||||
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 5
|
||||
when :le
|
||||
[@left.to_s.to_ber, @right.to_ber].to_ber_contextspecific 6
|
||||
when :and
|
||||
ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten
|
||||
ary.map {|a| a.to_ber}.to_ber_contextspecific( 0 )
|
||||
when :or
|
||||
ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten
|
||||
ary.map {|a| a.to_ber}.to_ber_contextspecific( 1 )
|
||||
when :not
|
||||
[@left.to_ber].to_ber_contextspecific 2
|
||||
else
|
||||
# ERROR, we'll return objectclass=* to keep things from blowing up,
|
||||
# but that ain't a good answer and we need to kick out an error of some kind.
|
||||
raise "unimplemented search filter"
|
||||
end
|
||||
end
|
||||
|
||||
#--
|
||||
# coalesce
|
||||
# This is a private helper method for dealing with chains of ANDs and ORs
|
||||
# that are longer than two. If BOTH of our branches are of the specified
|
||||
# type of joining operator, then return both of them as an array (calling
|
||||
# coalesce recursively). If they're not, then return an array consisting
|
||||
# only of self.
|
||||
#
|
||||
def coalesce operator
|
||||
if @op == operator
|
||||
[@left.coalesce( operator ), @right.coalesce( operator )]
|
||||
else
|
||||
[self]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
#--
|
||||
# We get a Ruby object which comes from parsing an RFC-1777 "Filter"
|
||||
# object. Convert it to a Net::LDAP::Filter.
|
||||
# TODO, we're hardcoding the RFC-1777 BER-encodings of the various
|
||||
# filter types. Could pull them out into a constant.
|
||||
#
|
||||
def Filter::parse_ldap_filter obj
|
||||
case obj.ber_identifier
|
||||
when 0x87 # present. context-specific primitive 7.
|
||||
Filter.eq( obj.to_s, "*" )
|
||||
when 0xa3 # equalityMatch. context-specific constructed 3.
|
||||
Filter.eq( obj[0], obj[1] )
|
||||
else
|
||||
raise LdapError.new( "unknown ldap search-filter type: #{obj.ber_identifier}" )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#--
|
||||
# We got a hash of attribute values.
|
||||
# Do we match the attributes?
|
||||
# Return T/F, and call match recursively as necessary.
|
||||
def match entry
|
||||
case @op
|
||||
when :eq
|
||||
if @right == "*"
|
||||
l = entry[@left] and l.length > 0
|
||||
else
|
||||
l = entry[@left] and l = l.to_a and l.index(@right)
|
||||
end
|
||||
else
|
||||
raise LdapError.new( "unknown filter type in match: #{@op}" )
|
||||
end
|
||||
end
|
||||
|
||||
# Converts an LDAP filter-string (in the prefix syntax specified in RFC-2254)
|
||||
# to a Net::LDAP::Filter.
|
||||
def self.construct ldap_filter_string
|
||||
FilterParser.new(ldap_filter_string).filter
|
||||
end
|
||||
|
||||
# Synonym for #construct.
|
||||
# to a Net::LDAP::Filter.
|
||||
def self.from_rfc2254 ldap_filter_string
|
||||
construct ldap_filter_string
|
||||
end
|
||||
|
||||
end # class Net::LDAP::Filter
|
||||
|
||||
|
||||
|
||||
class FilterParser #:nodoc:
|
||||
|
||||
attr_reader :filter
|
||||
|
||||
def initialize str
|
||||
require 'strscan'
|
||||
@filter = parse( StringScanner.new( str )) or raise Net::LDAP::LdapError.new( "invalid filter syntax" )
|
||||
end
|
||||
|
||||
def parse scanner
|
||||
parse_filter_branch(scanner) or parse_paren_expression(scanner)
|
||||
end
|
||||
|
||||
def parse_paren_expression scanner
|
||||
if scanner.scan(/\s*\(\s*/)
|
||||
b = if scanner.scan(/\s*\&\s*/)
|
||||
a = nil
|
||||
branches = []
|
||||
while br = parse_paren_expression(scanner)
|
||||
branches << br
|
||||
end
|
||||
if branches.length >= 2
|
||||
a = branches.shift
|
||||
while branches.length > 0
|
||||
a = a & branches.shift
|
||||
end
|
||||
a
|
||||
end
|
||||
elsif scanner.scan(/\s*\|\s*/)
|
||||
# TODO: DRY!
|
||||
a = nil
|
||||
branches = []
|
||||
while br = parse_paren_expression(scanner)
|
||||
branches << br
|
||||
end
|
||||
if branches.length >= 2
|
||||
a = branches.shift
|
||||
while branches.length > 0
|
||||
a = a | branches.shift
|
||||
end
|
||||
a
|
||||
end
|
||||
elsif scanner.scan(/\s*\!\s*/)
|
||||
br = parse_paren_expression(scanner)
|
||||
if br
|
||||
~ br
|
||||
end
|
||||
else
|
||||
parse_filter_branch( scanner )
|
||||
end
|
||||
|
||||
if b and scanner.scan( /\s*\)\s*/ )
|
||||
b
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Added a greatly-augmented filter contributed by Andre Nathan
|
||||
# for detecting special characters in values. (15Aug06)
|
||||
def parse_filter_branch scanner
|
||||
scanner.scan(/\s*/)
|
||||
if token = scanner.scan( /[\w\-_]+/ )
|
||||
scanner.scan(/\s*/)
|
||||
if op = scanner.scan( /\=|\<\=|\<|\>\=|\>|\!\=/ )
|
||||
scanner.scan(/\s*/)
|
||||
#if value = scanner.scan( /[\w\*\.]+/ ) (ORG)
|
||||
if value = scanner.scan( /[\w\*\.\+\-@=#\$%&!]+/ )
|
||||
case op
|
||||
when "="
|
||||
Filter.eq( token, value )
|
||||
when "!="
|
||||
Filter.ne( token, value )
|
||||
when "<"
|
||||
Filter.lt( token, value )
|
||||
when "<="
|
||||
Filter.le( token, value )
|
||||
when ">"
|
||||
Filter.gt( token, value )
|
||||
when ">="
|
||||
Filter.ge( token, value )
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end # class Net::LDAP::FilterParser
|
||||
|
||||
end # class Net::LDAP
|
||||
end # module Net
|
||||
|
||||
|
|
@ -1,205 +0,0 @@
|
|||
# $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
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
# $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
|
||||
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
# $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
|
||||
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
# $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
|
||||
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
# $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'
|
||||
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# $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
|
||||
|
|
@ -1,190 +0,0 @@
|
|||
# $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
|
||||
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
# $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
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# $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
|
||||
|
||||
|
Loading…
Reference in New Issue