pax_global_header00006660000000000000000000000064142475147560014531gustar00rootroot0000000000000052 comment=1d677e9af6db1483edc2fc0983799a7948b12fb6 ruby-net-ldap-0.17.1/000077500000000000000000000000001424751475600143025ustar00rootroot00000000000000ruby-net-ldap-0.17.1/.github/000077500000000000000000000000001424751475600156425ustar00rootroot00000000000000ruby-net-ldap-0.17.1/.github/workflows/000077500000000000000000000000001424751475600176775ustar00rootroot00000000000000ruby-net-ldap-0.17.1/.github/workflows/test.yml000066400000000000000000000014671424751475600214110ustar00rootroot00000000000000# This workflow uses actions that are not certified by GitHub. # They are provided by a third-party and are governed by # separate terms of service, privacy policy, and support # documentation. # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby name: Test on: pull_request: push: branches: - master jobs: test: runs-on: ubuntu-latest strategy: matrix: ruby: - "2.5" - "2.6" - "2.7" - "3.0" - "jruby-9.2" - "truffleruby-21.0.0" steps: - uses: actions/checkout@v2 - name: Run tests with Ruby ${{ matrix.ruby }} run: docker-compose run ci-${{ matrix.ruby }} ruby-net-ldap-0.17.1/.gitignore000066400000000000000000000001111424751475600162630ustar00rootroot00000000000000*~ *.swp .rvmrc pkg/ doc/ publish/ Gemfile.lock .bundle bin/ .idea *.gem ruby-net-ldap-0.17.1/.rubocop.yml000066400000000000000000000005151424751475600165550ustar00rootroot00000000000000inherit_from: .rubocop_todo.yml AllCops: Exclude: - 'pkg/**/*' Layout/ExtraSpacing: Enabled: false Lint/AssignmentInCondition: Enabled: false Style/ParallelAssignment: Enabled: false Style/TrailingCommaInLiteral: EnforcedStyleForMultiline: comma Style/TrailingCommaInArguments: EnforcedStyleForMultiline: comma ruby-net-ldap-0.17.1/.rubocop_todo.yml000066400000000000000000000451071424751475600176100ustar00rootroot00000000000000# This configuration was generated by # `rubocop --auto-gen-config` # on 2020-07-12 00:41:11 -0400 using RuboCop version 0.49.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # Offense count: 4 # Cop supports --auto-correct. Layout/AlignArray: Exclude: - 'lib/net/ldap.rb' - 'lib/net/ldap/auth_adapter/sasl.rb' - 'lib/net/ldap/connection.rb' # Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, IndentOneStep, IndentationWidth. # SupportedStyles: case, end Layout/CaseIndentation: Exclude: - 'lib/net/ldap/filter.rb' # Offense count: 1 # Cop supports --auto-correct. Layout/EmptyLineAfterMagicComment: Exclude: - 'net-ldap.gemspec' # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: AllowAdjacentOneLineDefs, NumberOfEmptyLines. Layout/EmptyLineBetweenDefs: Exclude: - 'lib/net/ldap.rb' - 'lib/net/ldap/dataset.rb' - 'lib/net/snmp.rb' # Offense count: 1 # Cop supports --auto-correct. Layout/EmptyLines: Exclude: - 'lib/net/snmp.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines Layout/EmptyLinesAroundClassBody: Exclude: - 'lib/net/ldap.rb' # Offense count: 1 # Cop supports --auto-correct. Layout/EmptyLinesAroundExceptionHandlingKeywords: Exclude: - 'lib/net/ldap/connection.rb' # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_brackets Layout/IndentArray: EnforcedStyle: consistent # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles, IndentationWidth. # SupportedStyles: special_inside_parentheses, consistent, align_braces Layout/IndentHash: EnforcedStyle: consistent # Offense count: 6 # Cop supports --auto-correct. # Configuration parameters: Width, IgnoredPatterns. Layout/IndentationWidth: Exclude: - 'lib/net/ber.rb' - 'lib/net/ldap/password.rb' - 'lib/net/snmp.rb' # Offense count: 3 # Cop supports --auto-correct. Layout/LeadingCommentSpace: Exclude: - 'lib/net/ber/core_ext/array.rb' - 'lib/net/ldap.rb' - 'lib/net/ldap/connection.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: symmetrical, new_line, same_line Layout/MultilineMethodCallBraceLayout: Exclude: - 'lib/net/ldap/filter.rb' # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: space, no_space Layout/SpaceAroundEqualsInParameterDefault: Exclude: - 'lib/net/ldap/connection.rb' - 'lib/net/snmp.rb' # Offense count: 4 # Cop supports --auto-correct. Layout/SpaceAroundKeyword: Exclude: - 'lib/net/ldap/entry.rb' - 'lib/net/snmp.rb' # Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: AllowForAlignment. Layout/SpaceAroundOperators: Exclude: - 'lib/net/ber/ber_parser.rb' - 'lib/net/ldap/connection.rb' - 'lib/net/ldap/entry.rb' - 'lib/net/ldap/filter.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SupportedStylesForEmptyBraces, SpaceBeforeBlockParameters. # SupportedStyles: space, no_space # SupportedStylesForEmptyBraces: space, no_space Layout/SpaceInsideBlockBraces: Exclude: - 'lib/net/ldap/dataset.rb' # Offense count: 8 # Cop supports --auto-correct. Layout/SpaceInsideParens: Exclude: - 'lib/net/ldap/entry.rb' - 'lib/net/snmp.rb' # Offense count: 1 Lint/AmbiguousBlockAssociation: Exclude: - 'testserver/ldapserver.rb' # Offense count: 1 Lint/EmptyWhen: Exclude: - 'lib/net/ldap/pdu.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyleAlignWith, SupportedStylesAlignWith, AutoCorrect. # SupportedStylesAlignWith: keyword, variable, start_of_line Lint/EndAlignment: Exclude: - 'testserver/ldapserver.rb' # Offense count: 30 Lint/ImplicitStringConcatenation: Exclude: - 'test/test_filter.rb' # Offense count: 1 Lint/NonLocalExitFromIterator: Exclude: - 'lib/net/ldap/connection.rb' # Offense count: 1 Lint/RescueException: Exclude: - 'lib/net/ldap/pdu.rb' # Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: Exclude: - 'lib/net/ldap.rb' - 'lib/net/snmp.rb' # Offense count: 7 # Cop supports --auto-correct. # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. Lint/UnusedMethodArgument: Exclude: - 'lib/net/ldap/entry.rb' - 'lib/net/ldap/pdu.rb' - 'test/test_ldap.rb' - 'test/test_ldap_connection.rb' - 'test/test_search.rb' # Offense count: 1 # Configuration parameters: ContextCreatingMethods, MethodCreatingMethods. Lint/UselessAccessModifier: Exclude: - 'lib/net/ldap/connection.rb' # Offense count: 6 Lint/UselessAssignment: Exclude: - 'test/integration/test_add.rb' - 'test/test_ldap_connection.rb' - 'test/test_search.rb' - 'test/test_snmp.rb' # Offense count: 48 Metrics/AbcSize: Max: 116 # Offense count: 4 # Configuration parameters: CountComments, ExcludedMethods. Metrics/BlockLength: Max: 119 # Offense count: 11 # Configuration parameters: CountBlocks. Metrics/BlockNesting: Max: 4 # Offense count: 11 # Configuration parameters: CountComments. Metrics/ClassLength: Max: 445 # Offense count: 23 Metrics/CyclomaticComplexity: Max: 41 # Offense count: 216 # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. # URISchemes: http, https Metrics/LineLength: Max: 360 # Offense count: 74 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 128 # Offense count: 1 # Configuration parameters: CountComments. Metrics/ModuleLength: Max: 103 # Offense count: 15 Metrics/PerceivedComplexity: Max: 38 # Offense count: 1 Style/AccessorMethodName: Exclude: - 'lib/net/ldap.rb' # Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: prefer_alias, prefer_alias_method Style/Alias: Exclude: - 'lib/net/ber/core_ext/array.rb' - 'lib/net/ldap.rb' - 'lib/net/ldap/entry.rb' - 'lib/net/ldap/filter.rb' - 'lib/net/ldap/pdu.rb' # Offense count: 33 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: always, conditionals Style/AndOr: Exclude: - 'lib/net/ber/ber_parser.rb' - 'lib/net/ldap.rb' - 'lib/net/ldap/connection.rb' - 'lib/net/ldap/dataset.rb' - 'lib/net/ldap/filter.rb' - 'lib/net/ldap/pdu.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: percent_q, bare_percent Style/BarePercentLiterals: Exclude: - 'test/test_entry.rb' # Offense count: 1 # Cop supports --auto-correct. Style/BlockComments: Exclude: - 'test/test_rename.rb' # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: braces, no_braces, context_dependent Style/BracesAroundHashParameters: Exclude: - 'lib/net/ldap/auth_adapter/gss_spnego.rb' - 'lib/net/snmp.rb' # Offense count: 4 # Cop supports --auto-correct. Style/CharacterLiteral: Exclude: - 'lib/net/ldap/dataset.rb' - 'lib/net/ldap/entry.rb' # Offense count: 1 Style/ClassAndModuleCamelCase: Exclude: - 'lib/net/ldap/auth_adapter/gss_spnego.rb' # Offense count: 23 # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: nested, compact Style/ClassAndModuleChildren: Enabled: false # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: is_a?, kind_of? Style/ClassCheck: Exclude: - 'lib/net/ber/core_ext/array.rb' - 'lib/net/ldap/error.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: Keywords. # Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW Style/CommentAnnotation: Exclude: - 'lib/net/ber.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly, IncludeTernaryExpressions. # SupportedStyles: assign_to_condition, assign_inside_condition Style/ConditionalAssignment: Exclude: - 'lib/net/ldap/dn.rb' # Offense count: 87 Style/ConstantName: Exclude: - 'lib/net/ldap.rb' - 'lib/net/ldap/connection.rb' - 'lib/net/ldap/filter.rb' - 'lib/net/ldap/pdu.rb' - 'lib/net/snmp.rb' - 'test/test_ldif.rb' - 'testserver/ldapserver.rb' # Offense count: 17 Style/Documentation: Exclude: - 'spec/**/*' - 'test/**/*' - 'lib/net/ber/core_ext.rb' - 'lib/net/ldap.rb' - 'lib/net/ldap/auth_adapter.rb' - 'lib/net/ldap/auth_adapter/sasl.rb' - 'lib/net/ldap/auth_adapter/simple.rb' - 'lib/net/ldap/connection.rb' - 'lib/net/ldap/error.rb' - 'lib/net/ldap/instrumentation.rb' - 'lib/net/ldap/password.rb' - 'lib/net/ldap/pdu.rb' - 'lib/net/snmp.rb' - 'testserver/ldapserver.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: compact, expanded Style/EmptyMethod: Exclude: - 'test/test_auth_adapter.rb' # Offense count: 3 # Cop supports --auto-correct. Style/EvenOdd: Exclude: - 'lib/net/ldap/dn.rb' # Offense count: 1 # Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts, AllowedAcronyms. # AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS Style/FileName: Exclude: - 'lib/net-ldap.rb' # Offense count: 9 # Configuration parameters: AllowedVariables. Style/GlobalVars: Exclude: - 'testserver/ldapserver.rb' # Offense count: 2 # Configuration parameters: MinBodyLength. Style/GuardClause: Exclude: - 'lib/net/ldap/filter.rb' # Offense count: 159 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. # SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys Style/HashSyntax: Exclude: - 'lib/net/ber.rb' - 'lib/net/ber/ber_parser.rb' - 'lib/net/ldap.rb' - 'lib/net/ldap/auth_adapter/gss_spnego.rb' - 'lib/net/ldap/connection.rb' - 'lib/net/ldap/pdu.rb' - 'lib/net/snmp.rb' - 'test/test_auth_adapter.rb' - 'test/test_ldap.rb' - 'test/test_ldap_connection.rb' - 'test/test_search.rb' - 'test/test_ssl_ber.rb' - 'testserver/ldapserver.rb' # Offense count: 1 Style/IfInsideElse: Exclude: - 'lib/net/ldap/instrumentation.rb' # Offense count: 6 # Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: Exclude: - 'lib/net/ber.rb' - 'lib/net/ber/core_ext/integer.rb' - 'lib/net/ldap.rb' - 'lib/net/ldap/filter.rb' - 'lib/net/snmp.rb' # Offense count: 21 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline Style/MethodDefParentheses: Exclude: - 'lib/net/ber.rb' - 'lib/net/ldap/pdu.rb' - 'lib/net/snmp.rb' - 'testserver/ldapserver.rb' # Offense count: 2 Style/MethodMissing: Exclude: - 'lib/net/ldap/dn.rb' - 'lib/net/ldap/entry.rb' # Offense count: 2 # Cop supports --auto-correct. Style/MultilineIfModifier: Exclude: - 'lib/net/ldap/connection.rb' # Offense count: 25 # Cop supports --auto-correct. Style/MutableConstant: Exclude: - 'lib/net/ber.rb' - 'lib/net/ldap.rb' - 'lib/net/ldap/connection.rb' - 'lib/net/ldap/dn.rb' - 'lib/net/ldap/filter.rb' - 'lib/net/ldap/version.rb' - 'lib/net/snmp.rb' - 'test/test_ldif.rb' - 'testserver/ldapserver.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: both, prefix, postfix Style/NegatedIf: Exclude: - 'test/test_helper.rb' # Offense count: 1 # Cop supports --auto-correct. Style/NegatedWhile: Exclude: - 'lib/net/ldap/filter.rb' # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles. # SupportedStyles: skip_modifier_ifs, always Style/Next: Exclude: - 'lib/net/ldap/connection.rb' - 'testserver/ldapserver.rb' # Offense count: 1 # Cop supports --auto-correct. Style/NilComparison: Exclude: - 'lib/net/ldap/connection.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: IncludeSemanticChanges. Style/NonNilCheck: Exclude: - 'lib/net/ber/ber_parser.rb' # Offense count: 1 # Cop supports --auto-correct. Style/Not: Exclude: - 'lib/net/ldap/filter.rb' # Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: Strict. Style/NumericLiterals: MinDigits: 8 # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: AutoCorrect, EnforcedStyle, SupportedStyles. # SupportedStyles: predicate, comparison Style/NumericPredicate: Exclude: - 'spec/**/*' - 'lib/net/ber/core_ext/integer.rb' - 'lib/net/ldap/dn.rb' - 'testserver/ldapserver.rb' # Offense count: 3 Style/OpMethod: Exclude: - 'lib/net/ldap/filter.rb' # Offense count: 6 # Cop supports --auto-correct. # Configuration parameters: AllowSafeAssignment. Style/ParenthesesAroundCondition: Exclude: - 'lib/net/ldap.rb' - 'lib/net/ldap/auth_adapter/gss_spnego.rb' - 'lib/net/ldap/auth_adapter/sasl.rb' - 'lib/net/ldap/auth_adapter/simple.rb' # Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: Exclude: - 'net-ldap.gemspec' - 'test/integration/test_add.rb' - 'test/integration/test_delete.rb' - 'test/integration/test_open.rb' - 'test/integration/test_password_modify.rb' - 'test/test_entry.rb' - 'test/test_helper.rb' # Offense count: 11 # Cop supports --auto-correct. Style/PerlBackrefs: Exclude: - 'lib/net/ldap/dataset.rb' - 'lib/net/ldap/filter.rb' - 'testserver/ldapserver.rb' # Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: compact, exploded Style/RaiseArgs: Exclude: - 'lib/net/ldap/connection.rb' - 'lib/net/ldap/pdu.rb' - 'lib/net/snmp.rb' # Offense count: 1 # Cop supports --auto-correct. Style/RedundantBegin: Exclude: - 'lib/net/snmp.rb' # Offense count: 4 # Cop supports --auto-correct. Style/RedundantParentheses: Exclude: - 'lib/net/ldap/filter.rb' - 'test/test_filter.rb' # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: Exclude: - 'lib/net/ber/core_ext/string.rb' - 'lib/net/ldap/auth_adapter.rb' - 'lib/net/ldap/entry.rb' # Offense count: 8 # Cop supports --auto-correct. Style/RedundantSelf: Exclude: - 'lib/net/ber/core_ext/array.rb' - 'lib/net/ber/core_ext/string.rb' - 'lib/net/ldap/dn.rb' - 'lib/net/ldap/filter.rb' # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed Style/RegexpLiteral: Exclude: - 'lib/net/ldap/filter.rb' - 'net-ldap.gemspec' # Offense count: 1 # Cop supports --auto-correct. Style/RescueModifier: Exclude: - 'test/ber/core_ext/test_string.rb' # Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: AllowAsExpressionSeparator. Style/Semicolon: Exclude: - 'lib/net/ldap/dn.rb' - 'lib/net/ldap/error.rb' - 'testserver/ldapserver.rb' # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. # SupportedStyles: use_perl_names, use_english_names Style/SpecialGlobalVars: Exclude: - 'lib/net/snmp.rb' - 'net-ldap.gemspec' - 'testserver/ldapserver.rb' # Offense count: 656 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: Enabled: false # Offense count: 1 Style/StructInheritance: Exclude: - 'test/test_ldap.rb' # Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: MinSize, SupportedStyles. # SupportedStyles: percent, brackets Style/SymbolArray: EnforcedStyle: brackets # Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment. # SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex Style/TernaryParentheses: Exclude: - 'lib/net/ber/core_ext/integer.rb' - 'lib/net/ldap/connection.rb' - 'lib/net/ldap/dataset.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist. # Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym Style/TrivialAccessors: Exclude: - 'lib/net/ldap/connection.rb' # Offense count: 5 # Cop supports --auto-correct. Style/UnneededPercentQ: Exclude: - 'net-ldap.gemspec' - 'test/test_entry.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/WhileUntilModifier: Exclude: - 'lib/net/ldap/filter.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: SupportedStyles, WordRegex. # SupportedStyles: percent, brackets Style/WordArray: EnforcedStyle: percent MinSize: 3 # Offense count: 2 # Cop supports --auto-correct. Style/YodaCondition: Exclude: - 'lib/net/ber/ber_parser.rb' - 'testserver/ldapserver.rb' # Offense count: 6 # Cop supports --auto-correct. Style/ZeroLengthPredicate: Exclude: - 'lib/net/ldap/connection.rb' - 'lib/net/ldap/filter.rb' - 'testserver/ldapserver.rb' ruby-net-ldap-0.17.1/.travis.yml000066400000000000000000000017121424751475600164140ustar00rootroot00000000000000language: ruby rvm: - 2.0.0 - 2.1 - 2.2 - 2.3 - 2.4 - 2.5 - 2.6 - 2.7 - jruby-9.2 # optional - ruby-head - jruby-19mode - jruby-9.2 - jruby-head addons: hosts: - ldap.example.org # needed for TLS verification - cert.mismatch.example.org services: - docker env: - INTEGRATION=openldap cache: bundler before_install: - gem update bundler install: - > docker run \ --hostname ldap.example.org \ --env LDAP_TLS_VERIFY_CLIENT=try \ -p 389:389 \ -p 636:636 \ -v "$(pwd)"/test/fixtures/ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom \ --name openldap \ --detach \ osixia/openldap:1.3.0 \ --copy-service \ --loglevel debug \ - bundle install script: bundle exec rake ci matrix: allow_failures: - rvm: ruby-head - rvm: jruby-19mode - rvm: jruby-9.2 - rvm: jruby-head fast_finish: true notifications: email: false ruby-net-ldap-0.17.1/CONTRIBUTING.md000066400000000000000000000023741424751475600165410ustar00rootroot00000000000000# Contribution guide Thank you for using net-ldap. If you'd like to help, keep these guidelines in mind. ## Submitting a New Issue If you find a bug, or would like to propose an idea, file a [new issue][issues]. Include as many details as possible: - Version of net-ldap gem - LDAP server version - Queries, connection information, any other input - output or error messages ## Sending a Pull Request [Pull requests][pr] are always welcome! Check out [the project's issues list][issues] for ideas on what could be improved. Before sending, please add tests and ensure the test suite passes. To run the full suite: `bundle exec rake` To run a specific test file: `bundle exec ruby test/test_ldap.rb` To run a specific test: `bundle exec ruby test/test_ldap.rb -n test_instrument_bind` Pull requests will trigger automatic continuous integration builds on [TravisCI][travis]. To run integration tests locally, see the `test/support` folder. ## Styleguide ```ruby # 1.9+ style hashes {key: "value"} # Multi-line arguments with `\` MyClass.new \ foo: 'bar', baz: 'garply' ``` [issues]: https://github.com/ruby-ldap/ruby-net-ldap/issues [pr]: https://help.github.com/articles/using-pull-requests [travis]: https://travis-ci.org/ruby-ldap/ruby-net-ldap ruby-net-ldap-0.17.1/Contributors.rdoc000066400000000000000000000010701424751475600176460ustar00rootroot00000000000000== 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 * David J. Lee (DavidJLee) * Cody Cutrer (ccutrer) * WoodsBagotAndreMarquesLee * Rufus Post (mynameisrufus) ruby-net-ldap-0.17.1/Gemfile000066400000000000000000000000461424751475600155750ustar00rootroot00000000000000source 'https://rubygems.org' gemspec ruby-net-ldap-0.17.1/Hacking.rdoc000066400000000000000000000045131424751475600165220ustar00rootroot00000000000000= 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://rubydoc.info/gems/net-ldap] 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 [Minitest](http://docs.seattlerb.org/minitest/) for unit testing; all changes must have 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. * *flexmock* == Participation * GitHub: {ruby-ldap/ruby-net-ldap}[https://github.com/ruby-ldap/ruby-net-ldap/] * Group: {ruby-ldap}[http://groups.google.com/group/ruby-ldap] ruby-net-ldap-0.17.1/History.rdoc000066400000000000000000000436651424751475600166320ustar00rootroot00000000000000=== Net::LDAP 0.17.1 * Fixed shebang of bash #385 * Omit some tests for now until we update our CA cert #386 * Add Ruby 3.0 support #388 * Add TruffleRuby 21.0.0 to CI #389 * Correct a typo in an error message #391 * Enable bundler caching for travis #390 * Fix circular require while loading lib/net/ldap/entry.rb and lib/net/ldap/dataset.rb #392 * Handle nil value in GetbyteForSSLSocket::getbyte #306 === Net::LDAP 0.17.0 * Added private recursive_delete as alternative to DELETE_TREE #268 * Test suite updates #373 #376 #377 * Use Base64.strict_encode64 and SSHA256 #303 * Remove deprecated ConnectionRefusedError #366 * Added method to get a duplicate of the internal Hash #286 * remove a circular require #380 * fix LdapServerAsnSyntax compile #379 * Implement '==' operator for entries #381 * fix for undefined method for write exception #383 === Net::LDAP 0.16.3 * Add Net::LDAP::InvalidDNError #371 * Use require_relative instead of require #360 * Address some warnings and fix JRuby test omissions #365 * Bump rake dev dependency to 12.3 #359 * Enable rubocop in ci #251 * Enhance rubocop configuration and test syntax #344 * CI: Drop rbx-2, uninstallable #364 * Fix RuboCop warnings #312 * Fix wrong error class #305 * CONTRIBUTING.md: Repair link to Issues #309 * Make the generate() method more idiomatic... #326 * Make encode_sort_controls() more idiomatic... #327 * Make the instrument() method more idiomatic... #328 * Fix uninitialised Net::LDAP::LdapPduError #338 * README.rdoc: Use SVG build badge #310 * Update TravisCI config to inclue Ruby 2.7 #346 * add explicit ** to silence Ruby 2.7 warning #342 * Support parsing filters with attribute tags #345 * Bump rubocop development dependency version #336 * Add link to generated and hosted documentation on rubydoc #319 * Fix 'uninitialized constant Net::LDAP::PDU::LdapPduError' error #317 * simplify encoding logic: no more chomping required #362 === Net::LDAP 0.16.2 * Net::LDAP#open does not cache bind result {#334}[https://github.com/ruby-ldap/ruby-net-ldap/pull/334] * Fix CI build {#333}[https://github.com/ruby-ldap/ruby-net-ldap/pull/333] * Fix to "undefined method 'result_code'" {#308}[https://github.com/ruby-ldap/ruby-net-ldap/pull/308] * Fixed Exception: incompatible character encodings: ASCII-8BIT and UTF-8 in filter.rb {#285}[https://github.com/ruby-ldap/ruby-net-ldap/pull/285] === Net::LDAP 0.16.1 * Send DN and newPassword with password_modify request {#271}[https://github.com/ruby-ldap/ruby-net-ldap/pull/271] === Net::LDAP 0.16.0 * Sasl fix {#281}[https://github.com/ruby-ldap/ruby-net-ldap/pull/281] * enable TLS hostname validation {#279}[https://github.com/ruby-ldap/ruby-net-ldap/pull/279] * update rubocop to 0.42.0 {#278}[https://github.com/ruby-ldap/ruby-net-ldap/pull/278] === Net::LDAP 0.15.0 * Respect connect_timeout when establishing SSL connections {#273}[https://github.com/ruby-ldap/ruby-net-ldap/pull/273] === Net::LDAP 0.14.0 * Normalize the encryption parameter passed to the LDAP constructor {#264}[https://github.com/ruby-ldap/ruby-net-ldap/pull/264] * Update Docs: Net::LDAP now requires ruby >= 2 {#261}[https://github.com/ruby-ldap/ruby-net-ldap/pull/261] * fix symbol proc {#255}[https://github.com/ruby-ldap/ruby-net-ldap/pull/255] * fix trailing commas {#256}[https://github.com/ruby-ldap/ruby-net-ldap/pull/256] * fix deprecated hash methods {#254}[https://github.com/ruby-ldap/ruby-net-ldap/pull/254] * fix space after comma {#253}[https://github.com/ruby-ldap/ruby-net-ldap/pull/253] * fix space inside brackets {#252}[https://github.com/ruby-ldap/ruby-net-ldap/pull/252] * Rubocop style fixes {#249}[https://github.com/ruby-ldap/ruby-net-ldap/pull/249] * Lazy initialize Net::LDAP::Connection's internal socket {#235}[https://github.com/ruby-ldap/ruby-net-ldap/pull/235] * Support for rfc3062 Password Modify, closes #163 {#178}[https://github.com/ruby-ldap/ruby-net-ldap/pull/178] === Net::LDAP 0.13.0 Avoid this release for because of an backwards incompatibility in how encryption is initialized https://github.com/ruby-ldap/ruby-net-ldap/pull/264. We did not yank it because people have already worked around it. * Set a connect_timeout for the creation of a socket {#243}[https://github.com/ruby-ldap/ruby-net-ldap/pull/243] * Update bundler before installing gems with bundler {#245}[https://github.com/ruby-ldap/ruby-net-ldap/pull/245] * Net::LDAP#encryption accepts string {#239}[https://github.com/ruby-ldap/ruby-net-ldap/pull/239] * Adds correct UTF-8 encoding to Net::BER::BerIdentifiedString {#242}[https://github.com/ruby-ldap/ruby-net-ldap/pull/242] * Remove 2.3.0-preview since ruby-head already is included {#241}[https://github.com/ruby-ldap/ruby-net-ldap/pull/241] * Drop support for ruby 1.9.3 {#240}[https://github.com/ruby-ldap/ruby-net-ldap/pull/240] * Fixed capitalization of StartTLSError {#234}[https://github.com/ruby-ldap/ruby-net-ldap/pull/234] === Net::LDAP 0.12.1 * Whitespace formatting cleanup {#236}[https://github.com/ruby-ldap/ruby-net-ldap/pull/236] * Set operation result if LDAP server is not accessible {#232}[https://github.com/ruby-ldap/ruby-net-ldap/pull/232] === Net::LDAP 0.12.0 * DRY up connection handling logic {#224}[https://github.com/ruby-ldap/ruby-net-ldap/pull/224] * Define auth adapters {#226}[https://github.com/ruby-ldap/ruby-net-ldap/pull/226] * add slash to attribute value filter {#225}[https://github.com/ruby-ldap/ruby-net-ldap/pull/225] * Add the ability to provide a list of hosts for a connection {#223}[https://github.com/ruby-ldap/ruby-net-ldap/pull/223] * Specify the port of LDAP server by giving INTEGRATION_PORT {#221}[https://github.com/ruby-ldap/ruby-net-ldap/pull/221] * Correctly set BerIdentifiedString values to UTF-8 {#212}[https://github.com/ruby-ldap/ruby-net-ldap/pull/212] * Raise Net::LDAP::ConnectionRefusedError when new connection is refused. {#213}[https://github.com/ruby-ldap/ruby-net-ldap/pull/213] * obscure auth password upon #inspect, added test, closes #216 {#217}[https://github.com/ruby-ldap/ruby-net-ldap/pull/217] * Fixing incorrect error class name {#207}[https://github.com/ruby-ldap/ruby-net-ldap/pull/207] * Travis update {#205}[https://github.com/ruby-ldap/ruby-net-ldap/pull/205] * Remove obsolete rbx-19mode from Travis {#204}[https://github.com/ruby-ldap/ruby-net-ldap/pull/204] * mv "sudo" from script/install-openldap to .travis.yml {#199}[https://github.com/ruby-ldap/ruby-net-ldap/pull/199] * Remove meaningless shebang {#200}[https://github.com/ruby-ldap/ruby-net-ldap/pull/200] * Fix Travis CI build {#202}[https://github.com/ruby-ldap/ruby-net-ldap/pull/202] * README.rdoc: fix travis link {#195}[https://github.com/ruby-ldap/ruby-net-ldap/pull/195] === Net::LDAP 0.11 * Major enhancements: * #183 Specific errors subclassing Net::LDAP::Error * Bug fixes: * #176 Fix nil tls options * #184 Search guards against nil queued reads. Connection#unescape handles numerics * Code clean-up: * #180 Refactor connection establishment === Net::LDAP 0.10.1 * Bug fixes: * Fix Integer BER encoding of signed values === Net::LDAP 0.10.0 * Major enhancements: * Accept SimpleTLS/StartTLS encryption options (compatible with `OpenSSL::SSL::SSLContext#set_params`) * Bug fixes: * Parse filter strings with square and curly braces (`[]` and `{}`) * Handle connection timeout errors (`Errno::ETIMEDOUT` raised as `Net::LDAP::LdapError`) * Testing changes: * Add integration tests for StartTLS connections to OpenLDAP * Meta changes: * Update Gem release tooling (remove Hoe, use Rake) * Fix Gem release date === Net::LDAP 0.9.0 * Major changes: * Dropped support for ruby 1.8.7, ruby >= 1.9.3 now required * Major enhancements: * Add support for search time limit parameter * Instrument received messages, PDU parsing * Minor enhancments: * Add support for querying ActiveDirectory capabilities from root dse * Bug fixes: * Fix reads for multiple concurrent requests with shared, open connections mixing up the results * Fix search size option * Fix BER encoding bug * Code clean-up: * Added integration test suite * Switch to minitest * Details * #150 Support querying ActiveDirectory capabilities when searching root dse * #142 Encode true as xFF * #124, #145, #146, #152 Cleanup gemspec * #138, #144 Track response messages by message id * #141 Magic number/constant cleanup * #119, #129, #130, #132, #133, #137 Integration tests * #115 Search timeout support * #140 Fix search size option * #139 Cleanup and inline documentation for Net::LDAP::Connection#search * #131 Instrumentation * #116 Refactor Connection#write * #126 Update gitignore * #128 Fix whitespace * #113, #121 Switch to minitest * #123 Base64 encoded dn * #114 Separate file for Net::LDAP::Connection * #104 Parse version spec in LDIF datasets * #106 ldap.modify doc fixes * #111 Fix test deprecations === Net::LDAP 0.5.0 / 2013-07-22 * Major changes: * Required Ruby version is >=1.9.3 * Major enhancements: * Added alias dereferencing (@ngwilson) * BER now unescapes characters that are already escaped in the source string (@jzinn) * BerIdentifiedString will now fall back to ASCII-8 encoding if the source Ruby object cannot be encoded in UTF-8 (@lfu) * Bug fixes: * Fixed nil variable error when following a reference response (@cmdrclueless) * Fixed FilterParser unable to parse multibyte strings (@satoryu) * Return ConverterNotFound when dealing with a potentially corrupt data response (@jamuc) === Net::LDAP 0.3.1 / 2012-02-15 * Bug Fixes: * Bundler should now work again === Net::LDAP 0.3.0 / 2012-02-14 * Major changes: * Now uses UTF-8 strings instead of ASCII-8 per the LDAP RFC * Major Enhancements: * Adding continuation reference processing * Bug Fixes: * Fixes usupported object type #139 * Fixes Net::LDAP namespace errors * Return nil instead of an empty array if the search fails === 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. ruby-net-ldap-0.17.1/License.rdoc000066400000000000000000000024671424751475600165460ustar00rootroot00000000000000== 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. ruby-net-ldap-0.17.1/README.rdoc000066400000000000000000000056741424751475600161240ustar00rootroot00000000000000= Net::LDAP for Ruby {Gem Version}[https://badge.fury.io/rb/net-ldap] {}[https://travis-ci.org/ruby-ldap/ruby-net-ldap] == 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 client compliance with the most recent LDAP RFCs (4510–4519, plus portions of 4520–4532). == Where * {GitHub}[https://github.com/ruby-ldap/ruby-net-ldap] * {ruby-ldap@googlegroups.com}[http://groups.google.com/group/ruby-ldap] == Synopsis See {Net::LDAP on rubydoc.info}[https://www.rubydoc.info/github/ruby-ldap/ruby-net-ldap] for documentation and usage samples. == Requirements Net::LDAP requires a Ruby 2.0.0 compatible 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'. == Extensions This library focuses on the core LDAP RFCs referenced in the description. However, we recognize there are commonly used extensions to the spec that are useful. If there is another library which handles it, we list it here. * {resolv-srv}[https://rubygems.org/gems/resolv-srv]: Support RFC2782 SRV record lookup and failover == Develop This task will run the test suite and the {RuboCop}[https://github.com/bbatsov/rubocop] static code analyzer. rake rubotest CI takes too long? If your local box supports {Docker}[https://www.docker.com/], you can also run integration tests locally. Simply run: script/ldap-docker INTEGRATION=openldap rake test Or, use {Docker Compose}[https://docs.docker.com/compose/]. See docker-compose.yml for available Ruby versions. docker-compose run ci-2.7 CAVEAT: you need to add the following line to /etc/hosts 127.0.0.1 ldap.example.org 127.0.0.1 cert.mismatch.example.org == Release This section is for gem maintainers to cut a new version of the gem. * Check out a new branch `release-VERSION` * Update lib/net/ldap/version.rb to next version number X.X.X following {semver}[http://semver.org/]. * Update `History.rdoc`. Get latest changes with `script/changelog` * Open a pull request with these changes for review * After merging, on the master branch, run `script/release` :include: Contributors.rdoc :include: License.rdoc ruby-net-ldap-0.17.1/Rakefile000066400000000000000000000012171424751475600157500ustar00rootroot00000000000000# -*- ruby encoding: utf-8 -*- # vim: syntax=ruby require 'rake/testtask' require 'rubocop/rake_task' require 'bundler' RuboCop::RakeTask.new Rake::TestTask.new do |t| t.libs << 'test' t.test_files = FileList['test/**/test_*.rb'] t.verbose = true t.description = 'Run tests, set INTEGRATION=openldap to run integration tests, INTEGRATION_HOST and INTEGRATION_PORT are also supported' end desc 'Run tests and RuboCop (RuboCop runs on mri only)' task ci: Bundler.current_ruby.mri? ? [:test, :rubocop] : [:test] desc 'Run tests and RuboCop' task rubotest: [:test, :rubocop] task default: Bundler.current_ruby.mri? ? [:test, :rubocop] : [:test] ruby-net-ldap-0.17.1/ci-run.sh000077500000000000000000000001341424751475600160340ustar00rootroot00000000000000#!/bin/bash set -e gem install bundler bundle check || bundle install bundle exec rake ci ruby-net-ldap-0.17.1/docker-compose.yml000066400000000000000000000043711424751475600177440ustar00rootroot00000000000000version: "3.8" networks: integration_test_network: services: openldap: image: osixia/openldap:1.4.0 networks: integration_test_network: aliases: - ldap.example.org - cert.mismatch.example.org environment: LDAP_TLS_VERIFY_CLIENT: "try" LDAP_SEED_INTERNAL_LDIF_PATH: "/ldif" healthcheck: test: ["CMD", "ldapsearch", "-x", "-s", "base"] interval: 60s start_period: 30s timeout: 5s retries: 1 hostname: "ldap.example.org" volumes: - ./test/fixtures/ldif:/ldif:ro ci-2.5: image: ruby:2.5 entrypoint: /code/ci-run.sh environment: INTEGRATION: openldap INTEGRATION_HOST: ldap.example.org depends_on: - openldap networks: integration_test_network: volumes: - .:/code working_dir: /code ci-2.6: image: ruby:2.7 entrypoint: /code/ci-run.sh environment: INTEGRATION: openldap INTEGRATION_HOST: ldap.example.org depends_on: - openldap networks: integration_test_network: volumes: - .:/code working_dir: /code ci-2.7: image: ruby:2.7 entrypoint: /code/ci-run.sh environment: INTEGRATION: openldap INTEGRATION_HOST: ldap.example.org depends_on: - openldap networks: integration_test_network: volumes: - .:/code working_dir: /code ci-3.0: image: ruby:3.0 entrypoint: /code/ci-run.sh environment: INTEGRATION: openldap INTEGRATION_HOST: ldap.example.org depends_on: - openldap networks: integration_test_network: volumes: - .:/code working_dir: /code ci-truffleruby-21.0.0: image: flavorjones/truffleruby:21.0.0 entrypoint: /code/ci-run.sh environment: INTEGRATION: openldap INTEGRATION_HOST: ldap.example.org depends_on: - openldap networks: integration_test_network: volumes: - .:/code working_dir: /code ci-jruby-9.2: image: jruby:9.2 entrypoint: /code/ci-run.sh environment: INTEGRATION: openldap INTEGRATION_HOST: ldap.example.org depends_on: - openldap networks: integration_test_network: volumes: - .:/code working_dir: /code ruby-net-ldap-0.17.1/lib/000077500000000000000000000000001424751475600150505ustar00rootroot00000000000000ruby-net-ldap-0.17.1/lib/net-ldap.rb000066400000000000000000000000731424751475600171010ustar00rootroot00000000000000# -*- ruby encoding: utf-8 -*- require_relative 'net/ldap' ruby-net-ldap-0.17.1/lib/net/000077500000000000000000000000001424751475600156365ustar00rootroot00000000000000ruby-net-ldap-0.17.1/lib/net/ber.rb000066400000000000000000000333171424751475600167420ustar00rootroot00000000000000# -*- ruby encoding: utf-8 -*- require_relative 'ldap/version' 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. # # # # == 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. # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
NamePrimitive
Constructed
Number
EOC (End-of-Content)P0: 0 (0x0, 0b00000000)
BOOLEANP1: 1 (0x01, 0b00000001)
INTEGERP2: 2 (0x02, 0b00000010)
BIT STRINGP3: 3 (0x03, 0b00000011)
BIT STRINGC3: 35 (0x23, 0b00100011)
OCTET STRINGP4: 4 (0x04, 0b00000100)
OCTET STRINGC4: 36 (0x24, 0b00100100)
NULLP5: 5 (0x05, 0b00000101)
OBJECT IDENTIFIERP6: 6 (0x06, 0b00000110)
Object DescriptorP7: 7 (0x07, 0b00000111)
EXTERNALC8: 40 (0x28, 0b00101000)
REAL (float)P9: 9 (0x09, 0b00001001)
ENUMERATEDP10: 10 (0x0a, 0b00001010)
EMBEDDED PDVC11: 43 (0x2b, 0b00101011)
UTF8StringP12: 12 (0x0c, 0b00001100)
UTF8StringC12: 44 (0x2c, 0b00101100)
RELATIVE-OIDP13: 13 (0x0d, 0b00001101)
SEQUENCE and SEQUENCE OFC16: 48 (0x30, 0b00110000)
SET and SET OFC17: 49 (0x31, 0b00110001)
NumericStringP18: 18 (0x12, 0b00010010)
NumericStringC18: 50 (0x32, 0b00110010)
PrintableStringP19: 19 (0x13, 0b00010011)
PrintableStringC19: 51 (0x33, 0b00110011)
T61StringP20: 20 (0x14, 0b00010100)
T61StringC20: 52 (0x34, 0b00110100)
VideotexStringP21: 21 (0x15, 0b00010101)
VideotexStringC21: 53 (0x35, 0b00110101)
IA5StringP22: 22 (0x16, 0b00010110)
IA5StringC22: 54 (0x36, 0b00110110)
UTCTimeP23: 23 (0x17, 0b00010111)
UTCTimeC23: 55 (0x37, 0b00110111)
GeneralizedTimeP24: 24 (0x18, 0b00011000)
GeneralizedTimeC24: 56 (0x38, 0b00111000)
GraphicStringP25: 25 (0x19, 0b00011001)
GraphicStringC25: 57 (0x39, 0b00111001)
VisibleStringP26: 26 (0x1a, 0b00011010)
VisibleStringC26: 58 (0x3a, 0b00111010)
GeneralStringP27: 27 (0x1b, 0b00011011)
GeneralStringC27: 59 (0x3b, 0b00111011)
UniversalStringP28: 28 (0x1c, 0b00011100)
UniversalStringC28: 60 (0x3c, 0b00111100)
CHARACTER STRINGP29: 29 (0x1d, 0b00011101)
CHARACTER STRINGC29: 61 (0x3d, 0b00111101)
BMPStringP30: 30 (0x1e, 0b00011110)
BMPStringC30: 62 (0x3e, 0b00111110)
ExtendedResponseC107: 139 (0x8b, 0b010001011)
module BER VERSION = Net::LDAP::VERSION ## # 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. # # # # # # # #
BitmaskDefinition
0b00______Universal (ASN.1 Native) Types
0b01______Application Types
0b10______Context-Specific Types
0b11______Private Types
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. # # # # # #
BitmaskDefinition
0b__0_____Primitive
0b__1_____Constructed
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. # # : => { # : => { # => # }, # }, # # === Permitted Object Types # :string:: A string value, represented as BerIdentifiedString. # :integer:: An integer value, represented with Fixnum. # :oid:: 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. # :array:: A sequence, represented as BerIdentifiedArray. # :boolean:: A boolean value, represented as +true+ or +false+. # :null:: 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(&: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 # The binary data provided when parsing the result of the LDAP search # has the encoding 'ASCII-8BIT' (which is basically 'BINARY', or 'unknown'). # # This is the kind of a backtrace showing how the binary `data` comes to # BerIdentifiedString.new(data): # # @conn.read_ber(syntax) # -> StringIO.new(self).read_ber(syntax), i.e. included from module # -> Net::BER::BERParser.read_ber(syntax) # -> (private)Net::BER::BERParser.parse_ber_object(syntax, id, data) # # In the `#parse_ber_object` method `data`, according to its OID, is being # 'casted' to one of the Net::BER:BerIdentifiedXXX classes. # # As we are using LDAP v3 we can safely assume that the data is encoded # in UTF-8 and therefore the only thing to be done when instantiating is to # switch the encoding from 'ASCII-8BIT' to 'UTF-8'. # # Unfortunately, there are some ActiveDirectory specific attributes # (like `objectguid`) that should remain binary (do they really?). # Using the `#valid_encoding?` we can trap this cases. Special cases like # Japanese, Korean, etc. encodings might also profit from this. However # I have no clue how this encodings function. def initialize args super # # Check the encoding of the newly created String and set the encoding # to 'UTF-8' (NOTE: we do NOT change the bytes, but only set the # encoding to 'UTF-8'). return unless encoding == Encoding::BINARY current_encoding = encoding force_encoding('UTF-8') force_encoding(current_encoding) unless valid_encoding? 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_relative 'ber/core_ext' ruby-net-ldap-0.17.1/lib/net/ber/000077500000000000000000000000001424751475600164065ustar00rootroot00000000000000ruby-net-ldap-0.17.1/lib/net/ber/ber_parser.rb000066400000000000000000000125031424751475600210600ustar00rootroot00000000000000# -*- 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 neg = !(data.unpack("C").first & 0x80).zero? int = 0 data.each_byte do |b| int = (int << 8) + (neg ? 255 - b : b) end if neg (int + 1) * -1 else int end 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). # # # # # # # # # # # # # # # # # # # # # # #
RangeLength
0x00 -- 0x7f
0b00000000 -- 0b01111111
0 - 127 bytes
0x80
0b10000000
Indeterminate (end-of-content marker required)
0x81 -- 0xfe
0b10000001 -- 0b11111110
1 - 126 bytes of length as an integer value
0xff
0b11111111
Illegal (reserved for future expansion)
# #-- # 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. # # Yields the object type `id` and the data `content_length` if a block is # given. This is namely to support instrumentation. # # 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 yield id, content_length if block_given? if -1 == content_length raise Net::BER::BerError, "Indeterminite BER content length not implemented." end data = read(content_length) parse_ber_object(syntax, id, data) end end ruby-net-ldap-0.17.1/lib/net/ber/core_ext.rb000066400000000000000000000016601424751475600205460ustar00rootroot00000000000000# -*- ruby encoding: utf-8 -*- require_relative '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_relative 'core_ext/string' # :stopdoc: class String include Net::BER::BERParser include Net::BER::Extensions::String end require_relative 'core_ext/array' # :stopdoc: class Array include Net::BER::Extensions::Array end # :startdoc: require_relative 'core_ext/integer' # :stopdoc: class Integer include Net::BER::Extensions::Integer end # :startdoc: require_relative 'core_ext/true_class' # :stopdoc: class TrueClass include Net::BER::Extensions::TrueClass end # :startdoc: require_relative 'core_ext/false_class' # :stopdoc: class FalseClass include Net::BER::Extensions::FalseClass end # :startdoc: ruby-net-ldap-0.17.1/lib/net/ber/core_ext/000077500000000000000000000000001424751475600202165ustar00rootroot00000000000000ruby-net-ldap-0.17.1/lib/net/ber/core_ext/array.rb000066400000000000000000000067661424751475600217000ustar00rootroot00000000000000# -*- 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 ## # Converts an array into a set of ber control codes # The expected format is [[control_oid, criticality, control_value(optional)]] # [['1.2.840.113556.1.4.805',true]] # def to_ber_control #if our array does not contain at least one array then wrap it in an array before going forward ary = self[0].kind_of?(Array) ? self : [self] ary = ary.collect do |control_sequence| control_sequence.collect(&:to_ber).to_ber_sequence.reject_empty_ber_arrays end ary.to_ber_sequence.reject_empty_ber_arrays end end ruby-net-ldap-0.17.1/lib/net/ber/core_ext/false_class.rb000066400000000000000000000003321424751475600230200ustar00rootroot00000000000000# -*- 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 ruby-net-ldap-0.17.1/lib/net/ber/core_ext/integer.rb000066400000000000000000000043011424751475600221760ustar00rootroot00000000000000# -*- ruby encoding: utf-8 -*- ## # BER extensions to the Integer class, affecting Fixnum and Bignum objects. module Net::BER::Extensions::Integer ## # Converts the Integer to BER format. def to_ber "\002#{to_ber_internal}" end ## # Converts the Integer to BER enumerated format. def to_ber_enumerated "\012#{to_ber_internal}" end ## # Converts the Integer to BER length encoding 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 an Integer. Callers # must prepend the tag byte for the contained value. def to_ber_internal # Compute the byte length, accounting for negative values requiring two's # complement. size = 1 size += 1 until (((self < 0) ? ~self : self) >> (size * 8)).zero? # Padding for positive, negative values. See section 8.5 of ITU-T X.690: # http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf # For positive integers, if most significant bit in an octet is set to one, # pad the result (otherwise it's decoded as a negative value). if self > 0 && (self & (0x80 << (size - 1) * 8)) > 0 size += 1 end # And for negative integers, pad if the most significant bit in the octet # is not set to one (othwerise, it's decoded as positive value). if self < 0 && (self & (0x80 << (size - 1) * 8)) == 0 size += 1 end # Store the size of the Integer in the result result = [size] # Appends bytes to result, starting with higher orders first. Extraction # of bytes is done by right shifting the original Integer 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 ruby-net-ldap-0.17.1/lib/net/ber/core_ext/string.rb000066400000000000000000000040471424751475600220560ustar00rootroot00000000000000# -*- 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) raw_string = raw_utf8_encoded [code].pack('C') + raw_string.length.to_ber_length_encoding + raw_string end ## # Converts a string to a BER string but does *not* encode to UTF-8 first. # This is required for proper representation of binary data for Microsoft # Active Directory def to_ber_bin(code = 0x04) [code].pack('C') + length.to_ber_length_encoding + self end def raw_utf8_encoded if self.respond_to?(:encode) # Strings should be UTF-8 encoded according to LDAP. # However, the BER code is not necessarily valid UTF-8 begin self.encode('UTF-8').force_encoding('ASCII-8BIT') rescue Encoding::UndefinedConversionError self rescue Encoding::ConverterNotFoundError self rescue Encoding::InvalidByteSequenceError self end else self end end private :raw_utf8_encoded ## # 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 def reject_empty_ber_arrays self.gsub(/0\000/n, '') end end ruby-net-ldap-0.17.1/lib/net/ber/core_ext/true_class.rb000066400000000000000000000004501424751475600227060ustar00rootroot00000000000000# -*- 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 # http://tools.ietf.org/html/rfc4511#section-5.1 "\001\001\xFF".force_encoding("ASCII-8BIT") end end ruby-net-ldap-0.17.1/lib/net/ldap.rb000066400000000000000000001557561424751475600171260ustar00rootroot00000000000000# -*- ruby encoding: utf-8 -*- require 'ostruct' module Net # :nodoc: class LDAP begin require 'openssl' ## # Set to +true+ if OpenSSL is available and LDAPS is supported. HasOpenSSL = true rescue LoadError # :stopdoc: HasOpenSSL = false # :startdoc: end end end require 'socket' require_relative 'ber' require_relative 'ldap/pdu' require_relative 'ldap/filter' require_relative 'ldap/dataset' require_relative 'ldap/password' require_relative 'ldap/entry' require_relative 'ldap/instrumentation' require_relative 'ldap/connection' require_relative 'ldap/version' require_relative 'ldap/error' require_relative 'ldap/auth_adapter' require_relative 'ldap/auth_adapter/simple' require_relative 'ldap/auth_adapter/sasl' Net::LDAP::AuthAdapter.register([:simple, :anon, :anonymous], Net::LDAP::AuthAdapter::Simple) Net::LDAP::AuthAdapter.register(:sasl, Net::LDAP::AuthAdapter::Sasl) # == Quick-start for the Impatient # === Quick Example of a user-authentication against an LDAP directory: # # require 'rubygems' # require 'net/ldap' # # ldap = Net::LDAP.new # ldap.host = your_server_ip_address # ldap.port = 389 # ldap.auth "joe_user", "opensesame" # if ldap.bind # # authentication succeeded # else # # authentication failed # end # # # === Quick Example of a search against an LDAP directory: # # require 'rubygems' # require 'net/ldap' # # ldap = Net::LDAP.new :host => server_ip_address, # :port => 389, # :auth => { # :method => :simple, # :username => "cn=manager, dc=example, dc=com", # :password => "opensesame" # } # # filter = Net::LDAP::Filter.eq("cn", "George*") # treebase = "dc=example, dc=com" # # ldap.search(:base => treebase, :filter => filter) do |entry| # puts "DN: #{entry.dn}" # entry.each do |attribute, values| # puts " #{attribute}:" # values.each do |value| # puts " --->#{value}" # end # end # end # # p ldap.get_operation_result # # === Setting connect timeout # # By default, Net::LDAP uses TCP sockets with a connection timeout of 5 seconds. # # This value can be tweaked passing the :connect_timeout parameter. # i.e. # ldap = Net::LDAP.new ..., # :connect_timeout => 3 # # == A Brief Introduction to LDAP # # We're going to provide a quick, informal introduction to LDAP terminology # and typical operations. If you're comfortable with this material, skip # ahead to "How to use Net::LDAP." If you want a more rigorous treatment of # this material, we recommend you start with the various IETF and ITU # standards that relate to LDAP. # # === Entities # LDAP is an Internet-standard protocol used to access directory servers. # The basic search unit is the entity, which corresponds to a person # or other domain-specific object. A directory service which supports the # LDAP protocol typically stores information about a number of entities. # # === Principals # LDAP servers are typically used to access information about people, but # also very often about such items as printers, computers, and other # resources. To reflect this, LDAP uses the term entity, or less # commonly, principal, to denote its basic data-storage unit. # # === Distinguished Names # In LDAP's view of the world, an entity is uniquely identified by a # globally-unique text string called a Distinguished Name, originally # defined in the X.400 standards from which LDAP is ultimately derived. Much # like a DNS hostname, a DN is a "flattened" text representation of a string # of tree nodes. Also like DNS (and unlike Java package names), a DN # expresses a chain of tree-nodes written from left to right in order from # the most-resolved node to the most-general one. # # If you know the DN of a person or other entity, then you can query an # LDAP-enabled directory for information (attributes) about the entity. # Alternatively, you can query the directory for a list of DNs matching a # set of criteria that you supply. # # === Attributes # # In the LDAP view of the world, a DN uniquely identifies an entity. # Information about the entity is stored as a set of Attributes. An # attribute is a text string which is associated with zero or more values. # Most LDAP-enabled directories store a well-standardized range of # attributes, and constrain their values according to standard rules. # # A good example of an attribute is sn, which stands for "Surname." # This attribute is generally used to store a person's surname, or last # name. Most directories enforce the standard convention that an entity's # sn attribute have exactly one value. In LDAP jargon, that # means that sn must be present and single-valued. # # Another attribute is mail, which is used to store email # addresses. (No, there is no attribute called "email, " perhaps because # X.400 terminology predates the invention of the term email.) # mail differs from sn in that most directories permit any # number of values for the mail attribute, including zero. # # === Tree-Base # We said above that X.400 Distinguished Names are globally unique. # In a manner reminiscent of DNS, LDAP supposes that each directory server # contains authoritative attribute data for a set of DNs corresponding to a # specific sub-tree of the (notional) global directory tree. This subtree is # generally configured into a directory server when it is created. It # matters for this discussion because most servers will not allow you to # query them unless you specify a correct tree-base. # # Let's say you work for the engineering department of Big Company, Inc., # whose internet domain is bigcompany.com. You may find that your # departmental directory is stored in a server with a defined tree-base of # ou=engineering, dc=bigcompany, dc=com # You will need to supply this string as the tree-base when querying # this directory. (Ou is a very old X.400 term meaning "organizational # unit." Dc is a more recent term meaning "domain component.") # # === LDAP Versions # (stub, discuss v2 and v3) # # === LDAP Operations # The essential operations are: #bind, #search, #add, #modify, #delete, and # #rename. # # ==== Bind # #bind supplies a user's authentication credentials to a server, which in # turn verifies or rejects them. There is a range of possibilities for # credentials, but most directories support a simple username and password # authentication. # # Taken by itself, #bind can be used to authenticate a user against # information stored in a directory, for example to permit or deny access to # some other resource. In terms of the other LDAP operations, most # directories require a successful #bind to be performed before the other # operations will be permitted. Some servers permit certain operations to be # performed with an "anonymous" binding, meaning that no credentials are # presented by the user. (We're glossing over a lot of platform-specific # detail here.) # # ==== Search # Calling #search against the directory involves specifying a treebase, a # set of search filters, and a list of attribute values. The filters # specify ranges of possible values for particular attributes. Multiple # filters can be joined together with AND, OR, and NOT operators. A server # will respond to a #search by returning a list of matching DNs together # with a set of attribute values for each entity, depending on what # attributes the search requested. # # ==== Add # #add specifies a new DN and an initial set of attribute values. If the # operation succeeds, a new entity with the corresponding DN and attributes # is added to the directory. # # ==== Modify # #modify specifies an entity DN, and a list of attribute operations. # #modify is used to change the attribute values stored in the directory for # a particular entity. #modify may add or delete attributes (which are lists # of values) or it change attributes by adding to or deleting from their # values. Net::LDAP provides three easier methods to modify an entry's # attribute values: #add_attribute, #replace_attribute, and # #delete_attribute. # # ==== Delete # #delete specifies an entity DN. If it succeeds, the entity and all its # attributes is removed from the directory. # # ==== Rename (or Modify RDN) # #rename (or #modify_rdn) is an operation added to version 3 of the LDAP # protocol. It responds to the often-arising need to change the DN of an # entity without discarding its attribute values. In earlier LDAP versions, # the only way to do this was to delete the whole entity and add it again # with a different DN. # # #rename works by taking an "old" DN (the one to change) and a "new RDN, " # which is the left-most part of the DN string. If successful, #rename # changes the entity DN so that its left-most node corresponds to the new # RDN given in the request. (RDN, or "relative distinguished name, " denotes # a single tree-node as expressed in a DN, which is a chain of tree nodes.) # # == How to use Net::LDAP # To access Net::LDAP functionality in your Ruby programs, start by # requiring the library: # # require 'net/ldap' # # If you installed the Gem version of Net::LDAP, and depending on your # version of Ruby and rubygems, you _may_ also need to require rubygems # explicitly: # # require 'rubygems' # require 'net/ldap' # # Most operations with Net::LDAP start by instantiating a Net::LDAP object. # The constructor for this object takes arguments specifying the network # location (address and port) of the LDAP server, and also the binding # (authentication) credentials, typically a username and password. Given an # object of class Net:LDAP, you can then perform LDAP operations by calling # instance methods on the object. These are documented with usage examples # below. # # The Net::LDAP library is designed to be very disciplined about how it # makes network connections to servers. This is different from many of the # standard native-code libraries that are provided on most platforms, which # share bloodlines with the original Netscape/Michigan LDAP client # implementations. These libraries sought to insulate user code from the # workings of the network. This is a good idea of course, but the practical # effect has been confusing and many difficult bugs have been caused by the # opacity of the native libraries, and their variable behavior across # platforms. # # In general, Net::LDAP instance methods which invoke server operations make # a connection to the server when the method is called. They execute the # operation (typically binding first) and then disconnect from the server. # The exception is Net::LDAP#open, which makes a connection to the server # and then keeps it open while it executes a user-supplied block. # Net::LDAP#open closes the connection on completion of the block. class Net::LDAP include Net::LDAP::Instrumentation SearchScope_BaseObject = 0 SearchScope_SingleLevel = 1 SearchScope_WholeSubtree = 2 SearchScopes = [SearchScope_BaseObject, SearchScope_SingleLevel, SearchScope_WholeSubtree] DerefAliases_Never = 0 DerefAliases_Search = 1 DerefAliases_Find = 2 DerefAliases_Always = 3 DerefAliasesArray = [DerefAliases_Never, DerefAliases_Search, DerefAliases_Find, DerefAliases_Always] primitive = { 2 => :null } # UnbindRequest body constructed = { 0 => :array, # BindRequest 1 => :array, # BindResponse 2 => :array, # UnbindRequest 3 => :array, # SearchRequest 4 => :array, # SearchData 5 => :array, # SearchResult 6 => :array, # ModifyRequest 7 => :array, # ModifyResponse 8 => :array, # AddRequest 9 => :array, # AddResponse 10 => :array, # DelRequest 11 => :array, # DelResponse 12 => :array, # ModifyRdnRequest 13 => :array, # ModifyRdnResponse 14 => :array, # CompareRequest 15 => :array, # CompareResponse 16 => :array, # AbandonRequest 19 => :array, # SearchResultReferral 24 => :array, # Unsolicited Notification } application = { :primitive => primitive, :constructed => constructed, } primitive = { 0 => :string, # password 1 => :string, # Kerberos v4 2 => :string, # Kerberos v5 3 => :string, # SearchFilter-extensible 4 => :string, # SearchFilter-extensible 7 => :string, # serverSaslCreds } constructed = { 0 => :array, # RFC-2251 Control and Filter-AND 1 => :array, # SearchFilter-OR 2 => :array, # SearchFilter-NOT 3 => :array, # Seach referral 4 => :array, # unknown use in Microsoft Outlook 5 => :array, # SearchFilter-GE 6 => :array, # SearchFilter-LE 7 => :array, # serverSaslCreds 9 => :array, # SearchFilter-extensible } context_specific = { :primitive => primitive, :constructed => constructed, } universal = { constructed: { 107 => :array, #ExtendedResponse (PasswdModifyResponseValue) }, } AsnSyntax = Net::BER.compile_syntax(:application => application, :universal => universal, :context_specific => context_specific) DefaultHost = "127.0.0.1" DefaultPort = 389 DefaultAuth = { :method => :anonymous } DefaultTreebase = "dc=com" DefaultForceNoPage = false StartTlsOid = '1.3.6.1.4.1.1466.20037' PasswdModifyOid = '1.3.6.1.4.1.4203.1.11.1' # https://tools.ietf.org/html/rfc4511#section-4.1.9 # https://tools.ietf.org/html/rfc4511#appendix-A ResultCodeSuccess = 0 ResultCodeOperationsError = 1 ResultCodeProtocolError = 2 ResultCodeTimeLimitExceeded = 3 ResultCodeSizeLimitExceeded = 4 ResultCodeCompareFalse = 5 ResultCodeCompareTrue = 6 ResultCodeAuthMethodNotSupported = 7 ResultCodeStrongerAuthRequired = 8 ResultCodeReferral = 10 ResultCodeAdminLimitExceeded = 11 ResultCodeUnavailableCriticalExtension = 12 ResultCodeConfidentialityRequired = 13 ResultCodeSaslBindInProgress = 14 ResultCodeNoSuchAttribute = 16 ResultCodeUndefinedAttributeType = 17 ResultCodeInappropriateMatching = 18 ResultCodeConstraintViolation = 19 ResultCodeAttributeOrValueExists = 20 ResultCodeInvalidAttributeSyntax = 21 ResultCodeNoSuchObject = 32 ResultCodeAliasProblem = 33 ResultCodeInvalidDNSyntax = 34 ResultCodeAliasDereferencingProblem = 36 ResultCodeInappropriateAuthentication = 48 ResultCodeInvalidCredentials = 49 ResultCodeInsufficientAccessRights = 50 ResultCodeBusy = 51 ResultCodeUnavailable = 52 ResultCodeUnwillingToPerform = 53 ResultCodeNamingViolation = 64 ResultCodeObjectClassViolation = 65 ResultCodeNotAllowedOnNonLeaf = 66 ResultCodeNotAllowedOnRDN = 67 ResultCodeEntryAlreadyExists = 68 ResultCodeObjectClassModsProhibited = 69 ResultCodeAffectsMultipleDSAs = 71 ResultCodeOther = 80 # https://tools.ietf.org/html/rfc4511#appendix-A.1 ResultCodesNonError = [ ResultCodeSuccess, ResultCodeCompareFalse, ResultCodeCompareTrue, ResultCodeReferral, ResultCodeSaslBindInProgress, ] # nonstandard list of "successful" result codes for searches ResultCodesSearchSuccess = [ ResultCodeSuccess, ResultCodeTimeLimitExceeded, ResultCodeSizeLimitExceeded, ] # map of result code to human message ResultStrings = { ResultCodeSuccess => "Success", ResultCodeOperationsError => "Operations Error", ResultCodeProtocolError => "Protocol Error", ResultCodeTimeLimitExceeded => "Time Limit Exceeded", ResultCodeSizeLimitExceeded => "Size Limit Exceeded", ResultCodeCompareFalse => "False Comparison", ResultCodeCompareTrue => "True Comparison", ResultCodeAuthMethodNotSupported => "Auth Method Not Supported", ResultCodeStrongerAuthRequired => "Stronger Auth Needed", ResultCodeReferral => "Referral", ResultCodeAdminLimitExceeded => "Admin Limit Exceeded", ResultCodeUnavailableCriticalExtension => "Unavailable critical extension", ResultCodeConfidentialityRequired => "Confidentiality Required", ResultCodeSaslBindInProgress => "saslBindInProgress", ResultCodeNoSuchAttribute => "No Such Attribute", ResultCodeUndefinedAttributeType => "Undefined Attribute Type", ResultCodeInappropriateMatching => "Inappropriate Matching", ResultCodeConstraintViolation => "Constraint Violation", ResultCodeAttributeOrValueExists => "Attribute or Value Exists", ResultCodeInvalidAttributeSyntax => "Invalide Attribute Syntax", ResultCodeNoSuchObject => "No Such Object", ResultCodeAliasProblem => "Alias Problem", ResultCodeInvalidDNSyntax => "Invalid DN Syntax", ResultCodeAliasDereferencingProblem => "Alias Dereferencing Problem", ResultCodeInappropriateAuthentication => "Inappropriate Authentication", ResultCodeInvalidCredentials => "Invalid Credentials", ResultCodeInsufficientAccessRights => "Insufficient Access Rights", ResultCodeBusy => "Busy", ResultCodeUnavailable => "Unavailable", ResultCodeUnwillingToPerform => "Unwilling to perform", ResultCodeNamingViolation => "Naming Violation", ResultCodeObjectClassViolation => "Object Class Violation", ResultCodeNotAllowedOnNonLeaf => "Not Allowed On Non-Leaf", ResultCodeNotAllowedOnRDN => "Not Allowed On RDN", ResultCodeEntryAlreadyExists => "Entry Already Exists", ResultCodeObjectClassModsProhibited => "ObjectClass Modifications Prohibited", ResultCodeAffectsMultipleDSAs => "Affects Multiple DSAs", ResultCodeOther => "Other", } module LDAPControls PAGED_RESULTS = "1.2.840.113556.1.4.319" # Microsoft evil from RFC 2696 SORT_REQUEST = "1.2.840.113556.1.4.473" SORT_RESPONSE = "1.2.840.113556.1.4.474" DELETE_TREE = "1.2.840.113556.1.4.805" end def self.result2string(code) #:nodoc: ResultStrings[code] || "unknown result (#{code})" end attr_accessor :host attr_accessor :port attr_accessor :hosts attr_accessor :base # Instantiate an object of type Net::LDAP to perform directory operations. # This constructor takes a Hash containing arguments, all of which are # either optional or may be specified later with other methods as # described below. The following arguments are supported: # * :host => the LDAP server's IP-address (default 127.0.0.1) # * :port => the LDAP server's TCP port (default 389) # * :hosts => an enumerable of pairs of hosts and corresponding ports with # which to attempt opening connections (default [[host, port]]) # * :auth => a Hash containing authorization parameters. Currently # supported values include: {:method => :anonymous} and {:method => # :simple, :username => your_user_name, :password => your_password } # The password parameter may be a Proc that returns a String. # * :base => a default treebase parameter for searches performed against # the LDAP server. If you don't give this value, then each call to # #search must specify a treebase parameter. If you do give this value, # then it will be used in subsequent calls to #search that do not # specify a treebase. If you give a treebase value in any particular # call to #search, that value will override any treebase value you give # here. # * :force_no_page => Set to true to prevent paged results even if your # server says it supports them. This is a fix for MS Active Directory # * :instrumentation_service => An object responsible for instrumenting # operations, compatible with ActiveSupport::Notifications' public API. # * :encryption => specifies the encryption to be used in communicating # with the LDAP server. The value must be a Hash containing additional # parameters, which consists of two keys: # method: - :simple_tls or :start_tls # tls_options: - Hash of options for that method # The :simple_tls encryption method encrypts all communications # with the LDAP server. It completely establishes SSL/TLS encryption with # the LDAP server before any LDAP-protocol data is exchanged. There is no # plaintext negotiation and no special encryption-request controls are # sent to the server. The :simple_tls option is the simplest, easiest # way to encrypt communications between Net::LDAP and LDAP servers. # If you get communications or protocol errors when using this option, # check with your LDAP server administrator. Pay particular attention # to the TCP port you are connecting to. It's impossible for an LDAP # server to support plaintext LDAP communications and simple TLS # connections on the same port. The standard TCP port for unencrypted # LDAP connections is 389, but the standard port for simple-TLS # encrypted connections is 636. Be sure you are using the correct port. # The :start_tls like the :simple_tls encryption method also encrypts all # communcations with the LDAP server. With the exception that it operates # over the standard TCP port. # # To validate the LDAP server's certificate (a security must if you're # talking over the public internet), you need to set :tls_options # something like this... # # Net::LDAP.new( # # ... set host, bind dn, etc ... # encryption: { # method: :simple_tls, # tls_options: OpenSSL::SSL::SSLContext::DEFAULT_PARAMS, # } # ) # # The above will use the operating system-provided store of CA # certificates to validate your LDAP server's cert. # If cert validation fails, it'll happen during the #bind # whenever you first try to open a connection to the server. # Those methods will throw Net::LDAP::ConnectionError with # a message about certificate verify failing. If your # LDAP server's certificate is signed by DigiCert, Comodo, etc., # you're probably good. If you've got a self-signed cert but it's # been added to the host's OS-maintained CA store (e.g. on Debian # add foobar.crt to /usr/local/share/ca-certificates/ and run # `update-ca-certificates`), then the cert should pass validation. # To ignore the OS's CA store, put your CA in a PEM-encoded file and... # # encryption: { # method: :simple_tls, # tls_options: { ca_file: '/path/to/my-little-ca.pem', # ssl_version: 'TLSv1_1' }, # } # # As you might guess, the above example also fails the connection # if the client can't negotiate TLS v1.1. # tls_options is ultimately passed to OpenSSL::SSL::SSLContext#set_params # For more details, see # http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html # # Instantiating a Net::LDAP object does not result in network # traffic to the LDAP server. It simply stores the connection and binding # parameters in the object. That's why Net::LDAP.new doesn't throw # cert validation errors itself; #bind does instead. def initialize(args = {}) @host = args[:host] || DefaultHost @port = args[:port] || DefaultPort @hosts = args[:hosts] @verbose = false # Make this configurable with a switch on the class. @auth = args[:auth] || DefaultAuth @base = args[:base] || DefaultTreebase @force_no_page = args[:force_no_page] || DefaultForceNoPage @encryption = normalize_encryption(args[:encryption]) # may be nil @connect_timeout = args[:connect_timeout] if pr = @auth[:password] and pr.respond_to?(:call) @auth[:password] = pr.call end @instrumentation_service = args[:instrumentation_service] # This variable is only set when we are created with LDAP::open. All of # our internal methods will connect using it, or else they will create # their own. @open_connection = nil end # Convenience method to specify authentication credentials to the LDAP # server. Currently supports simple authentication requiring a username # and password. # # Observe that on most LDAP servers, the username is a complete DN. # However, with A/D, it's often possible to give only a user-name rather # than a complete DN. In the latter case, beware that many A/D servers are # configured to permit anonymous (uncredentialled) binding, and will # silently accept your binding as anonymous if you give an unrecognized # username. This is not usually what you want. (See # #get_operation_result.) # # Important: The password argument may be a Proc that returns a # string. This makes it possible for you to write client programs that # solicit passwords from users or from other data sources without showing # them in your code or on command lines. # # require 'net/ldap' # # ldap = Net::LDAP.new # ldap.host = server_ip_address # ldap.authenticate "cn=Your Username, cn=Users, dc=example, dc=com", "your_psw" # # Alternatively (with a password block): # # require 'net/ldap' # # ldap = Net::LDAP.new # ldap.host = server_ip_address # psw = proc { your_psw_function } # ldap.authenticate "cn=Your Username, cn=Users, dc=example, dc=com", psw # def authenticate(username, password) password = password.call if password.respond_to?(:call) @auth = { :method => :simple, :username => username, :password => password, } end alias_method :auth, :authenticate # Convenience method to specify encryption characteristics for connections # to LDAP servers. Called implicitly by #new and #open, but may also be # called by user code if desired. The single argument is generally a Hash # (but see below for convenience alternatives). This implementation is # currently a stub, supporting only a few encryption alternatives. As # additional capabilities are added, more configuration values will be # added here. # # This method is deprecated. # def encryption(args) warn "Deprecation warning: please give :encryption option as a Hash to Net::LDAP.new" return if args.nil? @encryption = normalize_encryption(args) end # #open takes the same parameters as #new. #open makes a network # connection to the LDAP server and then passes a newly-created Net::LDAP # object to the caller-supplied block. Within the block, you can call any # of the instance methods of Net::LDAP to perform operations against the # LDAP directory. #open will perform all the operations in the # user-supplied block on the same network connection, which will be closed # automatically when the block finishes. # # # (PSEUDOCODE) # auth = { :method => :simple, :username => username, :password => password } # Net::LDAP.open(:host => ipaddress, :port => 389, :auth => auth) do |ldap| # ldap.search(...) # ldap.add(...) # ldap.modify(...) # end def self.open(args) ldap1 = new(args) ldap1.open { |ldap| yield ldap } end # Returns a meaningful result any time after a protocol operation (#bind, # #search, #add, #modify, #rename, #delete) has completed. It returns an # #OpenStruct containing an LDAP result code (0 means success), and a # human-readable string. # # unless ldap.bind # puts "Result: #{ldap.get_operation_result.code}" # puts "Message: #{ldap.get_operation_result.message}" # end # # Certain operations return additional information, accessible through # members of the object returned from #get_operation_result. Check # #get_operation_result.error_message and # #get_operation_result.matched_dn. # #-- # Modified the implementation, 20Mar07. We might get a hash of LDAP # response codes instead of a simple numeric code. #++ def get_operation_result result = @result os = OpenStruct.new if result.is_a?(Net::LDAP::PDU) os.extended_response = result.extended_response result = result.result end if result.is_a?(Hash) # We might get a hash of LDAP response codes instead of a simple # numeric code. os.code = (result[:resultCode] || "").to_i os.error_message = result[:errorMessage] os.matched_dn = result[:matchedDN] elsif result os.code = result else os.code = Net::LDAP::ResultCodeSuccess end os.message = Net::LDAP.result2string(os.code) os end # Opens a network connection to the server and then passes self # to the caller-supplied block. The connection is closed when the block # completes. Used for executing multiple LDAP operations without requiring # a separate network connection (and authentication) for each one. # Note: You do not need to log-in or "bind" to the server. This # will be done for you automatically. For an even simpler approach, see # the class method Net::LDAP#open. # # # (PSEUDOCODE) # auth = { :method => :simple, :username => username, :password => password } # ldap = Net::LDAP.new(:host => ipaddress, :port => 389, :auth => auth) # ldap.open do |ldap| # ldap.search(...) # ldap.add(...) # ldap.modify(...) # end def open # First we make a connection and then a binding, but we don't do # anything with the bind results. We then pass self to the caller's # block, where he will execute his LDAP operations. Of course they will # all generate auth failures if the bind was unsuccessful. raise Net::LDAP::AlreadyOpenedError, "Open already in progress" if @open_connection instrument "open.net_ldap" do |payload| begin @open_connection = new_connection payload[:connection] = @open_connection payload[:bind] = @result = @open_connection.bind(@auth) yield self ensure @open_connection.close if @open_connection @open_connection = nil end end end # Searches the LDAP directory for directory entries. Takes a hash argument # with parameters. Supported parameters include: # * :base (a string specifying the tree-base for the search); # * :filter (an object of type Net::LDAP::Filter, defaults to # objectclass=*); # * :attributes (a string or array of strings specifying the LDAP # attributes to return from the server); # * :return_result (a boolean specifying whether to return a result set). # * :attributes_only (a boolean flag, defaults false) # * :scope (one of: Net::LDAP::SearchScope_BaseObject, # Net::LDAP::SearchScope_SingleLevel, # Net::LDAP::SearchScope_WholeSubtree. Default is WholeSubtree.) # * :size (an integer indicating the maximum number of search entries to # return. Default is zero, which signifies no limit.) # * :time (an integer restricting the maximum time in seconds allowed for a search. Default is zero, no time limit RFC 4511 4.5.1.5) # * :deref (one of: Net::LDAP::DerefAliases_Never, Net::LDAP::DerefAliases_Search, # Net::LDAP::DerefAliases_Find, Net::LDAP::DerefAliases_Always. Default is Never.) # # #search queries the LDAP server and passes each entry to the # caller-supplied block, as an object of type Net::LDAP::Entry. If the # search returns 1000 entries, the block will be called 1000 times. If the # search returns no entries, the block will not be called. # # #search returns either a result-set or a boolean, depending on the value # of the :return_result argument. The default behavior is to # return a result set, which is an Array of objects of class # Net::LDAP::Entry. If you request a result set and #search fails with an # error, it will return nil. Call #get_operation_result to get the error # information returned by # the LDAP server. # # When :return_result => false, #search will return only a # Boolean, to indicate whether the operation succeeded. This can improve # performance with very large result sets, because the library can discard # each entry from memory after your block processes it. # # treebase = "dc=example, dc=com" # filter = Net::LDAP::Filter.eq("mail", "a*.com") # attrs = ["mail", "cn", "sn", "objectclass"] # ldap.search(:base => treebase, :filter => filter, :attributes => attrs, # :return_result => false) do |entry| # puts "DN: #{entry.dn}" # entry.each do |attr, values| # puts ".......#{attr}:" # values.each do |value| # puts " #{value}" # end # end # end def search(args = {}) unless args[:ignore_server_caps] args[:paged_searches_supported] = paged_searches_supported? end args[:base] ||= @base return_result_set = args[:return_result] != false result_set = return_result_set ? [] : nil instrument "search.net_ldap", args do |payload| @result = use_connection(args) do |conn| conn.search(args) do |entry| result_set << entry if result_set yield entry if block_given? end end if return_result_set unless @result.nil? if ResultCodesSearchSuccess.include?(@result.result_code) result_set end end else @result.success? end end end # #bind connects to an LDAP server and requests authentication based on # the :auth parameter passed to #open or #new. It takes no # parameters. # # User code does not need to call #bind directly. It will be called # implicitly by the library whenever you invoke an LDAP operation, such as # #search or #add. # # It is useful, however, to call #bind in your own code when the only # operation you intend to perform against the directory is to validate a # login credential. #bind returns true or false to indicate whether the # binding was successful. Reasons for failure include malformed or # unrecognized usernames and incorrect passwords. Use # #get_operation_result to find out what happened in case of failure. # # Here's a typical example using #bind to authenticate a credential which # was (perhaps) solicited from the user of a web site: # # require 'net/ldap' # ldap = Net::LDAP.new # ldap.host = your_server_ip_address # ldap.port = 389 # ldap.auth your_user_name, your_user_password # if ldap.bind # # authentication succeeded # else # # authentication failed # p ldap.get_operation_result # end # # Here's a more succinct example which does exactly the same thing, but # collects all the required parameters into arguments: # # require 'net/ldap' # ldap = Net::LDAP.new(:host => your_server_ip_address, :port => 389) # if ldap.bind(:method => :simple, :username => your_user_name, # :password => your_user_password) # # authentication succeeded # else # # authentication failed # p ldap.get_operation_result # end # # You don't need to pass a user-password as a String object to bind. You # can also pass a Ruby Proc object which returns a string. This will cause # bind to execute the Proc (which might then solicit input from a user # with console display suppressed). The String value returned from the # Proc is used as the password. # # You don't have to create a new instance of Net::LDAP every time you # perform a binding in this way. If you prefer, you can cache the # Net::LDAP object and re-use it to perform subsequent bindings, # provided you call #auth to specify a new credential before # calling #bind. Otherwise, you'll just re-authenticate the previous user! # (You don't need to re-set the values of #host and #port.) As noted in # the documentation for #auth, the password parameter can be a Ruby Proc # instead of a String. def bind(auth = @auth) instrument "bind.net_ldap" do |payload| if @open_connection payload[:connection] = @open_connection payload[:bind] = @result = @open_connection.bind(auth) else begin conn = new_connection payload[:connection] = conn payload[:bind] = @result = conn.bind(auth) ensure conn.close if conn end end @result.success? end end # #bind_as is for testing authentication credentials. # # As described under #bind, most LDAP servers require that you supply a # complete DN as a binding-credential, along with an authenticator such as # a password. But for many applications (such as authenticating users to a # Rails application), you often don't have a full DN to identify the user. # You usually get a simple identifier like a username or an email address, # along with a password. #bind_as allows you to authenticate these # user-identifiers. # # #bind_as is a combination of a search and an LDAP binding. First, it # connects and binds to the directory as normal. Then it searches the # directory for an entry corresponding to the email address, username, or # other string that you supply. If the entry exists, then #bind_as will # re-bind as that user with the password (or other authenticator) # that you supply. # # #bind_as takes the same parameters as #search, with the addition of # an authenticator. Currently, this authenticator must be # :password. Its value may be either a String, or a +proc+ that # returns a String. #bind_as returns +false+ on failure. On success, it # returns a result set, just as #search does. This result set is an Array # of objects of type Net::LDAP::Entry. It contains the directory # attributes corresponding to the user. (Just test whether the return # value is logically true, if you don't need this additional information.) # # Here's how you would use #bind_as to authenticate an email address and # password: # # require 'net/ldap' # # user, psw = "joe_user@yourcompany.com", "joes_psw" # # ldap = Net::LDAP.new # ldap.host = "192.168.0.100" # ldap.port = 389 # ldap.auth "cn=manager, dc=yourcompany, dc=com", "topsecret" # # result = ldap.bind_as(:base => "dc=yourcompany, dc=com", # :filter => "(mail=#{user})", # :password => psw) # if result # puts "Authenticated #{result.first.dn}" # else # puts "Authentication FAILED." # end def bind_as(args = {}) result = false open do |me| rs = search args if rs and rs.first and dn = rs.first.dn password = args[:password] password = password.call if password.respond_to?(:call) result = rs if bind(:method => :simple, :username => dn, :password => password) end end result end # Adds a new entry to the remote LDAP server. # Supported arguments: # :dn :: Full DN of the new entry # :attributes :: Attributes of the new entry. # # The attributes argument is supplied as a Hash keyed by Strings or # Symbols giving the attribute name, and mapping to Strings or Arrays of # Strings giving the actual attribute values. Observe that most LDAP # directories enforce schema constraints on the attributes contained in # entries. #add will fail with a server-generated error if your attributes # violate the server-specific constraints. # # Here's an example: # # dn = "cn=George Smith, ou=people, dc=example, dc=com" # attr = { # :cn => "George Smith", # :objectclass => ["top", "inetorgperson"], # :sn => "Smith", # :mail => "gsmith@example.com" # } # Net::LDAP.open(:host => host) do |ldap| # ldap.add(:dn => dn, :attributes => attr) # end def add(args) instrument "add.net_ldap", args do |payload| @result = use_connection(args) do |conn| conn.add(args) end @result.success? end end # Modifies the attribute values of a particular entry on the LDAP # directory. Takes a hash with arguments. Supported arguments are: # :dn :: (the full DN of the entry whose attributes are to be modified) # :operations :: (the modifications to be performed, detailed next) # # This method returns True or False to indicate whether the operation # succeeded or failed, with extended information available by calling # #get_operation_result. # # Also see #add_attribute, #replace_attribute, or #delete_attribute, which # provide simpler interfaces to this functionality. # # The LDAP protocol provides a full and well thought-out set of operations # for changing the values of attributes, but they are necessarily somewhat # complex and not always intuitive. If these instructions are confusing or # incomplete, please send us email or create an issue on GitHub. # # The :operations parameter to #modify takes an array of # operation-descriptors. Each individual operation is specified in one # element of the array, and most LDAP servers will attempt to perform the # operations in order. # # Each of the operations appearing in the Array must itself be an Array # with exactly three elements: # an operator :: must be :add, :replace, or :delete # an attribute name :: the attribute name (string or symbol) to modify # a value :: either a string or an array of strings. # # The :add operator will, unsurprisingly, add the specified values to the # specified attribute. If the attribute does not already exist, :add will # create it. Most LDAP servers will generate an error if you try to add a # value that already exists. # # :replace will erase the current value(s) for the specified attribute, if # there are any, and replace them with the specified value(s). # # :delete will remove the specified value(s) from the specified attribute. # If you pass nil, an empty string, or an empty array as the value # parameter to a :delete operation, the _entire_ _attribute_ will be # deleted, along with all of its values. # # For example: # # dn = "mail=modifyme@example.com, ou=people, dc=example, dc=com" # ops = [ # [:add, :mail, "aliasaddress@example.com"], # [:replace, :mail, ["newaddress@example.com", "newalias@example.com"]], # [:delete, :sn, nil] # ] # ldap.modify :dn => dn, :operations => ops # # (This example is contrived since you probably wouldn't add a mail # value right before replacing the whole attribute, but it shows that # order of execution matters. Also, many LDAP servers won't let you delete # SN because that would be a schema violation.) # # It's essential to keep in mind that if you specify more than one # operation in a call to #modify, most LDAP servers will attempt to # perform all of the operations in the order you gave them. This matters # because you may specify operations on the same attribute which must be # performed in a certain order. # # Most LDAP servers will _stop_ processing your modifications if one of # them causes an error on the server (such as a schema-constraint # violation). If this happens, you will probably get a result code from # the server that reflects only the operation that failed, and you may or # may not get extended information that will tell you which one failed. # #modify has no notion of an atomic transaction. If you specify a chain # of modifications in one call to #modify, and one of them fails, the # preceding ones will usually not be "rolled back", resulting in a # partial update. This is a limitation of the LDAP protocol, not of # Net::LDAP. # # The lack of transactional atomicity in LDAP means that you're usually # better off using the convenience methods #add_attribute, # #replace_attribute, and #delete_attribute, which are wrappers over # #modify. However, certain LDAP servers may provide concurrency # semantics, in which the several operations contained in a single #modify # call are not interleaved with other modification-requests received # simultaneously by the server. It bears repeating that this concurrency # does _not_ imply transactional atomicity, which LDAP does not provide. def modify(args) instrument "modify.net_ldap", args do |payload| @result = use_connection(args) do |conn| conn.modify(args) end @result.success? end end # Password Modify # # Change existing password: # # dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com' # auth = { # method: :simple, # username: dn, # password: 'passworD1' # } # ldap.password_modify(dn: dn, # auth: auth, # old_password: 'passworD1', # new_password: 'passworD2') # # Or get the LDAP server to generate a password for you: # # dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com' # auth = { # method: :simple, # username: dn, # password: 'passworD1' # } # ldap.password_modify(dn: dn, # auth: auth, # old_password: 'passworD1') # # ldap.get_operation_result.extended_response[0][0] #=> 'VtcgGf/G' # def password_modify(args) instrument "modify_password.net_ldap", args do |payload| @result = use_connection(args) do |conn| conn.password_modify(args) end @result.success? end end # Add a value to an attribute. Takes the full DN of the entry to modify, # the name (Symbol or String) of the attribute, and the value (String or # Array). If the attribute does not exist (and there are no schema # violations), #add_attribute will create it with the caller-specified # values. If the attribute already exists (and there are no schema # violations), the caller-specified values will be _added_ to the values # already present. # # Returns True or False to indicate whether the operation succeeded or # failed, with extended information available by calling # #get_operation_result. See also #replace_attribute and # #delete_attribute. # # dn = "cn=modifyme, dc=example, dc=com" # ldap.add_attribute dn, :mail, "newmailaddress@example.com" def add_attribute(dn, attribute, value) modify(:dn => dn, :operations => [[:add, attribute, value]]) end # Replace the value of an attribute. #replace_attribute can be thought of # as equivalent to calling #delete_attribute followed by #add_attribute. # It takes the full DN of the entry to modify, the name (Symbol or String) # of the attribute, and the value (String or Array). If the attribute does # not exist, it will be created with the caller-specified value(s). If the # attribute does exist, its values will be _discarded_ and replaced with # the caller-specified values. # # Returns True or False to indicate whether the operation succeeded or # failed, with extended information available by calling # #get_operation_result. See also #add_attribute and #delete_attribute. # # dn = "cn=modifyme, dc=example, dc=com" # ldap.replace_attribute dn, :mail, "newmailaddress@example.com" def replace_attribute(dn, attribute, value) modify(:dn => dn, :operations => [[:replace, attribute, value]]) end # Delete an attribute and all its values. Takes the full DN of the entry # to modify, and the name (Symbol or String) of the attribute to delete. # # Returns True or False to indicate whether the operation succeeded or # failed, with extended information available by calling # #get_operation_result. See also #add_attribute and #replace_attribute. # # dn = "cn=modifyme, dc=example, dc=com" # ldap.delete_attribute dn, :mail def delete_attribute(dn, attribute) modify(:dn => dn, :operations => [[:delete, attribute, nil]]) end # Rename an entry on the remote DIS by changing the last RDN of its DN. # # _Documentation_ _stub_ def rename(args) instrument "rename.net_ldap", args do |payload| @result = use_connection(args) do |conn| conn.rename(args) end @result.success? end end alias_method :modify_rdn, :rename # Delete an entry from the LDAP directory. Takes a hash of arguments. The # only supported argument is :dn, which must give the complete DN of the # entry to be deleted. # # Returns True or False to indicate whether the delete succeeded. Extended # status information is available by calling #get_operation_result. # # dn = "mail=deleteme@example.com, ou=people, dc=example, dc=com" # ldap.delete :dn => dn def delete(args) instrument "delete.net_ldap", args do |payload| @result = use_connection(args) do |conn| conn.delete(args) end @result.success? end end # Delete an entry from the LDAP directory along with all subordinate entries. # the regular delete method will fail to delete an entry if it has subordinate # entries. This method sends an extra control code to tell the LDAP server # to do a tree delete. ('1.2.840.113556.1.4.805') # # If the LDAP server does not support the DELETE_TREE control code, subordinate # entries are deleted recursively instead. # # Returns True or False to indicate whether the delete succeeded. Extended # status information is available by calling #get_operation_result. # # dn = "mail=deleteme@example.com, ou=people, dc=example, dc=com" # ldap.delete_tree :dn => dn def delete_tree(args) if search_root_dse[:supportedcontrol].include? Net::LDAP::LDAPControls::DELETE_TREE delete(args.merge(:control_codes => [[Net::LDAP::LDAPControls::DELETE_TREE, true]])) else recursive_delete(args) end end # This method is experimental and subject to change. Return the rootDSE # record from the LDAP server as a Net::LDAP::Entry, or an empty Entry if # the server doesn't return the record. #-- # cf. RFC4512 graf 5.1. # Note that the rootDSE record we return on success has an empty DN, which # is correct. On failure, the empty Entry will have a nil DN. There's no # real reason for that, so it can be changed if desired. The funky # number-disagreements in the set of attribute names is correct per the # RFC. We may be called by #search itself, which may need to determine # things like paged search capabilities. So to avoid an infinite regress, # set :ignore_server_caps, which prevents us getting called recursively. #++ def search_root_dse rs = search(:ignore_server_caps => true, :base => "", :scope => SearchScope_BaseObject, :attributes => [ :altServer, :namingContexts, :supportedCapabilities, :supportedControl, :supportedExtension, :supportedFeatures, :supportedLdapVersion, :supportedSASLMechanisms, ]) (rs and rs.first) or Net::LDAP::Entry.new end # Return the root Subschema record from the LDAP server as a # Net::LDAP::Entry, or an empty Entry if the server doesn't return the # record. On success, the Net::LDAP::Entry returned from this call will # have the attributes :dn, :objectclasses, and :attributetypes. If there # is an error, call #get_operation_result for more information. # # ldap = Net::LDAP.new # ldap.host = "your.ldap.host" # ldap.auth "your-user-dn", "your-psw" # subschema_entry = ldap.search_subschema_entry # # subschema_entry.attributetypes.each do |attrtype| # # your code # end # # subschema_entry.objectclasses.each do |attrtype| # # your code # end #-- # cf. RFC4512 section 4, particulary graff 4.4. # The :dn attribute in the returned Entry is the subschema name as # returned from the server. Set :ignore_server_caps, see the notes in # search_root_dse. #++ def search_subschema_entry rs = search(:ignore_server_caps => true, :base => "", :scope => SearchScope_BaseObject, :attributes => [:subschemaSubentry]) return Net::LDAP::Entry.new unless (rs and rs.first) subschema_name = rs.first.subschemasubentry return Net::LDAP::Entry.new unless (subschema_name and subschema_name.first) rs = search(:ignore_server_caps => true, :base => subschema_name.first, :scope => SearchScope_BaseObject, :filter => "objectclass=subschema", :attributes => [:objectclasses, :attributetypes]) (rs and rs.first) or Net::LDAP::Entry.new end #-- # Convenience method to query server capabilities. # Only do this once per Net::LDAP object. # Note, we call a search, and we might be called from inside a search! # MUST refactor the root_dse call out. #++ def paged_searches_supported? # active directory returns that it supports paged results. However # it returns binary data in the rfc2696_cookie which throws an # encoding exception breaking searching. return false if @force_no_page @server_caps ||= search_root_dse @server_caps[:supportedcontrol].include?(Net::LDAP::LDAPControls::PAGED_RESULTS) end # Mask auth password def inspect inspected = super inspected.gsub! @auth[:password], "*******" if @auth[:password] inspected end # Internal: Set @open_connection for testing def connection=(connection) @open_connection = connection end private # Yields an open connection if there is one, otherwise establishes a new # connection, binds, and yields it. If binding fails, it will return the # result from that, and :use_connection: will not yield at all. If not # the return value is whatever is returned from the block. def use_connection(args) if @open_connection yield @open_connection else begin conn = new_connection result = conn.bind(args[:auth] || @auth) return result unless result.result_code == Net::LDAP::ResultCodeSuccess yield conn ensure conn.close if conn end end end # Establish a new connection to the LDAP server def new_connection connection = Net::LDAP::Connection.new \ :host => @host, :port => @port, :hosts => @hosts, :encryption => @encryption, :instrumentation_service => @instrumentation_service, :connect_timeout => @connect_timeout # Force connect to see if there's a connection error connection.socket connection rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT => e @result = { :resultCode => 52, :errorMessage => ResultStrings[ResultCodeUnavailable], } raise e end # Normalize encryption parameter the constructor accepts, expands a few # convenience symbols into recognizable hashes def normalize_encryption(args) return if args.nil? return args if args.is_a? Hash case method = args.to_sym when :simple_tls, :start_tls { :method => method, :tls_options => {} } end end # Recursively delete a dn and it's subordinate children. # This is useful when a server does not support the DELETE_TREE control code. def recursive_delete(args) raise EmptyDNError unless args.is_a?(Hash) && args.key?(:dn) # Delete Children search(base: args[:dn], scope: Net::LDAP::SearchScope_SingleLevel) do |entry| recursive_delete(dn: entry.dn) end # Delete Self unless delete(dn: args[:dn]) raise Net::LDAP::Error, get_operation_result[:error_message].to_s end true end end # class LDAP ruby-net-ldap-0.17.1/lib/net/ldap/000077500000000000000000000000001424751475600165565ustar00rootroot00000000000000ruby-net-ldap-0.17.1/lib/net/ldap/auth_adapter.rb000066400000000000000000000011111424751475600215360ustar00rootroot00000000000000module Net class LDAP class AuthAdapter def self.register(names, adapter) names = Array(names) @adapters ||= {} names.each do |name| @adapters[name] = adapter end end def self.[](name) a = @adapters[name] if a.nil? raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (#{name})" end return a end def initialize(conn) @connection = conn end def bind raise "bind method must be overwritten" end end end end ruby-net-ldap-0.17.1/lib/net/ldap/auth_adapter/000077500000000000000000000000001424751475600212175ustar00rootroot00000000000000ruby-net-ldap-0.17.1/lib/net/ldap/auth_adapter/gss_spnego.rb000066400000000000000000000030051424751475600237110ustar00rootroot00000000000000require_relative '../auth_adapter' require_relative 'sasl' module Net class LDAP module AuthAdapers #-- # PROVISIONAL, only for testing SASL implementations. DON'T USE THIS YET. # Uses Kohei Kajimoto's Ruby/NTLM. We have to find a clean way to # integrate it without introducing an external dependency. # # This authentication method is accessed by calling #bind with a :method # parameter of :gss_spnego. It requires :username and :password # attributes, just like the :simple authentication method. It performs a # GSS-SPNEGO authentication with the server, which is presumed to be a # Microsoft Active Directory. #++ class GSS_SPNEGO < Net::LDAP::AuthAdapter def bind(auth) require 'ntlm' user, psw = [auth[:username] || auth[:dn], auth[:password]] raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw) nego = proc do |challenge| t2_msg = NTLM::Message.parse(challenge) t3_msg = t2_msg.response({ :user => user, :password => psw }, { :ntlmv2 => true }) t3_msg.serialize end Net::LDAP::AuthAdapter::Sasl.new(@connection).bind \ :method => :sasl, :mechanism => "GSS-SPNEGO", :initial_credential => NTLM::Message::Type1.new.serialize, :challenge_response => nego end end end end end ruby-net-ldap-0.17.1/lib/net/ldap/auth_adapter/sasl.rb000066400000000000000000000047641424751475600225210ustar00rootroot00000000000000require_relative '../auth_adapter' module Net class LDAP class AuthAdapter class Sasl < Net::LDAP::AuthAdapter MAX_SASL_CHALLENGES = 10 #-- # Required parameters: :mechanism, :initial_credential and # :challenge_response # # Mechanism is a string value that will be passed in the SASL-packet's # "mechanism" field. # # Initial credential is most likely a string. It's passed in the initial # BindRequest that goes to the server. In some protocols, it may be empty. # # Challenge-response is a Ruby proc that takes a single parameter and # returns an object that will typically be a string. The # challenge-response block is called when the server returns a # BindResponse with a result code of 14 (saslBindInProgress). The # challenge-response block receives a parameter containing the data # returned by the server in the saslServerCreds field of the LDAP # BindResponse packet. The challenge-response block may be called multiple # times during the course of a SASL authentication, and each time it must # return a value that will be passed back to the server as the credential # data in the next BindRequest packet. #++ def bind(auth) mech, cred, chall = auth[:mechanism], auth[:initial_credential], auth[:challenge_response] raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (mech && cred && chall) message_id = @connection.next_msgid n = 0 loop do sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3) request = [ Net::LDAP::Connection::LdapVersion.to_ber, "".to_ber, sasl ].to_ber_appsequence(Net::LDAP::PDU::BindRequest) @connection.send(:write, request, nil, message_id) pdu = @connection.queued_read(message_id) if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult raise Net::LDAP::NoBindResultError, "no bind result" end return pdu unless pdu.result_code == Net::LDAP::ResultCodeSaslBindInProgress raise Net::LDAP::SASLChallengeOverflowError, "sasl-challenge overflow" if ((n += 1) > MAX_SASL_CHALLENGES) cred = chall.call(pdu.result_server_sasl_creds) end raise Net::LDAP::SASLChallengeOverflowError, "why are we here?" end end end end end ruby-net-ldap-0.17.1/lib/net/ldap/auth_adapter/simple.rb000066400000000000000000000017631424751475600230440ustar00rootroot00000000000000require_relative '../auth_adapter' module Net class LDAP class AuthAdapter class Simple < AuthAdapter def bind(auth) user, psw = if auth[:method] == :simple [auth[:username] || auth[:dn], auth[:password]] else ["", ""] end raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw) message_id = @connection.next_msgid request = [ Net::LDAP::Connection::LdapVersion.to_ber, user.to_ber, psw.to_ber_contextspecific(0) ].to_ber_appsequence(Net::LDAP::PDU::BindRequest) @connection.send(:write, request, nil, message_id) pdu = @connection.queued_read(message_id) if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult raise Net::LDAP::NoBindResultError, "no bind result" end pdu end end end end end ruby-net-ldap-0.17.1/lib/net/ldap/connection.rb000066400000000000000000000600601424751475600212440ustar00rootroot00000000000000# This is a private class used internally by the library. It should not # be called by user code. class Net::LDAP::Connection #:nodoc: include Net::LDAP::Instrumentation # Seconds before failing for socket connect timeout DefaultConnectTimeout = 5 LdapVersion = 3 # Initialize a connection to an LDAP server # # :server # :hosts Array of tuples specifying host, port # :host host # :port port # :socket prepared socket # def initialize(server = {}) @server = server @instrumentation_service = server[:instrumentation_service] # Allows tests to parameterize what socket class to use @socket_class = server.fetch(:socket_class, DefaultSocket) yield self if block_given? end def socket_class=(socket_class) @socket_class = socket_class end def prepare_socket(server, timeout=nil) socket = server[:socket] encryption = server[:encryption] @conn = socket setup_encryption(encryption, timeout) if encryption end def open_connection(server) hosts = server[:hosts] encryption = server[:encryption] timeout = server[:connect_timeout] || DefaultConnectTimeout socket_opts = { connect_timeout: timeout, } errors = [] hosts.each do |host, port| begin prepare_socket(server.merge(socket: @socket_class.new(host, port, socket_opts)), timeout) if encryption if encryption[:tls_options] && encryption[:tls_options][:verify_mode] && encryption[:tls_options][:verify_mode] == OpenSSL::SSL::VERIFY_NONE warn "not verifying SSL hostname of LDAPS server '#{host}:#{port}'" else @conn.post_connection_check(host) end end return rescue Net::LDAP::Error, SocketError, SystemCallError, OpenSSL::SSL::SSLError => e # Ensure the connection is closed in the event a setup failure. close errors << [e, host, port] end end raise Net::LDAP::ConnectionError.new(errors) end module GetbyteForSSLSocket def getbyte c = getc c && c.ord end end module FixSSLSocketSyncClose def close super io.close end end def self.wrap_with_ssl(io, tls_options = {}, timeout=nil) raise Net::LDAP::NoOpenSSLError, "OpenSSL is unavailable" unless Net::LDAP::HasOpenSSL ctx = OpenSSL::SSL::SSLContext.new # By default, we do not verify certificates. For a 1.0 release, this should probably be changed at some point. # See discussion in https://github.com/ruby-ldap/ruby-net-ldap/pull/161 ctx.set_params(tls_options) unless tls_options.empty? conn = OpenSSL::SSL::SSLSocket.new(io, ctx) begin if timeout conn.connect_nonblock else conn.connect end rescue IO::WaitReadable raise Errno::ETIMEDOUT, "OpenSSL connection read timeout" unless IO.select([conn], nil, nil, timeout) retry rescue IO::WaitWritable raise Errno::ETIMEDOUT, "OpenSSL connection write timeout" unless IO.select(nil, [conn], nil, timeout) retry end # Doesn't work: # conn.sync_close = true conn.extend(GetbyteForSSLSocket) unless conn.respond_to?(:getbyte) conn.extend(FixSSLSocketSyncClose) conn end #-- # Helper method called only from prepare_socket or open_connection, and only # after we have a successfully-opened @conn instance variable, which is a TCP # connection. Depending on the received arguments, we establish SSL, # potentially replacing the value of @conn accordingly. Don't generate any # errors here if no encryption is requested. DO raise Net::LDAP::Error objects # if encryption is requested and we have trouble setting it up. That includes # if OpenSSL is not set up on the machine. (Question: how does the Ruby # OpenSSL wrapper react in that case?) DO NOT filter exceptions raised by the # OpenSSL library. Let them pass back to the user. That should make it easier # for us to debug the problem reports. Presumably (hopefully?) that will also # produce recognizable errors if someone tries to use this on a machine # without OpenSSL. # # The simple_tls method is intended as the simplest, stupidest, easiest # solution for people who want nothing more than encrypted comms with the # LDAP server. It doesn't do any server-cert validation and requires # nothing in the way of key files and root-cert files, etc etc. OBSERVE: # WE REPLACE the value of @conn, which is presumed to be a connected # TCPSocket object. # # The start_tls method is supported by many servers over the standard LDAP # port. It does not require an alternative port for encrypted # communications, as with simple_tls. Thanks for Kouhei Sutou for # generously contributing the :start_tls path. #++ def setup_encryption(args, timeout=nil) args[:tls_options] ||= {} case args[:method] when :simple_tls @conn = self.class.wrap_with_ssl(@conn, args[:tls_options], timeout) # additional branches requiring server validation and peer certs, etc. # go here. when :start_tls message_id = next_msgid request = [ Net::LDAP::StartTlsOid.to_ber_contextspecific(0), ].to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest) write(request, nil, message_id) pdu = queued_read(message_id) if pdu.nil? || pdu.app_tag != Net::LDAP::PDU::ExtendedResponse raise Net::LDAP::NoStartTLSResultError, "no start_tls result" end raise Net::LDAP::StartTLSError, "start_tls failed: #{pdu.result_code}" unless pdu.result_code.zero? @conn = self.class.wrap_with_ssl(@conn, args[:tls_options], timeout) else raise Net::LDAP::EncMethodUnsupportedError, "unsupported encryption method #{args[:method]}" end end #-- # This is provided as a convenience method to make sure a connection # object gets closed without waiting for a GC to happen. Clients shouldn't # have to call it, but perhaps it will come in handy someday. #++ def close return if !defined?(@conn) || @conn.nil? @conn.close @conn = nil end # Internal: Reads messages by ID from a queue, falling back to reading from # the connected socket until a message matching the ID is read. Any messages # with mismatched IDs gets queued for subsequent reads by the origin of that # message ID. # # Returns a Net::LDAP::PDU object or nil. def queued_read(message_id) if pdu = message_queue[message_id].shift return pdu end # read messages until we have a match for the given message_id while pdu = read return pdu if pdu.message_id == message_id message_queue[pdu.message_id].push pdu next end pdu end # Internal: The internal queue of messages, read from the socket, grouped by # message ID. # # Used by `queued_read` to return messages sent by the server with the given # ID. If no messages are queued for that ID, `queued_read` will `read` from # the socket and queue messages that don't match the given ID for other # readers. # # Returns the message queue Hash. def message_queue @message_queue ||= Hash.new do |hash, key| hash[key] = [] end end # Internal: Reads and parses data from the configured connection. # # - syntax: the BER syntax to use to parse the read data with # # Returns parsed Net::LDAP::PDU object. def read(syntax = Net::LDAP::AsnSyntax) ber_object = instrument "read.net_ldap_connection", :syntax => syntax do |payload| socket.read_ber(syntax) do |id, content_length| payload[:object_type_id] = id payload[:content_length] = content_length end end return unless ber_object instrument "parse_pdu.net_ldap_connection" do |payload| pdu = payload[:pdu] = Net::LDAP::PDU.new(ber_object) payload[:message_id] = pdu.message_id payload[:app_tag] = pdu.app_tag pdu end end private :read # Internal: Write a BER formatted packet with the next message id to the # configured connection. # # - request: required BER formatted request # - controls: optional BER formatted controls # # Returns the return value from writing to the connection, which in some # cases is the Integer number of bytes written to the socket. def write(request, controls = nil, message_id = next_msgid) instrument "write.net_ldap_connection" do |payload| packet = [message_id.to_ber, request, controls].compact.to_ber_sequence payload[:content_length] = socket.write(packet) end end private :write def next_msgid @msgid ||= 0 @msgid += 1 end def bind(auth) instrument "bind.net_ldap_connection" do |payload| payload[:method] = meth = auth[:method] adapter = Net::LDAP::AuthAdapter[meth] adapter.new(self).bind(auth) end end #-- # Allow the caller to specify a sort control # # The format of the sort control needs to be: # # :sort_control => ["cn"] # just a string # or # :sort_control => [["cn", "matchingRule", true]] #attribute, matchingRule, direction (true / false) # or # :sort_control => ["givenname","sn"] #multiple strings or arrays # def encode_sort_controls(sort_definitions) return sort_definitions unless sort_definitions sort_control_values = sort_definitions.map do |control| control = Array(control) # if there is only an attribute name as a string then infer the orderinrule and reverseorder control[0] = String(control[0]).to_ber, control[1] = String(control[1]).to_ber, control[2] = (control[2] == true).to_ber control.to_ber_sequence end [ Net::LDAP::LDAPControls::SORT_REQUEST.to_ber, false.to_ber, sort_control_values.to_ber_sequence.to_s.to_ber, ].to_ber_sequence end #-- # Alternate implementation, this yields each search entry to the caller as # it are received. # # TODO: certain search parameters are hardcoded. # TODO: if we mis-parse the server results or the results are wrong, we # can block forever. That's because we keep reading results until we get a # type-5 packet, which might never come. We need to support the time-limit # in the protocol. #++ def search(args = nil) args ||= {} # filtering, scoping, search base # filter: https://tools.ietf.org/html/rfc4511#section-4.5.1.7 # base: https://tools.ietf.org/html/rfc4511#section-4.5.1.1 # scope: https://tools.ietf.org/html/rfc4511#section-4.5.1.2 filter = args[:filter] || Net::LDAP::Filter.eq("objectClass", "*") base = args[:base] scope = args[:scope] || Net::LDAP::SearchScope_WholeSubtree # attr handling # attrs: https://tools.ietf.org/html/rfc4511#section-4.5.1.8 # attrs_only: https://tools.ietf.org/html/rfc4511#section-4.5.1.6 attrs = Array(args[:attributes]) attrs_only = args[:attributes_only] == true # references # refs: https://tools.ietf.org/html/rfc4511#section-4.5.3 # deref: https://tools.ietf.org/html/rfc4511#section-4.5.1.3 refs = args[:return_referrals] == true deref = args[:deref] || Net::LDAP::DerefAliases_Never # limiting, paging, sorting # size: https://tools.ietf.org/html/rfc4511#section-4.5.1.4 # time: https://tools.ietf.org/html/rfc4511#section-4.5.1.5 size = args[:size].to_i time = args[:time].to_i paged = args[:paged_searches_supported] sort = args.fetch(:sort_controls, false) # arg validation raise ArgumentError, "search base is required" unless base raise ArgumentError, "invalid search-size" unless size >= 0 raise ArgumentError, "invalid search scope" unless Net::LDAP::SearchScopes.include?(scope) raise ArgumentError, "invalid alias dereferencing value" unless Net::LDAP::DerefAliasesArray.include?(deref) # arg transforms filter = Net::LDAP::Filter.construct(filter) if filter.is_a?(String) ber_attrs = attrs.map { |attr| attr.to_s.to_ber } ber_sort = encode_sort_controls(sort) # An interesting value for the size limit would be close to A/D's # built-in page limit of 1000 records, but openLDAP newer than version # 2.2.0 chokes on anything bigger than 126. You get a silent error that # is easily visible by running slapd in debug mode. Go figure. # # Changed this around 06Sep06 to support a caller-specified search-size # limit. Because we ALWAYS do paged searches, we have to work around the # problem that it's not legal to specify a "normal" sizelimit (in the # body of the search request) that is larger than the page size we're # requesting. Unfortunately, I have the feeling that this will break # with LDAP servers that don't support paged searches!!! # # (Because we pass zero as the sizelimit on search rounds when the # remaining limit is larger than our max page size of 126. In these # cases, I think the caller's search limit will be ignored!) # # CONFIRMED: This code doesn't work on LDAPs that don't support paged # searches when the size limit is larger than 126. We're going to have # to do a root-DSE record search and not do a paged search if the LDAP # doesn't support it. Yuck. rfc2696_cookie = [126, ""] result_pdu = nil n_results = 0 message_id = next_msgid instrument "search.net_ldap_connection", message_id: message_id, filter: filter, base: base, scope: scope, size: size, time: time, sort: sort, referrals: refs, deref: deref, attributes: attrs do |payload| loop do # should collect this into a private helper to clarify the structure query_limit = 0 if size > 0 query_limit = if paged (((size - n_results) < 126) ? (size - n_results) : 0) else size end end request = [ base.to_ber, scope.to_ber_enumerated, deref.to_ber_enumerated, query_limit.to_ber, # size limit time.to_ber, attrs_only.to_ber, filter.to_ber, ber_attrs.to_ber_sequence, ].to_ber_appsequence(Net::LDAP::PDU::SearchRequest) # rfc2696_cookie sometimes contains binary data from Microsoft Active Directory # this breaks when calling to_ber. (Can't force binary data to UTF-8) # we have to disable paging (even though server supports it) to get around this... controls = [] controls << [ Net::LDAP::LDAPControls::PAGED_RESULTS.to_ber, # Criticality MUST be false to interoperate with normal LDAPs. false.to_ber, rfc2696_cookie.map(&:to_ber).to_ber_sequence.to_s.to_ber, ].to_ber_sequence if paged controls << ber_sort if ber_sort controls = controls.empty? ? nil : controls.to_ber_contextspecific(0) write(request, controls, message_id) result_pdu = nil controls = [] while pdu = queued_read(message_id) case pdu.app_tag when Net::LDAP::PDU::SearchReturnedData n_results += 1 yield pdu.search_entry if block_given? when Net::LDAP::PDU::SearchResultReferral if refs if block_given? se = Net::LDAP::Entry.new se[:search_referrals] = (pdu.search_referrals || []) yield se end end when Net::LDAP::PDU::SearchResult result_pdu = pdu controls = pdu.result_controls if refs && pdu.result_code == Net::LDAP::ResultCodeReferral if block_given? se = Net::LDAP::Entry.new se[:search_referrals] = (pdu.search_referrals || []) yield se end end break else raise Net::LDAP::ResponseTypeInvalidError, "invalid response-type in search: #{pdu.app_tag}" end end if result_pdu.nil? raise Net::LDAP::ResponseMissingOrInvalidError, "response missing" end # count number of pages of results payload[:page_count] ||= 0 payload[:page_count] += 1 # When we get here, we have seen a type-5 response. If there is no # error AND there is an RFC-2696 cookie, then query again for the next # page of results. If not, we're done. Don't screw this up or we'll # break every search we do. # # Noticed 02Sep06, look at the read_ber call in this loop, shouldn't # that have a parameter of AsnSyntax? Does this just accidentally # work? According to RFC-2696, the value expected in this position is # of type OCTET STRING, covered in the default syntax supported by # read_ber, so I guess we're ok. more_pages = false if result_pdu.result_code == Net::LDAP::ResultCodeSuccess and controls controls.each do |c| if c.oid == Net::LDAP::LDAPControls::PAGED_RESULTS # just in case some bogus server sends us more than 1 of these. more_pages = false if c.value and c.value.length > 0 cookie = c.value.read_ber[1] if cookie and cookie.length > 0 rfc2696_cookie[1] = cookie more_pages = true end end end end end break unless more_pages end # loop # track total result count payload[:result_count] = n_results result_pdu || OpenStruct.new(:status => :failure, :result_code => Net::LDAP::ResultCodeOperationsError, :message => "Invalid search") end # instrument ensure # clean up message queue for this search messages = message_queue.delete(message_id) # in the exceptional case some messages were *not* consumed from the queue, # instrument the event but do not fail. if !messages.nil? && !messages.empty? instrument "search_messages_unread.net_ldap_connection", message_id: message_id, messages: messages end end MODIFY_OPERATIONS = { #:nodoc: :add => 0, :delete => 1, :replace => 2, } def self.modify_ops(operations) ops = [] if operations operations.each do |op, attrib, values| # TODO, fix the following line, which gives a bogus error if the # opcode is invalid. op_ber = MODIFY_OPERATIONS[op.to_sym].to_ber_enumerated values = [values].flatten.map { |v| v.to_ber if v }.to_ber_set values = [attrib.to_s.to_ber, values].to_ber_sequence ops << [op_ber, values].to_ber end end ops end #-- # TODO: need to support a time limit, in case the server fails to respond. # TODO: We're throwing an exception here on empty DN. Should return a # proper error instead, probaby from farther up the chain. # TODO: If the user specifies a bogus opcode, we'll throw a confusing # error here ("to_ber_enumerated is not defined on nil"). #++ def modify(args) modify_dn = args[:dn] or raise "Unable to modify empty DN" ops = self.class.modify_ops args[:operations] message_id = next_msgid request = [ modify_dn.to_ber, ops.to_ber_sequence, ].to_ber_appsequence(Net::LDAP::PDU::ModifyRequest) write(request, nil, message_id) pdu = queued_read(message_id) if !pdu || pdu.app_tag != Net::LDAP::PDU::ModifyResponse raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid" end pdu end ## # Password Modify # # http://tools.ietf.org/html/rfc3062 # # passwdModifyOID OBJECT IDENTIFIER ::= 1.3.6.1.4.1.4203.1.11.1 # # PasswdModifyRequestValue ::= SEQUENCE { # userIdentity [0] OCTET STRING OPTIONAL # oldPasswd [1] OCTET STRING OPTIONAL # newPasswd [2] OCTET STRING OPTIONAL } # # PasswdModifyResponseValue ::= SEQUENCE { # genPasswd [0] OCTET STRING OPTIONAL } # # Encoded request: # # 00\x02\x01\x02w+\x80\x171.3.6.1.4.1.4203.1.11.1\x81\x100\x0E\x81\x05old\x82\x05new # def password_modify(args) dn = args[:dn] raise ArgumentError, 'DN is required' if !dn || dn.empty? ext_seq = [Net::LDAP::PasswdModifyOid.to_ber_contextspecific(0)] pwd_seq = [] pwd_seq << dn.to_ber(0x80) pwd_seq << args[:old_password].to_ber(0x81) unless args[:old_password].nil? pwd_seq << args[:new_password].to_ber(0x82) unless args[:new_password].nil? ext_seq << pwd_seq.to_ber_sequence.to_ber(0x81) request = ext_seq.to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest) message_id = next_msgid write(request, nil, message_id) pdu = queued_read(message_id) if !pdu || pdu.app_tag != Net::LDAP::PDU::ExtendedResponse raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid" end pdu end #-- # TODO: need to support a time limit, in case the server fails to respond. # Unlike other operation-methods in this class, we return a result hash # rather than a simple result number. This is experimental, and eventually # we'll want to do this with all the others. The point is to have access # to the error message and the matched-DN returned by the server. #++ def add(args) add_dn = args[:dn] or raise Net::LDAP::EmptyDNError, "Unable to add empty DN" add_attrs = [] a = args[:attributes] and a.each do |k, v| add_attrs << [k.to_s.to_ber, Array(v).map(&:to_ber).to_ber_set].to_ber_sequence end message_id = next_msgid request = [add_dn.to_ber, add_attrs.to_ber_sequence].to_ber_appsequence(Net::LDAP::PDU::AddRequest) write(request, nil, message_id) pdu = queued_read(message_id) if !pdu || pdu.app_tag != Net::LDAP::PDU::AddResponse raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid" end pdu end #-- # TODO: need to support a time limit, in case the server fails to respond. #++ def rename(args) old_dn = args[:olddn] or raise "Unable to rename empty DN" new_rdn = args[:newrdn] or raise "Unable to rename to empty RDN" delete_attrs = args[:delete_attributes] ? true : false new_superior = args[:new_superior] message_id = next_msgid request = [old_dn.to_ber, new_rdn.to_ber, delete_attrs.to_ber] request << new_superior.to_ber_contextspecific(0) unless new_superior == nil write(request.to_ber_appsequence(Net::LDAP::PDU::ModifyRDNRequest), nil, message_id) pdu = queued_read(message_id) if !pdu || pdu.app_tag != Net::LDAP::PDU::ModifyRDNResponse raise Net::LDAP::ResponseMissingOrInvalidError.new "response missing or invalid" end pdu end #-- # TODO, need to support a time limit, in case the server fails to respond. #++ def delete(args) dn = args[:dn] or raise "Unable to delete empty DN" controls = args.include?(:control_codes) ? args[:control_codes].to_ber_control : nil #use nil so we can compact later message_id = next_msgid request = dn.to_s.to_ber_application_string(Net::LDAP::PDU::DeleteRequest) write(request, controls, message_id) pdu = queued_read(message_id) if !pdu || pdu.app_tag != Net::LDAP::PDU::DeleteResponse raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid" end pdu end # Internal: Returns a Socket like object used internally to communicate with # LDAP server. # # Typically a TCPSocket, but can be a OpenSSL::SSL::SSLSocket def socket return @conn if defined?(@conn) && !@conn.nil? # First refactoring uses the existing methods open_connection and # prepare_socket to set @conn. Next cleanup would centralize connection # handling here. if @server[:socket] prepare_socket(@server) else @server[:hosts] = [[@server[:host], @server[:port]]] if @server[:hosts].nil? open_connection(@server) end @conn end private # Wrap around Socket.tcp to normalize with other Socket initializers class DefaultSocket def self.new(host, port, socket_opts = {}) Socket.tcp(host, port, **socket_opts) end end end # class Connection ruby-net-ldap-0.17.1/lib/net/ldap/dataset.rb000066400000000000000000000107501424751475600205330ustar00rootroot00000000000000# -*- 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 version, comments. attr_accessor :version attr_reader :comments def initialize(*args, &block) # :nodoc: super @version = nil @comments = [] end ## # Outputs an LDAP Dataset as an array of strings representing LDIF # entries. def to_ldif ary = [] if version ary << "version: #{version}" ary << "" end ary += @comments unless @comments.empty? keys.sort.each do |dn| ary << "dn: #{dn}" attributes = self[dn].keys.map(&: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 =~ /^ / line << $' else nextline = new_line if line =~ /^#/ ds.comments << line yield :comment, line if block_given? elsif line =~ /^version:[\s]*([0-9]+)$/i ds.version = $1 yield :version, line if block_given? elsif line =~ /^dn:([\:]?)[\s]*/i # $1 is a colon if the dn-value is base-64 encoded # $' is the dn-value # Avoid the Base64 class because not all Ruby versions have it. dn = ($1 == ":") ? $'.unpack('m').shift : $' 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 ruby-net-ldap-0.17.1/lib/net/ldap/dn.rb000066400000000000000000000153151424751475600175110ustar00rootroot00000000000000# -*- 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 Net::LDAP::InvalidDNError, "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 Net::LDAP::InvalidDNError, "DN badly formed" end when :key_oid then case char when '=' then state = :value when '0'..'9', '.', ' ' then key << char else raise Net::LDAP::InvalidDNError, "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 Net::LDAP::InvalidDNError, "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 Net::LDAP::InvalidDNError, "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 Net::LDAP::InvalidDNError, "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 Net::LDAP::InvalidDNError, "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 Net::LDAP::InvalidDNError, "DN badly formed" end else raise Net::LDAP::InvalidDNError, "Fell out of state machine" end end # Last pair raise Net::LDAP::InvalidDNError, "DN badly formed" unless [:value, :value_normal, :value_hexstring, :value_end].include? state yield key.string.strip, value.string.rstrip 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 ruby-net-ldap-0.17.1/lib/net/ldap/entry.rb000066400000000000000000000137751424751475600202610ustar00rootroot00000000000000# -*- 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 values. 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::EntryOverflowError, "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 ## # Read the first value for the provided attribute. The attribute name # is canonicalized prior to reading. Returns nil if the attribute does # not exist. def first(name) self[name].first 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 ## # Creates a duplicate of the internal Hash containing the attributes # of the entry. def to_h @myhash.dup 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 return unless block_given? attribute_names.each do|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, include_all = false) #: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? def ==(other) other.instance_of?(self.class) && @myhash == other.to_h end end # class Entry ruby-net-ldap-0.17.1/lib/net/ldap/error.rb000066400000000000000000000032411424751475600202340ustar00rootroot00000000000000class Net::LDAP class Error < StandardError; end class AlreadyOpenedError < Error; end class SocketError < Error; end class ConnectionError < Error def self.new(errors) error = errors.first.first if errors.size == 1 return error if error.is_a? Errno::ECONNREFUSED return Net::LDAP::Error.new(error.message) end super end def initialize(errors) message = "Unable to connect to any given server: \n #{errors.map { |e, h, p| "#{e.class}: #{e.message} (#{h}:#{p})" }.join("\n ")}" super(message) end end class NoOpenSSLError < Error; end class NoStartTLSResultError < Error; end class NoSearchBaseError < Error; end class StartTLSError < Error; end class EncryptionUnsupportedError < Error; end class EncMethodUnsupportedError < Error; end class AuthMethodUnsupportedError < Error; end class BindingInformationInvalidError < Error; end class NoBindResultError < Error; end class SASLChallengeOverflowError < Error; end class SearchSizeInvalidError < Error; end class SearchScopeInvalidError < Error; end class ResponseTypeInvalidError < Error; end class ResponseMissingOrInvalidError < Error; end class EmptyDNError < Error; end class InvalidDNError < Error; end class HashTypeUnsupportedError < Error; end class OperatorError < Error; end class SubstringFilterError < Error; end class SearchFilterError < Error; end class BERInvalidError < Error; end class SearchFilterTypeUnknownError < Error; end class BadAttributeError < Error; end class FilterTypeUnknownError < Error; end class FilterSyntaxInvalidError < Error; end class EntryOverflowError < Error; end end ruby-net-ldap-0.17.1/lib/net/ldap/filter.rb000066400000000000000000000652571424751475600204070ustar00rootroot00000000000000# -*- 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 # :filter 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, :bineq] def initialize(op, left, right) #:nodoc: unless FilterTypes.include?(op) raise Net::LDAP::OperatorError, "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 # mail. 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 sAMAccountName: # # 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 not supported due to limitations in the # underlying LDAP protocol. This example selects any entry with a # mail 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 a binary comparison. # this prevents the search data from being forced into a UTF-8 string. # # This is primarily used for Microsoft Active Directory to compare # GUID values. # # # for guid represented as hex charecters # guid = "6a31b4a12aa27a41aca9603f27dd5116" # guid_bin = [guid].pack("H*") # f = Net::LDAP::Filter.bineq("objectGUID", guid_bin) # # This filter does not perform any escaping. def bineq(attribute, value) new(:bineq, 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 # Filter.join(left, right) is the same as left & # right. # # # Selects only entries that have an objectclass attribute. # x = Net::LDAP::Filter.present("objectclass") # # Selects only entries that have a mail 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 # Filter.intersect(left, right) is the same as left | # right. # # # Selects only entries that have an objectclass attribute. # x = Net::LDAP::Filter.present("objectclass") # # Selects only entries that have a mail 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 Fitler.negate(filter) i s the same # as ~filter. # # # Selects only entries that do not have an objectclass # # 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 do |b| case b.ber_identifier when 0x80 # context-specific primitive 0, SubstringFilter "initial" raise Net::LDAP::SubstringFilterError, "Unrecognized substring filter; bad initial value." if str.length > 0 str += escape(b) when 0x81 # context-specific primitive 0, SubstringFilter "any" str += "*#{escape(b)}" when 0x82 # context-specific primitive 0, SubstringFilter "final" str += "*#{escape(b)}" final = true end 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::SearchFilterError, "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::BERInvalidError, "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::SearchFilterTypeUnknownError, "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 objectclass attribute. # x = Net::LDAP::Filter.present("objectclass") # # Selects only entries that have a mail 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 objectclass attribute. # x = Net::LDAP::Filter.present("objectclass") # # Selects only entries that have a mail 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 objectclass # # 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, :bineq "#{@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.to_s =~ /[*]/ # 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 = unescape(ary.shift).to_ber_contextspecific(0) end if ary.last.empty? last = nil ary.pop else last = unescape(ary.pop).to_ber_contextspecific(2) end seq = ary.map { |e| unescape(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 :bineq # make sure data is not forced to UTF-8 [@left.to_s.to_ber, unescape(@right).to_ber_bin].to_ber_contextspecific(3) when :ex seq = [] unless @left =~ /^([-;\w]*)(:dn)?(:(\w+|[.\w]+))?$/ raise Net::LDAP::BadAttributeError, "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(&:to_ber).to_ber_contextspecific(0) when :or ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten ary.map(&: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::FilterTypeUnknownError, "Unknown filter type in match: #{@op}" end end ## # Converts escaped characters (e.g., "\\28") to unescaped characters # @note slawson20170317: Don't attempt to unescape 16 byte binary data which we assume are objectGUIDs # The binary form of 5936AE79-664F-44EA-BCCB-5C39399514C6 triggers a BINARY -> UTF-8 conversion error def unescape(right) right = right.to_s if right.length == 16 && right.encoding == Encoding::BINARY right else right.to_s.gsub(/\\([a-fA-F\d]{2})/) { [$1.hex].pack("U") } end 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::FilterSyntaxInvalidError, "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 (&(test1)(test2) 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 (&(test1)(test2) 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\xC3\x80-\xCA\xAF]|[^\x00-\x7F]|\\[a-fA-F\d]{2})+/u) # 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 ruby-net-ldap-0.17.1/lib/net/ldap/instrumentation.rb000066400000000000000000000012561424751475600223520ustar00rootroot00000000000000module Net::LDAP::Instrumentation attr_reader :instrumentation_service private :instrumentation_service # Internal: Instrument a block with the defined instrumentation service. # # Yields the event payload if a block is given. # # Skips instrumentation if no service is set. # # Returns the return value of the block. def instrument(event, payload = {}) payload = (payload || {}).dup if instrumentation_service instrumentation_service.instrument(event, payload) do |instr_payload| instr_payload[:result] = yield(instr_payload) if block_given? end else yield(payload) if block_given? end end private :instrument end ruby-net-ldap-0.17.1/lib/net/ldap/password.rb000066400000000000000000000024351424751475600207510ustar00rootroot00000000000000# -*- ruby encoding: utf-8 -*- require 'digest/sha1' require 'digest/sha2' require 'digest/md5' require 'base64' require 'securerandom' class Net::LDAP::Password class << self # Generate a password-hash suitable for inclusion in an LDAP attribute. # Pass a hash type as a symbol (:md5, :sha, :ssha) 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: # * 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 '{MD5}' + Base64.strict_encode64(Digest::MD5.digest(str)) when :sha '{SHA}' + Base64.strict_encode64(Digest::SHA1.digest(str)) when :ssha salt = SecureRandom.random_bytes(16) '{SSHA}' + Base64.strict_encode64(Digest::SHA1.digest(str + salt) + salt) when :ssha256 salt = SecureRandom.random_bytes(16) '{SSHA256}' + Base64.strict_encode64(Digest::SHA256.digest(str + salt) + salt) else raise Net::LDAP::HashTypeUnsupportedError, "Unsupported password-hash type (#{type})" end end end end ruby-net-ldap-0.17.1/lib/net/ldap/pdu.rb000066400000000000000000000241371424751475600177020ustar00rootroot00000000000000# -*- 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. # # http://tools.ietf.org/html/rfc4511#section-4.1.1 # http://tools.ietf.org/html/rfc4511#section-4.1.9 class Net::LDAP::PDU class Error < RuntimeError; end # http://tools.ietf.org/html/rfc4511#section-4.2 BindRequest = 0 # http://tools.ietf.org/html/rfc4511#section-4.2.2 BindResult = 1 # http://tools.ietf.org/html/rfc4511#section-4.3 UnbindRequest = 2 # http://tools.ietf.org/html/rfc4511#section-4.5.1 SearchRequest = 3 # http://tools.ietf.org/html/rfc4511#section-4.5.2 SearchReturnedData = 4 SearchResult = 5 # see also SearchResultReferral (19) # http://tools.ietf.org/html/rfc4511#section-4.6 ModifyRequest = 6 ModifyResponse = 7 # http://tools.ietf.org/html/rfc4511#section-4.7 AddRequest = 8 AddResponse = 9 # http://tools.ietf.org/html/rfc4511#section-4.8 DeleteRequest = 10 DeleteResponse = 11 # http://tools.ietf.org/html/rfc4511#section-4.9 ModifyRDNRequest = 12 ModifyRDNResponse = 13 # http://tools.ietf.org/html/rfc4511#section-4.10 CompareRequest = 14 CompareResponse = 15 # http://tools.ietf.org/html/rfc4511#section-4.11 AbandonRequest = 16 # http://tools.ietf.org/html/rfc4511#section-4.5.2 SearchResultReferral = 19 # http://tools.ietf.org/html/rfc4511#section-4.12 ExtendedRequest = 23 ExtendedResponse = 24 # unused: http://tools.ietf.org/html/rfc4511#section-4.13 IntermediateResponse = 25 ## # 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 attr_reader :extended_response ## # 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_extended_response(ber_object[1]) else raise Error.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 def error_message result[:errorMessage] || "" 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 def status Net::LDAP::ResultCodesNonError.include?(result_code) ? :success : :failure end def success? status == :success end def failure? !success? 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], } parse_search_referral(sequence[3]) if @ldap_result[:resultCode] == Net::LDAP::ResultCodeReferral end private :parse_ldap_result ## # Parse an extended response # # http://www.ietf.org/rfc/rfc2251.txt # # Each Extended operation consists of an Extended request and an # Extended response. # # ExtendedRequest ::= [APPLICATION 23] SEQUENCE { # requestName [0] LDAPOID, # requestValue [1] OCTET STRING OPTIONAL } def parse_extended_response(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], } @extended_response = sequence[3] end private :parse_extended_response ## # 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 ruby-net-ldap-0.17.1/lib/net/ldap/version.rb000066400000000000000000000000711424751475600205660ustar00rootroot00000000000000module Net class LDAP VERSION = "0.17.1" end end ruby-net-ldap-0.17.1/lib/net/snmp.rb000066400000000000000000000161471424751475600171510ustar00rootroot00000000000000# -*- ruby encoding: utf-8 -*- require_relative 'ldap/version' # :stopdoc: module Net class SNMP VERSION = Net::LDAP::VERSION 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 do |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 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 do |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 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.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 do|n, v| [n.to_ber_oid, Net::BER::BerIdentifiedNull.new.to_ber].to_ber_sequence end, ].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 do|n, v| [n.to_ber_oid, Net::BER::BerIdentifiedNull.new.to_ber].to_ber_sequence end, ].to_ber_sequence, ].to_ber_contextspecific(1) when :get_response [ request_id.to_ber, error_status.to_ber, error_index.to_ber, [ @variables.map do|n, v| [n.to_ber_oid, v.to_ber].to_ber_sequence end, ].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: ruby-net-ldap-0.17.1/net-ldap.gemspec000066400000000000000000000041471424751475600173610ustar00rootroot00000000000000# -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require_relative 'lib/net/ldap/version' Gem::Specification.new do |s| s.name = %q{net-ldap} s.version = Net::LDAP::VERSION s.license = "MIT" s.authors = ["Francis Cianfrocca", "Emiel van de Laar", "Rory O'Connell", "Kaspar Schiess", "Austin Ziegler", "Michael Schaarschmidt"] 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 client 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 = ["Contributors.rdoc", "Hacking.rdoc", "History.rdoc", "License.rdoc", "README.rdoc"] s.files = Dir["*.rdoc", "lib/**/*"] s.test_files = s.files.grep(%r{^test}) s.homepage = %q{http://github.com/ruby-ldap/ruby-net-ldap} s.rdoc_options = ["--main", "README.rdoc"] s.require_paths = ["lib"] s.required_ruby_version = ">= 2.0.0" 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.add_development_dependency("flexmock", "~> 1.3") s.add_development_dependency("rake", "~> 12.3.3") s.add_development_dependency("rubocop", "~> 0.49.0") s.add_development_dependency("test-unit", "~> 3.3") s.add_development_dependency("byebug", "~> 9.0.6") unless RUBY_PLATFORM == "java" end ruby-net-ldap-0.17.1/script/000077500000000000000000000000001424751475600156065ustar00rootroot00000000000000ruby-net-ldap-0.17.1/script/changelog000077500000000000000000000025531424751475600174700ustar00rootroot00000000000000#!/usr/bin/env bash # Usage: script/changelog [-r ] [-b ] [-h ] # # repo: BASE string of GitHub REPOsitory url. e.g. "user_or_org/REPOsitory". Defaults to git remote url. # base: git ref to compare from. e.g. "v1.3.1". Defaults to latest git tag. # head: git ref to compare to. Defaults to "HEAD". # # Generate a changelog preview from pull requests merged between `base` and # `head`. # # https://github.com/jch/release-scripts/blob/master/changelog set -e [ $# -eq 0 ] && set -- --help while [[ $# > 1 ]] do key="$1" case $key in -r|--repo) repo="$2" shift ;; -b|--base) base="$2" shift ;; -h|--head) head="$2" shift ;; *) ;; esac shift done repo="${repo:-$(git remote -v | grep push | awk '{print $2}' | cut -d'/' -f4- | sed 's/\.git//')}" base="${base:-$(git tag -l | sort -t. -k 1,1n -k 2,2n -k 3,3n | tail -n 1)}" head="${head:-HEAD}" api_url="https://api.github.com" # get merged PR's. Better way is to query the API for these, but this is easier for pr in $(git log --oneline $base..$head | grep "Merge pull request" | awk '{gsub("#",""); print $5}') do # frustrated with trying to pull out the right values, fell back to ruby curl -s "$api_url/repos/$repo/pulls/$pr" | ruby -rjson -e 'pr=JSON.parse(STDIN.read); puts "* #{pr[%q(title)]} {##{pr[%q(number)]}}[#{pr[%q(html_url)]}]"' done ruby-net-ldap-0.17.1/script/ldap-docker000077500000000000000000000006411424751475600177220ustar00rootroot00000000000000#!/usr/bin/env bash # Usage: script/ldap-docker # # Starts a openldap docker container ready for integration tests docker run --rm -ti \ --hostname ldap.example.org \ --env LDAP_TLS_VERIFY_CLIENT=try \ -p 389:389 -p 636:636 \ -v "$(pwd)"/test/fixtures/ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom \ --name my-openldap-container \ osixia/openldap:1.3.0 --copy-service --loglevel debugruby-net-ldap-0.17.1/script/package000077500000000000000000000002341424751475600171260ustar00rootroot00000000000000#!/usr/bin/env bash # Usage: script/package # Updates the gemspec and builds a new gem in the pkg directory. mkdir -p pkg gem build *.gemspec mv *.gem pkg ruby-net-ldap-0.17.1/script/release000077500000000000000000000005621424751475600171570ustar00rootroot00000000000000#!/usr/bin/env bash # Usage: script/release # Build the package, tag a commit, push it to origin, and then release the # package publicly. set -e version="$(script/package | grep Version: | awk '{print $2}')" [ -n "$version" ] || exit 1 echo $version git tag "v$version" -m "Release $version" git push origin git push origin "v$version" gem push pkg/*-${version}.gem ruby-net-ldap-0.17.1/test/000077500000000000000000000000001424751475600152615ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/ber/000077500000000000000000000000001424751475600160315ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/ber/core_ext/000077500000000000000000000000001424751475600176415ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/ber/core_ext/test_array.rb000066400000000000000000000012601424751475600223420ustar00rootroot00000000000000require_relative '../../test_helper' class TestBERArrayExtension < Test::Unit::TestCase def test_control_code_array control_codes = [] control_codes << ['1.2.3'.to_ber, true.to_ber].to_ber_sequence control_codes << ['1.7.9'.to_ber, false.to_ber].to_ber_sequence control_codes = control_codes.to_ber_sequence res = [['1.2.3', true], ['1.7.9', false]].to_ber_control assert_equal control_codes, res end def test_wrap_array_if_not_nested result1 = ['1.2.3', true].to_ber_control result2 = [['1.2.3', true]].to_ber_control assert_equal result2, result1 end def test_empty_string_if_empty_array assert_equal "", [].to_ber_control end end ruby-net-ldap-0.17.1/test/ber/core_ext/test_string.rb000066400000000000000000000014671424751475600225430ustar00rootroot00000000000000require_relative '../../test_helper' class TestBERStringExtension < Test::Unit::TestCase def setup @bind_request = "0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus UNCONSUMED".b @result = @bind_request.read_ber!(Net::LDAP::AsnSyntax) end def test_parse_ber assert_equal [1, [3, "Administrator", "ad_is_bogus"]], @result end def test_unconsumed_message assert_equal " UNCONSUMED", @bind_request end def test_exception_does_not_modify_string original = "0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus".b duplicate = original.dup flexmock(StringIO).new_instances.should_receive(:read_ber).and_raise(Net::BER::BerError) duplicate.read_ber!(Net::LDAP::AsnSyntax) rescue Net::BER::BerError assert_equal original, duplicate end end ruby-net-ldap-0.17.1/test/ber/test_ber.rb000066400000000000000000000106251424751475600201710ustar00rootroot00000000000000require_relative '../test_helper' class TestBEREncoding < Test::Unit::TestCase def test_empty_array assert_equal [], [].to_ber.read_ber end def test_array ary = [1, 2, 3] encoded_ary = ary.map(&:to_ber).to_ber assert_equal ary, encoded_ary.read_ber end # http://tools.ietf.org/html/rfc4511#section-5.1 def test_true assert_equal "\x01\x01\xFF".b, true.to_ber end def test_false assert_equal "\x01\x01\x00", false.to_ber end # Sample based { 0 => "\x02\x01\x00", 1 => "\x02\x01\x01", 127 => "\x02\x01\x7F", 128 => "\x02\x02\x00\x80", 255 => "\x02\x02\x00\xFF", 256 => "\x02\x02\x01\x00", 65535 => "\x02\x03\x00\xFF\xFF", 65536 => "\x02\x03\x01\x00\x00", 8388607 => "\x02\x03\x7F\xFF\xFF", 8388608 => "\x02\x04\x00\x80\x00\x00", 16_777_215 => "\x02\x04\x00\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 => "\x02\x01\x05", 500 => "\x02\x02\x01\xf4", 50_000 => "\x02\x03\x00\xC3\x50", 5_000_000_000 => "\x02\x05\x01\x2a\x05\xF2\x00", # negatives -1 => "\x02\x01\xFF", -127 => "\x02\x01\x81", -128 => "\x02\x01\x80", -255 => "\x02\x02\xFF\x01", -256 => "\x02\x02\xFF\x00", -65535 => "\x02\x03\xFF\x00\x01", -65536 => "\x02\x03\xFF\x00\x00", -65537 => "\x02\x03\xFE\xFF\xFF", -8388607 => "\x02\x03\x80\x00\x01", -8388608 => "\x02\x03\x80\x00\x00", -16_777_215 => "\x02\x04\xFF\x00\x00\x01", }.each do |number, expected_encoding| define_method "test_encode_#{number}" do assert_equal expected_encoding.b, number.to_ber end define_method "test_decode_encoded_#{number}" do assert_equal number, expected_encoding.b.read_ber end end # Round-trip encoding: This is mostly to be sure to cover Bignums well. def test_powers_of_two 100.times do |p| n = 2 << p assert_equal n, n.to_ber.read_ber end end def test_powers_of_ten 100.times do |p| n = 5 * 10**p assert_equal n, n.to_ber.read_ber end end if "Ruby 1.9".respond_to?(:encoding) def test_encode_utf8_strings assert_equal "\x04\x02\xC3\xA5".b, "\u00e5".force_encoding("UTF-8").to_ber end def test_utf8_encodable_strings assert_equal "\x04\nteststring", "teststring".encode("US-ASCII").to_ber end def test_encode_binary_data # This is used for searching for GUIDs in Active Directory assert_equal "\x04\x10" + "j1\xB4\xA1*\xA2zA\xAC\xA9`?'\xDDQ\x16".b, ["6a31b4a12aa27a41aca9603f27dd5116"].pack("H*").to_ber_bin end def test_non_utf8_encodable_strings assert_equal "\x04\x01\x81".b, "\x81".to_ber end end end class TestBERDecoding < Test::Unit::TestCase def test_decode_number assert_equal 6, "\002\001\006".read_ber(Net::LDAP::AsnSyntax) end def test_decode_string assert_equal "testing", "\004\007testing".read_ber(Net::LDAP::AsnSyntax) end def test_decode_ldap_bind_request assert_equal [1, [3, "Administrator", "ad_is_bogus"]], "0$\002\001\001`\037\002\001\003\004\rAdministrator\200\vad_is_bogus".read_ber(Net::LDAP::AsnSyntax) end end class TestBERIdentifiedString < Test::Unit::TestCase def test_binary_data data = ["6a31b4a12aa27a41aca9603f27dd5116"].pack("H*").force_encoding("ASCII-8BIT") bis = Net::BER::BerIdentifiedString.new(data) assert bis.valid_encoding?, "should be a valid encoding" assert_equal "ASCII-8BIT", bis.encoding.name end def test_ascii_data_in_utf8 data = "some text".force_encoding("UTF-8") bis = Net::BER::BerIdentifiedString.new(data) assert bis.valid_encoding?, "should be a valid encoding" assert_equal "UTF-8", bis.encoding.name end def test_umlaut_data_in_utf8 data = "Müller".force_encoding("UTF-8") bis = Net::BER::BerIdentifiedString.new(data) assert bis.valid_encoding?, "should be a valid encoding" assert_equal "UTF-8", bis.encoding.name end def test_utf8_data_in_utf8 data = ["e4b8ad"].pack("H*").force_encoding("UTF-8") bis = Net::BER::BerIdentifiedString.new(data) assert bis.valid_encoding?, "should be a valid encoding" assert_equal "UTF-8", bis.encoding.name end end ruby-net-ldap-0.17.1/test/fixtures/000077500000000000000000000000001424751475600171325ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/fixtures/ca/000077500000000000000000000000001424751475600175155ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/fixtures/ca/docker-ca.pem000066400000000000000000000020221424751475600220440ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIC0zCCAlmgAwIBAgIUCfQ+m0pgZ/BjYAJvxrn/bdGNZokwCgYIKoZIzj0EAwMw gZYxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxBMUEgQ2FyIFdhc2gxJDAiBgNVBAsT G0luZm9ybWF0aW9uIFRlY2hub2xvZ3kgRGVwLjEUMBIGA1UEBxMLQWxidXF1ZXJx dWUxEzARBgNVBAgTCk5ldyBNZXhpY28xHzAdBgNVBAMTFmRvY2tlci1saWdodC1i YXNlaW1hZ2UwHhcNMTUxMjIzMTM1MzAwWhcNMjAxMjIxMTM1MzAwWjCBljELMAkG A1UEBhMCVVMxFTATBgNVBAoTDEExQSBDYXIgV2FzaDEkMCIGA1UECxMbSW5mb3Jt YXRpb24gVGVjaG5vbG9neSBEZXAuMRQwEgYDVQQHEwtBbGJ1cXVlcnF1ZTETMBEG A1UECBMKTmV3IE1leGljbzEfMB0GA1UEAxMWZG9ja2VyLWxpZ2h0LWJhc2VpbWFn ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMZf/12pupAgl8Sm+j8GmjNeNbSFAZWW oTmIvf2Mu4LWPHy4bTldkQgHUbBpT3xWz8f0lB/ru7596CHsGoL2A28hxuclq5hb Ux1yrIt3bJIY3TuiX25HGTe6kGCJPB1aLaNmMGQwDgYDVR0PAQH/BAQDAgEGMBIG A1UdEwEB/wQIMAYBAf8CAQIwHQYDVR0OBBYEFE+l6XolXDAYnGLTl4W6ULKHrm74 MB8GA1UdIwQYMBaAFE+l6XolXDAYnGLTl4W6ULKHrm74MAoGCCqGSM49BAMDA2gA MGUCMQCXLZj8okyxW6UTL7hribUUbu63PbjuwIXnwi420DdNsvA9A7fcQEXScWFL XAGC8rkCMGcqwXZPSRfwuI9r+R11gTrP92hnaVxs9sjRikctpkQpOyNlIXFPopFK 8FdfWPypvA== -----END CERTIFICATE----- ruby-net-ldap-0.17.1/test/fixtures/ldif/000077500000000000000000000000001424751475600200505ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/fixtures/ldif/06-retcode.ldif000066400000000000000000000063461424751475600225710ustar00rootroot00000000000000dn: cn=module{0},cn=config changetype: modify add: olcModuleLoad olcModuleLoad: retcode # source: http://www.opensource.apple.com/source/OpenLDAP/OpenLDAP-186/OpenLDAP/tests/data/retcode.conf?txt dn: olcOverlay={2}retcode,olcDatabase={1}{{ LDAP_BACKEND }},cn=config changetype: add objectClass: olcConfig objectClass: olcRetcodeConfig objectClass: olcOverlayConfig objectClass: top olcOverlay: retcode olcRetcodeParent: ou=Retcodes,dc=example,dc=org olcRetcodeInDir: TRUE olcRetcodeSleep: 0 olcRetcodeItem: "cn=success" 0x00 olcRetcodeItem: "cn=success w/ delay" 0x00 sleeptime=2 olcRetcodeItem: "cn=operationsError" 0x01 olcRetcodeItem: "cn=protocolError" 0x02 olcRetcodeItem: "cn=timeLimitExceeded" 0x03 op=search olcRetcodeItem: "cn=sizeLimitExceeded" 0x04 op=search olcRetcodeItem: "cn=compareFalse" 0x05 op=compare olcRetcodeItem: "cn=compareTrue" 0x06 op=compare olcRetcodeItem: "cn=authMethodNotSupported" 0x07 olcRetcodeItem: "cn=strongAuthNotSupported" 0x07 text="same as authMethodNotSupported" olcRetcodeItem: "cn=strongAuthRequired" 0x08 olcRetcodeItem: "cn=strongerAuthRequired" 0x08 text="same as strongAuthRequired" olcRetcodeItem: "cn=referral" 0x0a text="LDAPv3" ref="ldap://:9019" olcRetcodeItem: "cn=adminLimitExceeded" 0x0b text="LDAPv3" olcRetcodeItem: "cn=unavailableCriticalExtension" 0x0c text="LDAPv3" olcRetcodeItem: "cn=confidentialityRequired" 0x0d text="LDAPv3" olcRetcodeItem: "cn=saslBindInProgress" 0x0e text="LDAPv3" olcRetcodeItem: "cn=noSuchAttribute" 0x10 olcRetcodeItem: "cn=undefinedAttributeType" 0x11 olcRetcodeItem: "cn=inappropriateMatching" 0x12 olcRetcodeItem: "cn=constraintViolation" 0x13 olcRetcodeItem: "cn=attributeOrValueExists" 0x14 olcRetcodeItem: "cn=invalidAttributeSyntax" 0x15 olcRetcodeItem: "cn=noSuchObject" 0x20 olcRetcodeItem: "cn=aliasProblem" 0x21 olcRetcodeItem: "cn=invalidDNSyntax" 0x22 olcRetcodeItem: "cn=aliasDereferencingProblem" 0x24 olcRetcodeItem: "cn=proxyAuthzFailure" 0x2F text="LDAPv3 proxy authorization" olcRetcodeItem: "cn=inappropriateAuthentication" 0x30 olcRetcodeItem: "cn=invalidCredentials" 0x31 olcRetcodeItem: "cn=insufficientAccessRights" 0x32 olcRetcodeItem: "cn=busy" 0x33 olcRetcodeItem: "cn=unavailable" 0x34 olcRetcodeItem: "cn=unwillingToPerform" 0x35 olcRetcodeItem: "cn=loopDetect" 0x36 olcRetcodeItem: "cn=namingViolation" 0x40 olcRetcodeItem: "cn=objectClassViolation" 0x41 olcRetcodeItem: "cn=notAllowedOnNonleaf" 0x42 olcRetcodeItem: "cn=notAllowedOnRDN" 0x43 olcRetcodeItem: "cn=entryAlreadyExists" 0x44 olcRetcodeItem: "cn=objectClassModsProhibited" 0x45 olcRetcodeItem: "cn=resultsTooLarge" 0x46 text="CLDAP" olcRetcodeItem: "cn=affectsMultipleDSAs" 0x47 text="LDAPv3" olcRetcodeItem: "cn=other" 0x50 olcRetcodeItem: "cn=cupResourcesExhausted" 0x71 olcRetcodeItem: "cn=cupSecurityViolation" 0x72 olcRetcodeItem: "cn=cupInvalidData" 0x73 olcRetcodeItem: "cn=cupUnsupportedScheme" 0x74 olcRetcodeItem: "cn=cupReloadRequired" 0x75 olcRetcodeItem: "cn=cancelled" 0x76 olcRetcodeItem: "cn=noSuchOperation" 0x77 olcRetcodeItem: "cn=tooLate" 0x78 olcRetcodeItem: "cn=cannotCancel" 0x79 olcRetcodeItem: "cn=syncRefreshRequired" 0x4100 olcRetcodeItem: "cn=noOperation" 0x410e olcRetcodeItem: "cn=assertionFailed" 0x410f olcRetcodeItem: "cn=noReferralsFound" 0x4110 olcRetcodeItem: "cn=cannotChain" 0x4111 ruby-net-ldap-0.17.1/test/fixtures/ldif/50-seed.ldif000066400000000000000000000242331424751475600220560ustar00rootroot00000000000000dn: ou=People,dc=example,dc=org objectClass: top objectClass: organizationalUnit ou: People dn: ou=Groups,dc=example,dc=org objectClass: top objectClass: organizationalUnit ou: Groups # Directory Superuser dn: uid=admin,dc=example,dc=org uid: admin cn: system administrator sn: administrator objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson displayName: Directory Superuser userPassword: passworD1 # Users 1-10 dn: uid=user1,ou=People,dc=example,dc=org uid: user1 cn: user1 sn: user1 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user1@rubyldap.com dn: uid=user2,ou=People,dc=example,dc=org uid: user2 cn: user2 sn: user2 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user2@rubyldap.com dn: uid=user3,ou=People,dc=example,dc=org uid: user3 cn: user3 sn: user3 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user3@rubyldap.com dn: uid=user4,ou=People,dc=example,dc=org uid: user4 cn: user4 sn: user4 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user4@rubyldap.com dn: uid=user5,ou=People,dc=example,dc=org uid: user5 cn: user5 sn: user5 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user5@rubyldap.com dn: uid=user6,ou=People,dc=example,dc=org uid: user6 cn: user6 sn: user6 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user6@rubyldap.com dn: uid=user7,ou=People,dc=example,dc=org uid: user7 cn: user7 sn: user7 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user7@rubyldap.com dn: uid=user8,ou=People,dc=example,dc=org uid: user8 cn: user8 sn: user8 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user8@rubyldap.com dn: uid=user9,ou=People,dc=example,dc=org uid: user9 cn: user9 sn: user9 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user9@rubyldap.com dn: uid=user10,ou=People,dc=example,dc=org uid: user10 cn: user10 sn: user10 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: user10@rubyldap.com # Emailless User dn: uid=emailless-user1,ou=People,dc=example,dc=org uid: emailless-user1 cn: emailless-user1 sn: emailless-user1 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 # Groupless User dn: uid=groupless-user1,ou=People,dc=example,dc=org uid: groupless-user1 cn: groupless-user1 sn: groupless-user1 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 # Admin User dn: uid=admin1,ou=People,dc=example,dc=org uid: admin1 cn: admin1 sn: admin1 objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson userPassword: passworD1 mail: admin1@rubyldap.com # Groups dn: cn=ghe-users,ou=Groups,dc=example,dc=org cn: ghe-users objectClass: groupOfNames member: uid=user1,ou=People,dc=example,dc=org member: uid=emailless-user1,ou=People,dc=example,dc=org dn: cn=all-users,ou=Groups,dc=example,dc=org cn: all-users objectClass: groupOfNames member: cn=ghe-users,ou=Groups,dc=example,dc=org member: uid=user1,ou=People,dc=example,dc=org member: uid=user2,ou=People,dc=example,dc=org member: uid=user3,ou=People,dc=example,dc=org member: uid=user4,ou=People,dc=example,dc=org member: uid=user5,ou=People,dc=example,dc=org member: uid=user6,ou=People,dc=example,dc=org member: uid=user7,ou=People,dc=example,dc=org member: uid=user8,ou=People,dc=example,dc=org member: uid=user9,ou=People,dc=example,dc=org member: uid=user10,ou=People,dc=example,dc=org member: uid=emailless-user1,ou=People,dc=example,dc=org dn: cn=ghe-admins,ou=Groups,dc=example,dc=org cn: ghe-admins objectClass: groupOfNames member: uid=admin1,ou=People,dc=example,dc=org dn: cn=all-admins,ou=Groups,dc=example,dc=org cn: all-admins objectClass: groupOfNames member: cn=ghe-admins,ou=Groups,dc=example,dc=org member: uid=admin1,ou=People,dc=example,dc=org dn: cn=n-member-group10,ou=Groups,dc=example,dc=org cn: n-member-group10 objectClass: groupOfNames member: uid=user1,ou=People,dc=example,dc=org member: uid=user2,ou=People,dc=example,dc=org member: uid=user3,ou=People,dc=example,dc=org member: uid=user4,ou=People,dc=example,dc=org member: uid=user5,ou=People,dc=example,dc=org member: uid=user6,ou=People,dc=example,dc=org member: uid=user7,ou=People,dc=example,dc=org member: uid=user8,ou=People,dc=example,dc=org member: uid=user9,ou=People,dc=example,dc=org member: uid=user10,ou=People,dc=example,dc=org dn: cn=nested-group1,ou=Groups,dc=example,dc=org cn: nested-group1 objectClass: groupOfNames member: uid=user1,ou=People,dc=example,dc=org member: uid=user2,ou=People,dc=example,dc=org member: uid=user3,ou=People,dc=example,dc=org member: uid=user4,ou=People,dc=example,dc=org member: uid=user5,ou=People,dc=example,dc=org dn: cn=nested-group2,ou=Groups,dc=example,dc=org cn: nested-group2 objectClass: groupOfNames member: uid=user6,ou=People,dc=example,dc=org member: uid=user7,ou=People,dc=example,dc=org member: uid=user8,ou=People,dc=example,dc=org member: uid=user9,ou=People,dc=example,dc=org member: uid=user10,ou=People,dc=example,dc=org dn: cn=nested-groups,ou=Groups,dc=example,dc=org cn: nested-groups objectClass: groupOfNames member: cn=nested-group1,ou=Groups,dc=example,dc=org member: cn=nested-group2,ou=Groups,dc=example,dc=org dn: cn=n-member-nested-group1,ou=Groups,dc=example,dc=org cn: n-member-nested-group1 objectClass: groupOfNames member: cn=nested-group1,ou=Groups,dc=example,dc=org dn: cn=deeply-nested-group0.0.0,ou=Groups,dc=example,dc=org cn: deeply-nested-group0.0.0 objectClass: groupOfNames member: uid=user1,ou=People,dc=example,dc=org member: uid=user2,ou=People,dc=example,dc=org member: uid=user3,ou=People,dc=example,dc=org member: uid=user4,ou=People,dc=example,dc=org member: uid=user5,ou=People,dc=example,dc=org dn: cn=deeply-nested-group0.0.1,ou=Groups,dc=example,dc=org cn: deeply-nested-group0.0.1 objectClass: groupOfNames member: uid=user6,ou=People,dc=example,dc=org member: uid=user7,ou=People,dc=example,dc=org member: uid=user8,ou=People,dc=example,dc=org member: uid=user9,ou=People,dc=example,dc=org member: uid=user10,ou=People,dc=example,dc=org dn: cn=deeply-nested-group0.0,ou=Groups,dc=example,dc=org cn: deeply-nested-group0.0 objectClass: groupOfNames member: cn=deeply-nested-group0.0.0,ou=Groups,dc=example,dc=org member: cn=deeply-nested-group0.0.1,ou=Groups,dc=example,dc=org dn: cn=deeply-nested-group0,ou=Groups,dc=example,dc=org cn: deeply-nested-group0 objectClass: groupOfNames member: cn=deeply-nested-group0.0,ou=Groups,dc=example,dc=org dn: cn=deeply-nested-groups,ou=Groups,dc=example,dc=org cn: deeply-nested-groups objectClass: groupOfNames member: cn=deeply-nested-group0,ou=Groups,dc=example,dc=org dn: cn=n-depth-nested-group1,ou=Groups,dc=example,dc=org cn: n-depth-nested-group1 objectClass: groupOfNames member: cn=nested-group1,ou=Groups,dc=example,dc=org dn: cn=n-depth-nested-group2,ou=Groups,dc=example,dc=org cn: n-depth-nested-group2 objectClass: groupOfNames member: cn=n-depth-nested-group1,ou=Groups,dc=example,dc=org dn: cn=n-depth-nested-group3,ou=Groups,dc=example,dc=org cn: n-depth-nested-group3 objectClass: groupOfNames member: cn=n-depth-nested-group2,ou=Groups,dc=example,dc=org dn: cn=n-depth-nested-group4,ou=Groups,dc=example,dc=org cn: n-depth-nested-group4 objectClass: groupOfNames member: cn=n-depth-nested-group3,ou=Groups,dc=example,dc=org dn: cn=n-depth-nested-group5,ou=Groups,dc=example,dc=org cn: n-depth-nested-group5 objectClass: groupOfNames member: cn=n-depth-nested-group4,ou=Groups,dc=example,dc=org dn: cn=n-depth-nested-group6,ou=Groups,dc=example,dc=org cn: n-depth-nested-group6 objectClass: groupOfNames member: cn=n-depth-nested-group5,ou=Groups,dc=example,dc=org dn: cn=n-depth-nested-group7,ou=Groups,dc=example,dc=org cn: n-depth-nested-group7 objectClass: groupOfNames member: cn=n-depth-nested-group6,ou=Groups,dc=example,dc=org dn: cn=n-depth-nested-group8,ou=Groups,dc=example,dc=org cn: n-depth-nested-group8 objectClass: groupOfNames member: cn=n-depth-nested-group7,ou=Groups,dc=example,dc=org dn: cn=n-depth-nested-group9,ou=Groups,dc=example,dc=org cn: n-depth-nested-group9 objectClass: groupOfNames member: cn=n-depth-nested-group8,ou=Groups,dc=example,dc=org dn: cn=head-group,ou=Groups,dc=example,dc=org cn: head-group objectClass: groupOfNames member: cn=tail-group,ou=Groups,dc=example,dc=org member: uid=user1,ou=People,dc=example,dc=org member: uid=user2,ou=People,dc=example,dc=org member: uid=user3,ou=People,dc=example,dc=org member: uid=user4,ou=People,dc=example,dc=org member: uid=user5,ou=People,dc=example,dc=org dn: cn=tail-group,ou=Groups,dc=example,dc=org cn: tail-group objectClass: groupOfNames member: cn=head-group,ou=Groups,dc=example,dc=org member: uid=user6,ou=People,dc=example,dc=org member: uid=user7,ou=People,dc=example,dc=org member: uid=user8,ou=People,dc=example,dc=org member: uid=user9,ou=People,dc=example,dc=org member: uid=user10,ou=People,dc=example,dc=org dn: cn=recursively-nested-groups,ou=Groups,dc=example,dc=org cn: recursively-nested-groups objectClass: groupOfNames member: cn=head-group,ou=Groups,dc=example,dc=org member: cn=tail-group,ou=Groups,dc=example,dc=org # posixGroup dn: cn=posix-group1,ou=Groups,dc=example,dc=org cn: posix-group1 objectClass: posixGroup gidNumber: 1001 memberUid: user1 memberUid: user2 memberUid: user3 memberUid: user4 memberUid: user5 # missing members dn: cn=missing-users,ou=Groups,dc=example,dc=org cn: missing-users objectClass: groupOfNames member: uid=user1,ou=People,dc=example,dc=org member: uid=user2,ou=People,dc=example,dc=org member: uid=nonexistent-user,ou=People,dc=example,dc=org ruby-net-ldap-0.17.1/test/integration/000077500000000000000000000000001424751475600176045ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/integration/test_add.rb000066400000000000000000000011711424751475600217200ustar00rootroot00000000000000require_relative '../test_helper' class TestAddIntegration < LDAPIntegrationTestCase def setup super @dn = "uid=added-user1,ou=People,dc=example,dc=org" end def test_add attrs = { objectclass: %w(top inetOrgPerson organizationalPerson person), uid: "added-user1", cn: "added-user1", sn: "added-user1", mail: "added-user1@rubyldap.com", } assert @ldap.add(dn: @dn, attributes: attrs), @ldap.get_operation_result.inspect assert result = @ldap.search(base: @dn, scope: Net::LDAP::SearchScope_BaseObject).first end def teardown @ldap.delete dn: @dn end end ruby-net-ldap-0.17.1/test/integration/test_ber.rb000066400000000000000000000016111424751475600217370ustar00rootroot00000000000000require_relative '../test_helper' class TestBERIntegration < LDAPIntegrationTestCase # Test whether the TRUE boolean value is encoded correctly by performing a # search operation. def test_true_ber_encoding # request these attrs to simplify test; use symbols to match Entry#attribute_names attrs = [:dn, :uid, :cn, :mail] assert types_entry = @ldap.search( base: "dc=example,dc=org", filter: "(uid=user1)", size: 1, attributes: attrs, attributes_only: true, ).first # matches attributes we requested assert_equal attrs, types_entry.attribute_names # assert values are empty types_entry.each do |name, values| next if name == :dn assert values.empty? end assert_includes Net::LDAP::ResultCodesSearchSuccess, @ldap.get_operation_result.code, "should be a successful search operation" end end ruby-net-ldap-0.17.1/test/integration/test_bind.rb000066400000000000000000000166011424751475600221100ustar00rootroot00000000000000require_relative '../test_helper' class TestBindIntegration < LDAPIntegrationTestCase INTEGRATION_HOSTNAME = 'ldap.example.org'.freeze def test_bind_success assert @ldap.bind(BIND_CREDS), @ldap.get_operation_result.inspect end def test_bind_timeout @ldap.host = "10.255.255.1" # non-routable IP error = assert_raise Net::LDAP::Error do @ldap.bind BIND_CREDS end msgs = ['Operation timed out - user specified timeout', 'Connection timed out - user specified timeout'] assert_send([msgs, :include?, error.message]) end def test_bind_anonymous_fail refute @ldap.bind(BIND_CREDS.merge(password: '')), @ldap.get_operation_result.inspect result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeUnwillingToPerform, result.code assert_equal Net::LDAP::ResultStrings[Net::LDAP::ResultCodeUnwillingToPerform], result.message assert_equal "unauthenticated bind (DN with no password) disallowed", result.error_message assert_equal "", result.matched_dn end def test_bind_fail refute @ldap.bind(BIND_CREDS.merge(password: "not my password")), @ldap.get_operation_result.inspect end def test_bind_tls_with_cafile omit "We need to update our CA cert" @ldap.host = INTEGRATION_HOSTNAME @ldap.encryption( method: :start_tls, tls_options: TLS_OPTS.merge(ca_file: CA_FILE), ) assert @ldap.bind(BIND_CREDS), @ldap.get_operation_result.inspect end def test_bind_tls_with_bad_hostname_verify_none_no_ca_passes @ldap.host = INTEGRATION_HOSTNAME @ldap.encryption( method: :start_tls, tls_options: { verify_mode: OpenSSL::SSL::VERIFY_NONE }, ) assert @ldap.bind(BIND_CREDS), @ldap.get_operation_result.inspect end def test_bind_tls_with_bad_hostname_verify_none_no_ca_opt_merge_passes @ldap.host = 'cert.mismatch.example.org' @ldap.encryption( method: :start_tls, tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_NONE), ) assert @ldap.bind(BIND_CREDS), @ldap.get_operation_result.inspect end def test_bind_tls_with_bad_hostname_verify_peer_ca_fails omit "We need to update our CA cert" @ldap.host = 'cert.mismatch.example.org' @ldap.encryption( method: :start_tls, tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER, ca_file: CA_FILE }, ) error = assert_raise Net::LDAP::Error, Errno::ECONNREFUSED do @ldap.bind BIND_CREDS end assert_equal( "hostname \"#{@ldap.host}\" does not match the server certificate", error.message, ) end def test_bind_tls_with_bad_hostname_ca_default_opt_merge_fails omit "We need to update our CA cert" @ldap.host = 'cert.mismatch.example.org' @ldap.encryption( method: :start_tls, tls_options: TLS_OPTS.merge(ca_file: CA_FILE), ) error = assert_raise Net::LDAP::Error, Errno::ECONNREFUSED do @ldap.bind BIND_CREDS end assert_equal( "hostname \"#{@ldap.host}\" does not match the server certificate", error.message, ) end def test_bind_tls_with_bad_hostname_ca_no_opt_merge_fails omit "We need to update our CA cert" @ldap.host = 'cert.mismatch.example.org' @ldap.encryption( method: :start_tls, tls_options: { ca_file: CA_FILE }, ) error = assert_raise Net::LDAP::Error, Errno::ECONNREFUSED do @ldap.bind BIND_CREDS end assert_equal( "hostname \"#{@ldap.host}\" does not match the server certificate", error.message, ) end def test_bind_tls_with_valid_hostname_default_opts_passes omit "We need to update our CA cert" @ldap.host = INTEGRATION_HOSTNAME @ldap.encryption( method: :start_tls, tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_PEER, ca_file: CA_FILE), ) assert @ldap.bind(BIND_CREDS), @ldap.get_operation_result.inspect end def test_bind_tls_with_valid_hostname_just_verify_peer_ca_passes omit "We need to update our CA cert" @ldap.host = INTEGRATION_HOSTNAME @ldap.encryption( method: :start_tls, tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER, ca_file: CA_FILE }, ) assert @ldap.bind(BIND_CREDS), @ldap.get_operation_result.inspect end def test_bind_tls_with_bogus_hostname_system_ca_fails @ldap.host = 'cert.mismatch.example.org' @ldap.encryption(method: :start_tls, tls_options: {}) error = assert_raise Net::LDAP::Error, Errno::ECONNREFUSED do @ldap.bind BIND_CREDS end assert_equal( "hostname \"#{@ldap.host}\" does not match the server certificate", error.message, ) end def test_bind_tls_with_multiple_hosts omit "We need to update our CA cert" @ldap.host = nil @ldap.hosts = [[INTEGRATION_HOSTNAME, 389], [INTEGRATION_HOSTNAME, 389]] @ldap.encryption( method: :start_tls, tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_PEER, ca_file: CA_FILE), ) assert @ldap.bind(BIND_CREDS), @ldap.get_operation_result.inspect end def test_bind_tls_with_multiple_bogus_hosts # omit "We need to update our CA cert" @ldap.host = nil @ldap.hosts = [['cert.mismatch.example.org', 389], ['bogus.example.com', 389]] @ldap.encryption( method: :start_tls, tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_PEER, ca_file: CA_FILE), ) error = assert_raise Net::LDAP::Error, Net::LDAP::ConnectionError do @ldap.bind BIND_CREDS end assert_equal("Unable to connect to any given server: ", error.message.split("\n").shift) end def test_bind_tls_with_multiple_bogus_hosts_no_verification omit "We need to update our CA cert" @ldap.host = nil @ldap.hosts = [['cert.mismatch.example.org', 389], ['bogus.example.com', 389]] @ldap.encryption( method: :start_tls, tls_options: TLS_OPTS.merge(verify_mode: OpenSSL::SSL::VERIFY_NONE), ) assert @ldap.bind(BIND_CREDS), @ldap.get_operation_result.inspect end def test_bind_tls_with_multiple_bogus_hosts_ca_check_only_fails @ldap.host = nil @ldap.hosts = [['cert.mismatch.example.org', 389], ['bogus.example.com', 389]] @ldap.encryption( method: :start_tls, tls_options: { ca_file: CA_FILE }, ) error = assert_raise Net::LDAP::Error, Net::LDAP::ConnectionError do @ldap.bind BIND_CREDS end assert_equal("Unable to connect to any given server: ", error.message.split("\n").shift) end # This test is CI-only because we can't add the fixture CA # to the system CA store on people's dev boxes. def test_bind_tls_valid_hostname_system_ca_on_travis_passes omit "not sure how to install custom CA cert in travis" omit_unless ENV['TRAVIS'] == 'true' @ldap.host = INTEGRATION_HOSTNAME @ldap.encryption( method: :start_tls, tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER }, ) assert @ldap.bind(BIND_CREDS), @ldap.get_operation_result.inspect end end ruby-net-ldap-0.17.1/test/integration/test_delete.rb000066400000000000000000000046401424751475600224360ustar00rootroot00000000000000require_relative '../test_helper' class TestDeleteIntegration < LDAPIntegrationTestCase def setup super @dn = "uid=delete-user1,ou=People,dc=example,dc=org" attrs = { objectclass: %w(top inetOrgPerson organizationalPerson person), uid: "delete-user1", cn: "delete-user1", sn: "delete-user1", mail: "delete-user1@rubyldap.com", } unless @ldap.search(base: @dn, scope: Net::LDAP::SearchScope_BaseObject) assert @ldap.add(dn: @dn, attributes: attrs), @ldap.get_operation_result.inspect end assert @ldap.search(base: @dn, scope: Net::LDAP::SearchScope_BaseObject) @parent_dn = "uid=parent,ou=People,dc=example,dc=org" parent_attrs = { objectclass: %w(top inetOrgPerson organizationalPerson person), uid: "parent", cn: "parent", sn: "parent", mail: "parent@rubyldap.com", } @child_dn = "uid=child,uid=parent,ou=People,dc=example,dc=org" child_attrs = { objectclass: %w(top inetOrgPerson organizationalPerson person), uid: "child", cn: "child", sn: "child", mail: "child@rubyldap.com", } unless @ldap.search(base: @parent_dn, scope: Net::LDAP::SearchScope_BaseObject) assert @ldap.add(dn: @parent_dn, attributes: parent_attrs), @ldap.get_operation_result.inspect assert @ldap.add(dn: @child_dn, attributes: child_attrs), @ldap.get_operation_result.inspect end assert @ldap.search(base: @parent_dn, scope: Net::LDAP::SearchScope_BaseObject) assert @ldap.search(base: @child_dn, scope: Net::LDAP::SearchScope_BaseObject) end def test_delete assert @ldap.delete(dn: @dn), @ldap.get_operation_result.inspect refute @ldap.search(base: @dn, scope: Net::LDAP::SearchScope_BaseObject) result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeNoSuchObject, result.code assert_equal Net::LDAP::ResultStrings[Net::LDAP::ResultCodeNoSuchObject], result.message end def test_delete_tree assert @ldap.delete_tree(dn: @parent_dn), @ldap.get_operation_result.inspect refute @ldap.search(base: @parent_dn, scope: Net::LDAP::SearchScope_BaseObject) refute @ldap.search(base: @child_dn, scope: Net::LDAP::SearchScope_BaseObject) result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeNoSuchObject, result.code assert_equal Net::LDAP::ResultStrings[Net::LDAP::ResultCodeNoSuchObject], result.message end end ruby-net-ldap-0.17.1/test/integration/test_open.rb000066400000000000000000000053431424751475600221360ustar00rootroot00000000000000require_relative '../test_helper' class TestBindIntegration < LDAPIntegrationTestCase def test_binds_without_open events = @service.subscribe "bind.net_ldap_connection" @ldap.search(filter: "uid=user1", base: "ou=People,dc=example,dc=org", ignore_server_caps: true) @ldap.search(filter: "uid=user1", base: "ou=People,dc=example,dc=org", ignore_server_caps: true) assert_equal 2, events.size end def test_binds_with_open events = @service.subscribe "bind.net_ldap_connection" @ldap.open do @ldap.search(filter: "uid=user1", base: "ou=People,dc=example,dc=org", ignore_server_caps: true) @ldap.search(filter: "uid=user1", base: "ou=People,dc=example,dc=org", ignore_server_caps: true) end assert_equal 1, events.size end # NOTE: query for two or more entries so that the socket must be read # multiple times. # See The Problem: https://github.com/ruby-ldap/ruby-net-ldap/issues/136 def test_nested_search_without_open entries = [] nested_entry = nil @ldap.search(filter: "(|(uid=user1)(uid=user2))", base: "ou=People,dc=example,dc=org") do |entry| entries << entry.uid.first nested_entry ||= @ldap.search(filter: "uid=user3", base: "ou=People,dc=example,dc=org").first end assert_equal "user3", nested_entry.uid.first assert_equal %w(user1 user2), entries end def test_nested_search_with_open entries = [] nested_entry = nil @ldap.open do @ldap.search(filter: "(|(uid=user1)(uid=user2))", base: "ou=People,dc=example,dc=org") do |entry| entries << entry.uid.first nested_entry ||= @ldap.search(filter: "uid=user3", base: "ou=People,dc=example,dc=org").first end end assert_equal "user3", nested_entry.uid.first assert_equal %w(user1 user2), entries end def test_nested_add_with_open entries = [] nested_entry = nil dn = "uid=nested-open-added-user1,ou=People,dc=example,dc=org" attrs = { objectclass: %w(top inetOrgPerson organizationalPerson person), uid: "nested-open-added-user1", cn: "nested-open-added-user1", sn: "nested-open-added-user1", mail: "nested-open-added-user1@rubyldap.com", } @ldap.delete dn: dn @ldap.open do @ldap.search(filter: "(|(uid=user1)(uid=user2))", base: "ou=People,dc=example,dc=org") do |entry| entries << entry.uid.first nested_entry ||= begin assert @ldap.add(dn: dn, attributes: attrs), @ldap.get_operation_result.inspect @ldap.search(base: dn, scope: Net::LDAP::SearchScope_BaseObject).first end end end assert_equal %w(user1 user2), entries assert_equal "nested-open-added-user1", nested_entry.uid.first ensure @ldap.delete dn: dn end end ruby-net-ldap-0.17.1/test/integration/test_password_modify.rb000066400000000000000000000062231424751475600244040ustar00rootroot00000000000000require_relative '../test_helper' class TestPasswordModifyIntegration < LDAPIntegrationTestCase def setup super @admin_account = { dn: 'cn=admin,dc=example,dc=org', password: 'admin', method: :simple } @ldap.authenticate @admin_account[:dn], @admin_account[:password] @dn = 'uid=modify-password-user1,ou=People,dc=example,dc=org' attrs = { objectclass: %w(top inetOrgPerson organizationalPerson person), uid: 'modify-password-user1', cn: 'modify-password-user1', sn: 'modify-password-user1', mail: 'modify-password-user1@rubyldap.com', userPassword: 'admin', } unless @ldap.search(base: @dn, scope: Net::LDAP::SearchScope_BaseObject) assert @ldap.add(dn: @dn, attributes: attrs), @ldap.get_operation_result.inspect end assert @ldap.search(base: @dn, scope: Net::LDAP::SearchScope_BaseObject) @auth = { method: :simple, username: @dn, password: 'admin', } end def test_password_modify assert @ldap.password_modify(dn: @dn, auth: @auth, old_password: 'admin', new_password: 'passworD2') assert @ldap.get_operation_result.extended_response.nil?, 'Should not have generated a new password' refute @ldap.bind(username: @dn, password: 'admin', method: :simple), 'Old password should no longer be valid' assert @ldap.bind(username: @dn, password: 'passworD2', method: :simple), 'New password should be valid' end def test_password_modify_generate assert @ldap.password_modify(dn: @dn, auth: @auth, old_password: 'admin') generated_password = @ldap.get_operation_result.extended_response[0][0] assert generated_password, 'Should have generated a password' refute @ldap.bind(username: @dn, password: 'admin', method: :simple), 'Old password should no longer be valid' assert @ldap.bind(username: @dn, password: generated_password, method: :simple), 'New password should be valid' end def test_password_modify_generate_no_old_password assert @ldap.password_modify(dn: @dn, auth: @auth) generated_password = @ldap.get_operation_result.extended_response[0][0] assert generated_password, 'Should have generated a password' refute @ldap.bind(username: @dn, password: 'admin', method: :simple), 'Old password should no longer be valid' assert @ldap.bind(username: @dn, password: generated_password, method: :simple), 'New password should be valid' end def test_password_modify_overwrite_old_password assert @ldap.password_modify(dn: @dn, auth: @admin_account, new_password: 'passworD3') refute @ldap.bind(username: @dn, password: 'admin', method: :simple), 'Old password should no longer be valid' assert @ldap.bind(username: @dn, password: 'passworD3', method: :simple), 'New password should be valid' end def teardown @ldap.delete dn: @dn end end ruby-net-ldap-0.17.1/test/integration/test_return_codes.rb000066400000000000000000000034151424751475600236670ustar00rootroot00000000000000require_relative '../test_helper' # NOTE: These tests depend on the OpenLDAP retcode overlay. # See: section 12.12 http://www.openldap.org/doc/admin24/overlays.html class TestReturnCodeIntegration < LDAPIntegrationTestCase def test_open_error @ldap.authenticate "cn=fake", "creds" @ldap.open do result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeInvalidCredentials, result.code end end def test_operations_error refute @ldap.search(filter: "cn=operationsError", base: "ou=Retcodes,dc=example,dc=org") assert result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeOperationsError, result.code assert_equal Net::LDAP::ResultStrings[Net::LDAP::ResultCodeOperationsError], result.message end def test_protocol_error refute @ldap.search(filter: "cn=protocolError", base: "ou=Retcodes,dc=example,dc=org") assert result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeProtocolError, result.code assert_equal Net::LDAP::ResultStrings[Net::LDAP::ResultCodeProtocolError], result.message end def test_time_limit_exceeded assert @ldap.search(filter: "cn=timeLimitExceeded", base: "ou=Retcodes,dc=example,dc=org") assert result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeTimeLimitExceeded, result.code assert_equal Net::LDAP::ResultStrings[Net::LDAP::ResultCodeTimeLimitExceeded], result.message end def test_size_limit_exceeded assert @ldap.search(filter: "cn=sizeLimitExceeded", base: "ou=Retcodes,dc=example,dc=org") assert result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeSizeLimitExceeded, result.code assert_equal Net::LDAP::ResultStrings[Net::LDAP::ResultCodeSizeLimitExceeded], result.message end end ruby-net-ldap-0.17.1/test/integration/test_search.rb000066400000000000000000000041471424751475600224430ustar00rootroot00000000000000require_relative '../test_helper' class TestSearchIntegration < LDAPIntegrationTestCase def test_search entries = [] result = @ldap.search(base: "dc=example,dc=org") do |entry| assert_kind_of Net::LDAP::Entry, entry entries << entry end refute entries.empty? assert_equal entries, result end def test_search_without_result entries = [] result = @ldap.search(base: "dc=example,dc=org", return_result: false) do |entry| assert_kind_of Net::LDAP::Entry, entry entries << entry end assert result refute_equal entries, result end def test_search_filter_string entries = @ldap.search(base: "dc=example,dc=org", filter: "(uid=user1)") assert_equal 1, entries.size end def test_search_filter_object filter = Net::LDAP::Filter.eq("uid", "user1") | Net::LDAP::Filter.eq("uid", "user2") entries = @ldap.search(base: "dc=example,dc=org", filter: filter) assert_equal 2, entries.size end def test_search_constrained_attributes entry = @ldap.search(base: "uid=user1,ou=People,dc=example,dc=org", attributes: ["cn", "sn"]).first assert_equal [:cn, :dn, :sn], entry.attribute_names.sort # :dn is always included assert_empty entry[:mail] end def test_search_attributes_only entry = @ldap.search(base: "uid=user1,ou=People,dc=example,dc=org", attributes_only: true).first assert_empty entry[:cn], "unexpected attribute value: #{entry[:cn]}" end def test_search_timeout entries = [] events = @service.subscribe "search.net_ldap_connection" result = @ldap.search(base: "dc=example,dc=org", time: 5) do |entry| assert_kind_of Net::LDAP::Entry, entry entries << entry end payload, = events.pop assert_equal 5, payload[:time] assert_equal entries, result end # http://tools.ietf.org/html/rfc4511#section-4.5.1.4 def test_search_with_size entries = [] result = @ldap.search(base: "dc=example,dc=org", size: 1) do |entry| assert_kind_of Net::LDAP::Entry, entry entries << entry end assert_equal 1, result.size assert_equal entries, result end end ruby-net-ldap-0.17.1/test/support/000077500000000000000000000000001424751475600167755ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/support/vm/000077500000000000000000000000001424751475600174175ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/support/vm/openldap/000077500000000000000000000000001424751475600212215ustar00rootroot00000000000000ruby-net-ldap-0.17.1/test/support/vm/openldap/.gitignore000066400000000000000000000000121424751475600232020ustar00rootroot00000000000000/.vagrant ruby-net-ldap-0.17.1/test/test_auth_adapter.rb000066400000000000000000000006241424751475600213100ustar00rootroot00000000000000require 'test_helper' class TestAuthAdapter < Test::Unit::TestCase class FakeSocket def initialize(*args) end end def test_undefined_auth_adapter conn = Net::LDAP::Connection.new(host: 'ldap.example.com', port: 379, :socket_class => FakeSocket) assert_raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (foo)" do conn.bind(method: :foo) end end end ruby-net-ldap-0.17.1/test/test_dn.rb000066400000000000000000000021731424751475600172510ustar00rootroot00000000000000require_relative 'test_helper' require_relative '../lib/net/ldap/dn' class TestDN < Test::Unit::TestCase def test_escape assert_equal '\\,\\+\\"\\\\\\<\\>\\;', Net::LDAP::DN.escape(',+"\\<>;') end def test_escape_on_initialize dn = Net::LDAP::DN.new('cn', ',+"\\<>;', 'ou=company') assert_equal 'cn=\\,\\+\\"\\\\\\<\\>\\;,ou=company', dn.to_s end def test_to_a dn = Net::LDAP::DN.new('cn=James, ou=Company\\,\\20LLC') assert_equal ['cn', 'James', 'ou', 'Company, LLC'], dn.to_a end def test_to_a_parenthesis dn = Net::LDAP::DN.new('cn = \ James , ou = "Comp\28ny" ') assert_equal ['cn', ' James', 'ou', 'Comp(ny'], dn.to_a end def test_to_a_hash_symbol dn = Net::LDAP::DN.new('1.23.4= #A3B4D5 ,ou=Company') assert_equal ['1.23.4', '#A3B4D5', 'ou', 'Company'], dn.to_a end def test_bad_input_raises_error [ 'cn=James,', 'cn=#aa aa', 'cn="James', 'cn=J\ames', 'cn=\\', '1.2.d=Value', 'd1.2=Value', ].each do |input| dn = Net::LDAP::DN.new(input) assert_raises(Net::LDAP::InvalidDNError) { dn.to_a } end end end ruby-net-ldap-0.17.1/test/test_entry.rb000066400000000000000000000042041424751475600200060ustar00rootroot00000000000000require_relative 'test_helper' class TestEntry < Test::Unit::TestCase def setup @entry = Net::LDAP::Entry.new 'cn=Barbara,o=corp' end def test_dn assert_equal 'cn=Barbara,o=corp', @entry.dn end def test_empty_array_when_accessing_nonexistent_attribute assert_equal [], @entry['sn'] end def test_attribute_assignment @entry['sn'] = 'Jensen' assert_equal ['Jensen'], @entry['sn'] assert_equal ['Jensen'], @entry.sn assert_equal ['Jensen'], @entry[:sn] @entry[:sn] = 'Jensen' assert_equal ['Jensen'], @entry['sn'] assert_equal ['Jensen'], @entry.sn assert_equal ['Jensen'], @entry[:sn] @entry.sn = 'Jensen' assert_equal ['Jensen'], @entry['sn'] assert_equal ['Jensen'], @entry.sn assert_equal ['Jensen'], @entry[:sn] end def test_case_insensitive_attribute_names @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 def test_to_h @entry['sn'] = 'Jensen' expected = { dn: ['cn=Barbara,o=corp'], sn: ['Jensen'], } duplicate = @entry.to_h assert_equal expected, duplicate # check that changing the duplicate # does not affect the internal state duplicate.delete(:sn) assert_not_equal duplicate, @entry.to_h end def test_equal_operator entry_two = Net::LDAP::Entry.new 'cn=Barbara,o=corp' assert_equal @entry, entry_two @entry['sn'] = 'Jensen' assert_not_equal @entry, entry_two entry_two['sn'] = 'Jensen' assert_equal @entry, entry_two end end class TestEntryLDIF < Test::Unit::TestCase def setup @entry = Net::LDAP::Entry.from_single_ldif_string( %Q{dn: something foo: foo barAttribute: bar }, ) end def test_attribute assert_equal ['foo'], @entry.foo assert_equal ['foo'], @entry.Foo end def test_modify_attribute @entry.foo = 'bar' assert_equal ['bar'], @entry.foo @entry.fOo = 'baz' assert_equal ['baz'], @entry.foo end end ruby-net-ldap-0.17.1/test/test_filter.rb000066400000000000000000000202441424751475600201340ustar00rootroot00000000000000require_relative 'test_helper' 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::FilterSyntaxInvalidError) { Filter.from_rfc2254("") } end def test_invalid_filter assert_raises(Net::LDAP::OperatorError) do # 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 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 # tests ported over from rspec. Not sure if these overlap with the above # https://github.com/ruby-ldap/ruby-net-ldap/pull/121 class TestFilterRSpec < Test::Unit::TestCase def test_ex_convert assert_equal '(foo:=bar)', Net::LDAP::Filter.ex('foo', 'bar').to_s end def test_ex_rfc2254_roundtrip filter = Net::LDAP::Filter.ex('foo', 'bar') assert_equal filter, Net::LDAP::Filter.from_rfc2254(filter.to_s) end def test_ber_conversion filter = Net::LDAP::Filter.ex('foo', 'bar') ber = filter.to_ber assert_equal filter, Net::LDAP::Filter.parse_ber(ber.read_ber(Net::LDAP::AsnSyntax)) end [ '(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_with_index do |filter_str, index| define_method "test_decode_filter_#{index}" do filter = Net::LDAP::Filter.from_rfc2254(filter_str) assert_kind_of Net::LDAP::Filter, filter end define_method "test_ber_conversion_#{index}" do filter = Net::LDAP::Filter.from_rfc2254(filter_str) ber = Net::LDAP::Filter.from_rfc2254(filter_str).to_ber assert_equal filter, Net::LDAP::Filter.parse_ber(ber.read_ber(Net::LDAP::AsnSyntax)) end end def test_apostrophes assert_equal "(uid=O'Keefe)", Net::LDAP::Filter.construct("uid=O'Keefe").to_rfc2254 end def test_equals assert_equal Net::LDAP::Filter.eq('dn', 'f\2Aoo'), Net::LDAP::Filter.equals('dn', 'f*oo') end def test_begins assert_equal Net::LDAP::Filter.eq('dn', 'f\2Aoo*'), Net::LDAP::Filter.begins('dn', 'f*oo') end def test_ends assert_equal Net::LDAP::Filter.eq('dn', '*f\2Aoo'), Net::LDAP::Filter.ends('dn', 'f*oo') end def test_contains assert_equal Net::LDAP::Filter.eq('dn', '*f\2Aoo*'), Net::LDAP::Filter.contains('dn', 'f*oo') end def test_escape # escapes nul, *, (, ) and \\ assert_equal "\\00\\2A\\28\\29\\5C", Net::LDAP::Filter.escape("\0*()\\") end def test_well_known_ber_string ber = "\xa4\x2d" \ "\x04\x0b" "objectclass" \ "\x30\x1e" \ "\x80\x08" "foo" "*\\" "bar" \ "\x81\x08" "foo" "*\\" "bar" \ "\x82\x08" "foo" "*\\" "bar".b [ "foo" "\\2A\\5C" "bar", "foo" "\\2a\\5c" "bar", "foo" "\\2A\\5c" "bar", "foo" "\\2a\\5C" "bar", ].each do |escaped| # unescapes escaped characters filter = Net::LDAP::Filter.eq("objectclass", "#{escaped}*#{escaped}*#{escaped}") assert_equal ber, filter.to_ber end end def test_parse_ber_escapes_characters ber = "\xa4\x2d" \ "\x04\x0b" "objectclass" \ "\x30\x1e" \ "\x80\x08" "foo" "*\\" "bar" \ "\x81\x08" "foo" "*\\" "bar" \ "\x82\x08" "foo" "*\\" "bar".b escaped = Net::LDAP::Filter.escape("foo" "*\\" "bar") filter = Net::LDAP::Filter.parse_ber(ber.read_ber(Net::LDAP::AsnSyntax)) assert_equal "(objectclass=#{escaped}*#{escaped}*#{escaped})", filter.to_s end def test_unescape_fixnums filter = Net::LDAP::Filter.eq("objectclass", 3) assert_equal "\xA3\x10\x04\vobjectclass\x04\x013".b, filter.to_ber end end ruby-net-ldap-0.17.1/test/test_filter_parser.rb000066400000000000000000000016441424751475600215130ustar00rootroot00000000000000# encoding: utf-8 require_relative 'test_helper' class TestFilterParser < Test::Unit::TestCase def test_ascii assert_kind_of Net::LDAP::Filter, Net::LDAP::Filter::FilterParser.parse("(cn=name)") end def test_multibyte_characters assert_kind_of Net::LDAP::Filter, Net::LDAP::Filter::FilterParser.parse("(cn=名前)") end def test_brackets assert_kind_of Net::LDAP::Filter, Net::LDAP::Filter::FilterParser.parse("(cn=[{something}])") end def test_slash assert_kind_of Net::LDAP::Filter, Net::LDAP::Filter::FilterParser.parse("(departmentNumber=FOO//BAR/FOO)") end def test_colons assert_kind_of Net::LDAP::Filter, Net::LDAP::Filter::FilterParser.parse("(ismemberof=cn=edu:berkeley:app:calmessages:deans,ou=campus groups,dc=berkeley,dc=edu)") end def test_attr_tag assert_kind_of Net::LDAP::Filter, Net::LDAP::Filter::FilterParser.parse("(mail;primary=jane@example.org)") end end ruby-net-ldap-0.17.1/test/test_helper.rb000066400000000000000000000033251424751475600201270ustar00rootroot00000000000000# Add 'lib' to load path. require 'test/unit' require_relative '../lib/net/ldap' require 'flexmock/test_unit' # Whether integration tests should be run. INTEGRATION = ENV.fetch("INTEGRATION", "skip") != "skip" # The CA file to verify certs against for tests. # Override with CA_FILE env variable; otherwise checks for the VM-specific path # and falls back to the test/fixtures/cacert.pem for local testing. CA_FILE = ENV.fetch("CA_FILE") do if File.exist?("/etc/ssl/certs/cacert.pem") "/etc/ssl/certs/cacert.pem" else File.expand_path("fixtures/ca/docker-ca.pem", File.dirname(__FILE__)) end end BIND_CREDS = { method: :simple, username: "cn=admin,dc=example,dc=org", password: "admin", }.freeze TLS_OPTS = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.merge({}).freeze if RUBY_VERSION < "2.0" class String def b self end end end class MockInstrumentationService def initialize @events = {} end def instrument(event, payload) result = yield(payload) @events[event] ||= [] @events[event] << [payload, result] result end def subscribe(event) @events[event] ||= [] @events[event] end end class LDAPIntegrationTestCase < Test::Unit::TestCase # If integration tests aren't enabled, noop these tests. if !INTEGRATION def run(*) self end end def setup @service = MockInstrumentationService.new @ldap = Net::LDAP.new \ host: ENV.fetch('INTEGRATION_HOST', 'localhost'), port: ENV.fetch('INTEGRATION_PORT', 389), search_domains: %w(dc=example,dc=org), uid: 'uid', instrumentation_service: @service @ldap.authenticate "cn=admin,dc=example,dc=org", "admin" end end ruby-net-ldap-0.17.1/test/test_ldap.rb000066400000000000000000000064421424751475600175730ustar00rootroot00000000000000require_relative 'test_helper' class TestLDAPInstrumentation < Test::Unit::TestCase # Fake Net::LDAP::Connection for testing class FakeConnection # It's difficult to instantiate Net::LDAP::PDU objects. Faking out what we # need here until that object is brought under test and has it's constructor # cleaned up. class Result < Struct.new(:success?, :result_code); end def initialize @bind_success = Result.new(true, Net::LDAP::ResultCodeSuccess) @search_success = Result.new(true, Net::LDAP::ResultCodeSizeLimitExceeded) end def bind(args = {}) @bind_success end def search(*args) yield @search_success if block_given? @search_success end end def setup @connection = flexmock(:connection, :close => true) flexmock(Net::LDAP::Connection).should_receive(:new).and_return(@connection) @service = MockInstrumentationService.new @subject = Net::LDAP.new \ :host => "test.mocked.com", :port => 636, :force_no_page => true, # so server capabilities are not queried :instrumentation_service => @service end def test_instrument_bind events = @service.subscribe "bind.net_ldap" fake_connection = FakeConnection.new @subject.connection = fake_connection bind_result = fake_connection.bind assert @subject.bind payload, result = events.pop assert result assert_equal bind_result, payload[:bind] end def test_instrument_search events = @service.subscribe "search.net_ldap" fake_connection = FakeConnection.new @subject.connection = fake_connection entry = fake_connection.search refute_nil @subject.search(:filter => "(uid=user1)") payload, result = events.pop assert_equal [entry], result assert_equal [entry], payload[:result] assert_equal "(uid=user1)", payload[:filter] end def test_instrument_search_with_size events = @service.subscribe "search.net_ldap" fake_connection = FakeConnection.new @subject.connection = fake_connection entry = fake_connection.search refute_nil @subject.search(:filter => "(uid=user1)", :size => 1) payload, result = events.pop assert_equal [entry], result assert_equal [entry], payload[:result] assert_equal "(uid=user1)", payload[:filter] assert_equal result.size, payload[:size] end def test_obscure_auth password = "opensesame" assert_include(@subject.inspect, "anonymous") @subject.auth "joe_user", password assert_not_include(@subject.inspect, password) end def test_encryption enc = @subject.encryption('start_tls') assert_equal enc[:method], :start_tls end def test_normalize_encryption_symbol enc = @subject.send(:normalize_encryption, :start_tls) assert_equal enc, :method => :start_tls, :tls_options => {} end def test_normalize_encryption_nil enc = @subject.send(:normalize_encryption, nil) assert_equal enc, nil end def test_normalize_encryption_string enc = @subject.send(:normalize_encryption, 'start_tls') assert_equal enc, :method => :start_tls, :tls_options => {} end def test_normalize_encryption_hash enc = @subject.send(:normalize_encryption, :method => :start_tls, :tls_options => { :foo => :bar }) assert_equal enc, :method => :start_tls, :tls_options => { :foo => :bar } end end ruby-net-ldap-0.17.1/test/test_ldap_connection.rb000066400000000000000000000413621424751475600220120ustar00rootroot00000000000000require_relative 'test_helper' class TestLDAPConnection < Test::Unit::TestCase def capture_stderr stderr, $stderr = $stderr, StringIO.new yield $stderr.string ensure $stderr = stderr end # Fake socket for testing # # FakeTCPSocket.new("success", 636) # FakeTCPSocket.new("fail.SocketError", 636) # raises SocketError class FakeTCPSocket def initialize(host, port, socket_opts = {}) status, error = host.split(".") raise Object.const_get(error) if status == "fail" end end def test_list_of_hosts_with_first_host_successful hosts = [ ["success.host", 636], ["fail.SocketError", 636], ["fail.SocketError", 636], ] connection = Net::LDAP::Connection.new(:hosts => hosts, :socket_class => FakeTCPSocket) connection.socket end def test_list_of_hosts_with_first_host_failure hosts = [ ["fail.SocketError", 636], ["success.host", 636], ["fail.SocketError", 636], ] connection = Net::LDAP::Connection.new(:hosts => hosts, :socket_class => FakeTCPSocket) connection.socket end def test_list_of_hosts_with_all_hosts_failure hosts = [ ["fail.SocketError", 636], ["fail.SocketError", 636], ["fail.SocketError", 636], ] connection = Net::LDAP::Connection.new(:hosts => hosts, :socket_class => FakeTCPSocket) assert_raise Net::LDAP::ConnectionError do connection.socket end end # This belongs in test_ldap, not test_ldap_connection def test_result_for_connection_failed_is_set flexmock(Socket).should_receive(:tcp).and_raise(Errno::ECONNREFUSED) ldap_client = Net::LDAP.new(host: '127.0.0.1', port: 12345) assert_raise Errno::ECONNREFUSED do ldap_client.bind(method: :simple, username: 'asdf', password: 'asdf') end assert_equal(ldap_client.get_operation_result.code, 52) assert_equal(ldap_client.get_operation_result.message, 'Unavailable') end def test_unresponsive_host connection = Net::LDAP::Connection.new(:host => "fail.Errno::ETIMEDOUT", :port => 636, :socket_class => FakeTCPSocket) assert_raise Net::LDAP::Error do connection.socket end end def test_blocked_port connection = Net::LDAP::Connection.new(:host => "fail.SocketError", :port => 636, :socket_class => FakeTCPSocket) assert_raise Net::LDAP::Error do connection.socket end end def test_connection_refused connection = Net::LDAP::Connection.new(:host => "fail.Errno::ECONNREFUSED", :port => 636, :socket_class => FakeTCPSocket) stderr = capture_stderr do assert_raise Errno::ECONNREFUSED do connection.socket end end end def test_connection_timeout connection = Net::LDAP::Connection.new(:host => "fail.Errno::ETIMEDOUT", :port => 636, :socket_class => FakeTCPSocket) capture_stderr do assert_raise Net::LDAP::Error do connection.socket end end end def test_raises_unknown_exceptions connection = Net::LDAP::Connection.new(:host => "fail.StandardError", :port => 636, :socket_class => FakeTCPSocket) assert_raise StandardError do connection.socket end end 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 def test_write mock = flexmock("socket") mock.should_receive(:write).with([1.to_ber, "request"].to_ber_sequence).and_return(true) conn = Net::LDAP::Connection.new(:socket => mock) conn.send(:write, "request") end def test_write_with_controls mock = flexmock("socket") mock.should_receive(:write).with([1.to_ber, "request", "controls"].to_ber_sequence).and_return(true) conn = Net::LDAP::Connection.new(:socket => mock) conn.send(:write, "request", "controls") end def test_write_increments_msgid mock = flexmock("socket") mock.should_receive(:write).with([1.to_ber, "request1"].to_ber_sequence).and_return(true) mock.should_receive(:write).with([2.to_ber, "request2"].to_ber_sequence).and_return(true) conn = Net::LDAP::Connection.new(:socket => mock) conn.send(:write, "request1") conn.send(:write, "request2") end end class TestLDAPConnectionSocketReads < Test::Unit::TestCase def make_message(message_id, options = {}) options = { app_tag: Net::LDAP::PDU::SearchResult, code: Net::LDAP::ResultCodeSuccess, matched_dn: "", error_message: "", }.merge(options) result = Net::BER::BerIdentifiedArray.new([options[:code], options[:matched_dn], options[:error_message]]) result.ber_identifier = options[:app_tag] [message_id, result] end def test_queued_read_drains_queue_before_read result1a = make_message(1, error_message: "one") result1b = make_message(1, error_message: "two") mock = flexmock("socket") mock.should_receive(:read_ber).and_return(result1b) conn = Net::LDAP::Connection.new(:socket => mock) conn.message_queue[1].push Net::LDAP::PDU.new(result1a) assert msg1 = conn.queued_read(1) assert msg2 = conn.queued_read(1) assert_equal 1, msg1.message_id assert_equal "one", msg1.error_message assert_equal 1, msg2.message_id assert_equal "two", msg2.error_message end def test_queued_read_reads_until_message_id_match result1 = make_message(1) result2 = make_message(2) mock = flexmock("socket") mock.should_receive(:read_ber) .and_return(result1) .and_return(result2) conn = Net::LDAP::Connection.new(:socket => mock) assert result = conn.queued_read(2) assert_equal 2, result.message_id assert_equal 1, conn.queued_read(1).message_id end def test_queued_read_modify result1 = make_message(1, app_tag: Net::LDAP::PDU::SearchResult) result2 = make_message(2, app_tag: Net::LDAP::PDU::ModifyResponse) mock = flexmock("socket") mock.should_receive(:read_ber) .and_return(result1) .and_return(result2) mock.should_receive(:write) conn = Net::LDAP::Connection.new(:socket => mock) conn.next_msgid # simulates ongoing query conn.instance_variable_get("@msgid") assert result = conn.modify(dn: "uid=modified-user1,ou=People,dc=rubyldap,dc=com", operations: [[:add, :mail, "modified-user1@example.com"]]) assert result.success? assert_equal 2, result.message_id end def test_queued_read_add result1 = make_message(1, app_tag: Net::LDAP::PDU::SearchResult) result2 = make_message(2, app_tag: Net::LDAP::PDU::AddResponse) mock = flexmock("socket") mock.should_receive(:read_ber) .and_return(result1) .and_return(result2) mock.should_receive(:write) conn = Net::LDAP::Connection.new(:socket => mock) conn.next_msgid # simulates ongoing query assert result = conn.add(dn: "uid=added-user1,ou=People,dc=rubyldap,dc=com") assert result.success? assert_equal 2, result.message_id end def test_queued_read_rename result1 = make_message(1, app_tag: Net::LDAP::PDU::SearchResult) result2 = make_message(2, app_tag: Net::LDAP::PDU::ModifyRDNResponse) mock = flexmock("socket") mock.should_receive(:read_ber) .and_return(result1) .and_return(result2) mock.should_receive(:write) conn = Net::LDAP::Connection.new(:socket => mock) conn.next_msgid # simulates ongoing query assert result = conn.rename( olddn: "uid=renamable-user1,ou=People,dc=rubyldap,dc=com", newrdn: "uid=renamed-user1", ) assert result.success? assert_equal 2, result.message_id end def test_queued_read_delete result1 = make_message(1, app_tag: Net::LDAP::PDU::SearchResult) result2 = make_message(2, app_tag: Net::LDAP::PDU::DeleteResponse) mock = flexmock("socket") mock.should_receive(:read_ber) .and_return(result1) .and_return(result2) mock.should_receive(:write) conn = Net::LDAP::Connection.new(:socket => mock) conn.next_msgid # simulates ongoing query assert result = conn.delete(dn: "uid=deletable-user1,ou=People,dc=rubyldap,dc=com") assert result.success? assert_equal 2, result.message_id end def test_queued_read_setup_encryption_with_start_tls result1 = make_message(1, app_tag: Net::LDAP::PDU::SearchResult) result2 = make_message(2, app_tag: Net::LDAP::PDU::ExtendedResponse) mock = flexmock("socket") mock.should_receive(:read_ber) .and_return(result1) .and_return(result2) mock.should_receive(:write) conn = Net::LDAP::Connection.new(:socket => mock) flexmock(Net::LDAP::Connection).should_receive(:wrap_with_ssl).with(mock, {}, nil) .and_return(mock) conn.next_msgid # simulates ongoing query assert result = conn.setup_encryption(method: :start_tls) assert_equal mock, result end def test_queued_read_bind_simple result1 = make_message(1, app_tag: Net::LDAP::PDU::SearchResult) result2 = make_message(2, app_tag: Net::LDAP::PDU::BindResult) mock = flexmock("socket") mock.should_receive(:read_ber) .and_return(result1) .and_return(result2) mock.should_receive(:write) conn = Net::LDAP::Connection.new(:socket => mock) conn.next_msgid # simulates ongoing query assert result = conn.bind( method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1", ) assert result.success? assert_equal 2, result.message_id end def test_queued_read_bind_sasl result1 = make_message(1, app_tag: Net::LDAP::PDU::SearchResult) result2 = make_message(2, app_tag: Net::LDAP::PDU::BindResult) mock = flexmock("socket") mock.should_receive(:read_ber) .and_return(result1) .and_return(result2) mock.should_receive(:write) conn = Net::LDAP::Connection.new(:socket => mock) conn.next_msgid # simulates ongoing query assert result = conn.bind( method: :sasl, mechanism: "fake", initial_credential: "passworD1", challenge_response: flexmock("challenge proc"), ) assert result.success? assert_equal 2, result.message_id end def test_invalid_pdu_type options = { code: Net::LDAP::ResultCodeSuccess, matched_dn: "", error_message: "", } ber = Net::BER::BerIdentifiedArray.new([options[:code], options[:matched_dn], options[:error_message]]) assert_raise Net::LDAP::PDU::Error do Net::LDAP::PDU.new([0, ber]) end end end class TestLDAPConnectionErrors < Test::Unit::TestCase def setup @tcp_socket = flexmock(:connection) @tcp_socket.should_receive(:write) flexmock(Socket).should_receive(:tcp).and_return(@tcp_socket) @connection = Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636) end def test_error_failed_operation ber = Net::BER::BerIdentifiedArray.new([Net::LDAP::ResultCodeUnwillingToPerform, "", "The provided password value was rejected by a password validator: The provided password did not contain enough characters from the character set 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. The minimum number of characters from that set that must be present in user passwords is 1"]) ber.ber_identifier = Net::LDAP::PDU::ModifyResponse @tcp_socket.should_receive(:read_ber).and_return([1, ber]) result = @connection.modify(:dn => "1", :operations => [[:replace, "mail", "something@sothsdkf.com"]]) assert result.failure?, "should be failure" assert_equal "The provided password value was rejected by a password validator: The provided password did not contain enough characters from the character set 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. The minimum number of characters from that set that must be present in user passwords is 1", result.error_message end def test_no_error_on_success ber = Net::BER::BerIdentifiedArray.new([Net::LDAP::ResultCodeSuccess, "", ""]) ber.ber_identifier = Net::LDAP::PDU::ModifyResponse @tcp_socket.should_receive(:read_ber).and_return([1, ber]) result = @connection.modify(:dn => "1", :operations => [[:replace, "mail", "something@sothsdkf.com"]]) assert result.success?, "should be success" assert_equal "", result.error_message end end class TestLDAPConnectionInstrumentation < Test::Unit::TestCase def setup @tcp_socket = flexmock(:connection) @tcp_socket.should_receive(:write) flexmock(Socket).should_receive(:tcp).and_return(@tcp_socket) @service = MockInstrumentationService.new @connection = Net::LDAP::Connection.new \ :host => 'test.mocked.com', :port => 636, :instrumentation_service => @service end def test_write_net_ldap_connection_event ber = Net::BER::BerIdentifiedArray.new([Net::LDAP::ResultCodeSuccess, "", ""]) ber.ber_identifier = Net::LDAP::PDU::BindResult read_result = [1, ber] @tcp_socket.should_receive(:read_ber).and_return(read_result) events = @service.subscribe "write.net_ldap_connection" result = @connection.bind(method: :anon) assert result.success?, "should be success" # a write event payload, result = events.pop assert payload.key?(:result) assert payload.key?(:content_length) end def test_read_net_ldap_connection_event ber = Net::BER::BerIdentifiedArray.new([Net::LDAP::ResultCodeSuccess, "", ""]) ber.ber_identifier = Net::LDAP::PDU::BindResult read_result = [1, ber] @tcp_socket.should_receive(:read_ber).and_return(read_result) events = @service.subscribe "read.net_ldap_connection" result = @connection.bind(method: :anon) assert result.success?, "should be success" # a read event payload, result = events.pop assert payload.key?(:result) assert_equal read_result, result end def test_parse_pdu_net_ldap_connection_event ber = Net::BER::BerIdentifiedArray.new([Net::LDAP::ResultCodeSuccess, "", ""]) ber.ber_identifier = Net::LDAP::PDU::BindResult read_result = [1, ber] @tcp_socket.should_receive(:read_ber).and_return(read_result) events = @service.subscribe "parse_pdu.net_ldap_connection" result = @connection.bind(method: :anon) assert result.success?, "should be success" # a parse_pdu event payload, result = events.pop assert payload.key?(:pdu) assert payload.key?(:app_tag) assert payload.key?(:message_id) assert_equal Net::LDAP::PDU::BindResult, payload[:app_tag] assert_equal 1, payload[:message_id] pdu = payload[:pdu] assert_equal Net::LDAP::ResultCodeSuccess, pdu.result_code end def test_bind_net_ldap_connection_event ber = Net::BER::BerIdentifiedArray.new([Net::LDAP::ResultCodeSuccess, "", ""]) ber.ber_identifier = Net::LDAP::PDU::BindResult bind_result = [1, ber] @tcp_socket.should_receive(:read_ber).and_return(bind_result) events = @service.subscribe "bind.net_ldap_connection" result = @connection.bind(method: :anon) assert result.success?, "should be success" # a read event payload, result = events.pop assert payload.key?(:result) assert result.success?, "should be success" end def test_search_net_ldap_connection_event # search data search_data_ber = Net::BER::BerIdentifiedArray.new([1, [ "uid=user1,ou=People,dc=rubyldap,dc=com", [["uid", ["user1"]]], ]]) search_data_ber.ber_identifier = Net::LDAP::PDU::SearchReturnedData search_data = [1, search_data_ber] # search result (end of results) search_result_ber = Net::BER::BerIdentifiedArray.new([Net::LDAP::ResultCodeSuccess, "", ""]) search_result_ber.ber_identifier = Net::LDAP::PDU::SearchResult search_result = [1, search_result_ber] @tcp_socket.should_receive(:read_ber).and_return(search_data) .and_return(search_result) events = @service.subscribe "search.net_ldap_connection" unread = @service.subscribe "search_messages_unread.net_ldap_connection" result = @connection.search(filter: "(uid=user1)", base: "ou=People,dc=rubyldap,dc=com") assert result.success?, "should be success" # a search event payload, result = events.pop assert payload.key?(:result) assert payload.key?(:filter) assert_equal "(uid=user1)", payload[:filter].to_s assert result # ensure no unread assert unread.empty?, "should not have any leftover unread messages" end end ruby-net-ldap-0.17.1/test/test_ldif.rb000066400000000000000000000066641424751475600175770ustar00rootroot00000000000000# $Id: testldif.rb 61 2006-04-18 20:55:55Z blackhedd $ require_relative 'test_helper' 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_version io = StringIO.new("version: 1") ds = Net::LDAP::Dataset.read_ldif(io) assert_equal "1", ds.version 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.key?("abcdefghijklmn")) end def test_ldif_with_continuation_lines_and_extra_whitespace ds1 = Net::LDAP::Dataset.read_ldif(StringIO.new("dn: abcdefg\r\n hijklmn\r\n\r\n")) assert_equal(true, ds1.key?("abcdefg hijklmn")) ds2 = Net::LDAP::Dataset.read_ldif(StringIO.new("dn: abcdefg\r\n hij klmn\r\n\r\n")) assert_equal(true, ds2.key?("abcdefghij klmn")) end def test_ldif_tab_is_not_continuation ds = Net::LDAP::Dataset.read_ldif(StringIO.new("dn: key\r\n\tnotcontinued\r\n\r\n")) assert_equal(true, ds.key?("key")) end def test_ldif_with_base64_dn str = "dn:: Q049QmFzZTY0IGRuIHRlc3QsT1U9VGVzdCxPVT1Vbml0cyxEQz1leGFtcGxlLERDPWNvbQ==\r\n\r\n" ds = Net::LDAP::Dataset.read_ldif(StringIO.new(str)) assert_equal(true, ds.key?("CN=Base64 dn test,OU=Test,OU=Units,DC=example,DC=com")) end def test_ldif_with_base64_dn_and_continuation_lines str = "dn:: Q049QmFzZTY0IGRuIHRlc3Qgd2l0aCBjb250aW51YXRpb24gbGluZSxPVT1UZXN0LE9VPVVua\r\n XRzLERDPWV4YW1wbGUsREM9Y29t\r\n\r\n" ds = Net::LDAP::Dataset.read_ldif(StringIO.new(str)) assert_equal(true, ds.key?("CN=Base64 dn test with continuation line,OU=Test,OU=Units,DC=example,DC=com")) end # TODO, INADEQUATE. We need some more tests # to verify the content. def test_ldif File.open(TestLdifFilename, "r") do |f| ds = Net::LDAP::Dataset.read_ldif(f) assert_equal(13, ds.length) end end # Must test folded lines and base64-encoded lines as well as normal ones. def test_to_ldif data = File.open(TestLdifFilename, "rb", &: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) do |type, value| case type when :dn assert_equal(dn_entries.first, value) dn_entries.shift end end assert_equal(entries.size, ds.size) assert_equal(entries.sort, ds.to_ldif.grep(/^dn:\s*/) { $'.chomp }) end def test_to_ldif_with_version ds = Net::LDAP::Dataset.new ds.version = "1" assert_equal "version: 1", ds.to_ldif_string.chomp end end ruby-net-ldap-0.17.1/test/test_password.rb000066400000000000000000000013551424751475600205130ustar00rootroot00000000000000# $Id: testpsw.rb 72 2006-04-24 21:58:14Z blackhedd $ require_relative 'test_helper' 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 def test_psw_with_ssha256_should_not_contain_linefeed flexmock(SecureRandom).should_receive(:random_bytes).and_return('\xE5\x8A\x99\xF8\xCB\x15GW\xE8\xEA\xAD\x0F\xBF\x95\xB0\xDC') assert_equal("{SSHA256}Cc7MXboTyUP5PnPAeJeCrgMy8+7Gus0sw7kBJuTrmf1ceEU1XHg4QVx4OTlceEY4XHhDQlx4MTVHV1x4RThceEVBXHhBRFx4MEZceEJGXHg5NVx4QjBceERD", Net::LDAP::Password.generate(:ssha256, "cashflow")) end end ruby-net-ldap-0.17.1/test/test_rename.rb000066400000000000000000000053741424751475600201250ustar00rootroot00000000000000require_relative 'test_helper' # 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 ruby-net-ldap-0.17.1/test/test_search.rb000066400000000000000000000017651424751475600201230ustar00rootroot00000000000000# -*- ruby encoding: utf-8 -*- require_relative 'test_helper' class TestSearch < Test::Unit::TestCase class FakeConnection def search(args) OpenStruct.new(:result_code => Net::LDAP::ResultCodeOperationsError, :message => "error", :success? => false) end end def setup @service = MockInstrumentationService.new @connection = Net::LDAP.new :instrumentation_service => @service @connection.instance_variable_set(:@open_connection, FakeConnection.new) end def test_true_result assert_nil @connection.search(:return_result => true) end def test_false_result refute @connection.search(:return_result => false) end def test_no_result assert_nil @connection.search end def test_instrumentation_publishes_event events = @service.subscribe "search.net_ldap" @connection.search(:filter => "test") payload, result = events.pop assert payload.key?(:result) assert payload.key?(:filter) assert_equal "test", payload[:filter] end end ruby-net-ldap-0.17.1/test/test_snmp.rb000066400000000000000000000074411424751475600176300ustar00rootroot00000000000000# $Id: testsnmp.rb 231 2006-12-21 15:09:29Z blackhedd $ require_relative 'test_helper' require_relative '../lib/net/snmp' class TestSnmp < Test::Unit::TestCase def self.raw_string(s) # Conveniently, String#b only needs to be called when it exists s.respond_to?(:b) ? s.b : s end SnmpGetRequest = raw_string("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 = raw_string("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 = raw_string("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) do data.read_ber(Net::SNMP::AsnSyntax) end 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) do Net::SnmpPdu.parse("aaaaaaaaaaaaaa") end 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 ruby-net-ldap-0.17.1/test/test_ssl_ber.rb000066400000000000000000000022411424751475600202750ustar00rootroot00000000000000require_relative 'test_helper' require 'timeout' class TestSSLBER < Test::Unit::TestCase # Transmits str to @to and reads it back from @from. # def transmit(str) Timeout.timeout(1) do @to.write(str) @to.close @from.read end end def setup @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. # # TODO: Replace test with real socket # https://github.com/ruby-ldap/ruby-net-ldap/pull/121#discussion_r18746386 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 def test_transmit_strings omit_if RUBY_PLATFORM == "java", "JRuby throws an error without a real socket" assert_equal "foo", transmit("foo") end def test_transmit_ber_encoded_numbers omit_if RUBY_PLATFORM == "java", "JRuby throws an error without a real socket" @to.write 1234.to_ber assert_equal 1234, @from.read_ber end end ruby-net-ldap-0.17.1/test/testdata.ldif000066400000000000000000000062531424751475600177400ustar00rootroot00000000000000# $Id: testdata.ldif 50 2006-04-17 17:57:33Z blackhedd $ # # This is test-data for an LDAP server in LDIF format. # dn: dc=bayshorenetworks,dc=com objectClass: dcObject objectClass: organization o: Bayshore Networks LLC dc: bayshorenetworks dn: cn=Manager,dc=bayshorenetworks,dc=com objectClass: organizationalrole cn: Manager dn: ou=people,dc=bayshorenetworks,dc=com objectClass: organizationalunit ou: people dn: ou=privileges,dc=bayshorenetworks,dc=com objectClass: organizationalunit ou: privileges dn: ou=roles,dc=bayshorenetworks,dc=com objectClass: organizationalunit ou: roles dn: ou=office,dc=bayshorenetworks,dc=com objectClass: organizationalunit ou: office dn: mail=nogoodnik@steamheat.net,ou=people,dc=bayshorenetworks,dc=com cn: Bob Fosse mail: nogoodnik@steamheat.net sn: Fosse ou: people objectClass: top objectClass: inetorgperson objectClass: authorizedperson hasAccessRole: uniqueIdentifier=engineer,ou=roles hasAccessRole: uniqueIdentifier=ldapadmin,ou=roles hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles hasAccessRole: uniqueIdentifier=ogilvy_eagle_user,ou=roles hasAccessRole: uniqueIdentifier=greenplug_user,ou=roles hasAccessRole: uniqueIdentifier=brandplace_logging_user,ou=roles hasAccessRole: uniqueIdentifier=brandplace_report_user,ou=roles hasAccessRole: uniqueIdentifier=workorder_user,ou=roles hasAccessRole: uniqueIdentifier=bayshore_eagle_user,ou=roles hasAccessRole: uniqueIdentifier=bayshore_eagle_superuser,ou=roles hasAccessRole: uniqueIdentifier=kledaras_user,ou=roles dn: mail=elephant@steamheat.net,ou=people,dc=bayshorenetworks,dc=com cn: Gwen Verdon mail: elephant@steamheat.net sn: Verdon ou: people objectClass: top objectClass: inetorgperson objectClass: authorizedperson hasAccessRole: uniqueIdentifier=brandplace_report_user,ou=roles hasAccessRole: uniqueIdentifier=engineer,ou=roles hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles hasAccessRole: uniqueIdentifier=ldapadmin,ou=roles dn: uniqueIdentifier=engineering,ou=privileges,dc=bayshorenetworks,dc=com uniqueIdentifier: engineering ou: privileges objectClass: accessPrivilege dn: uniqueIdentifier=engineer,ou=roles,dc=bayshorenetworks,dc=com uniqueIdentifier: engineer ou: roles objectClass: accessRole hasAccessPrivilege: uniqueIdentifier=engineering,ou=privileges dn: uniqueIdentifier=ldapadmin,ou=roles,dc=bayshorenetworks,dc=com uniqueIdentifier: ldapadmin ou: roles objectClass: accessRole dn: uniqueIdentifier=ldapsuperadmin,ou=roles,dc=bayshorenetworks,dc=com uniqueIdentifier: ldapsuperadmin ou: roles objectClass: accessRole dn: mail=catperson@steamheat.net,ou=people,dc=bayshorenetworks,dc=com cn: Sid Sorokin mail: catperson@steamheat.net sn: Sorokin ou: people objectClass: top objectClass: inetorgperson objectClass: authorizedperson hasAccessRole: uniqueIdentifier=engineer,ou=roles hasAccessRole: uniqueIdentifier=ogilvy_elephant_user,ou=roles hasAccessRole: uniqueIdentifier=ldapsuperadmin,ou=roles hasAccessRole: uniqueIdentifier=ogilvy_eagle_user,ou=roles hasAccessRole: uniqueIdentifier=greenplug_user,ou=roles hasAccessRole: uniqueIdentifier=workorder_user,ou=roles ruby-net-ldap-0.17.1/testserver/000077500000000000000000000000001424751475600165105ustar00rootroot00000000000000ruby-net-ldap-0.17.1/testserver/ldapserver.rb000066400000000000000000000135061424751475600212110ustar00rootroot00000000000000# $Id$ # # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved. # Gmail account: garbagecat10. # # This is an LDAP server intended for unit testing of Net::LDAP. # It implements as much of the protocol as we have the stomach # to implement but serves static data. Use ldapsearch to test # this server! # # To make this easier to write, we use the Ruby/EventMachine # reactor library. # #------------------------------------------------ module LdapServer LdapServerAsnSyntaxTemplate = { :application => { :constructed => { 0 => :array, # LDAP BindRequest 3 => :array # LDAP SearchRequest }, :primitive => { 2 => :string, # ldapsearch sends this to unbind }, }, :context_specific => { :primitive => { 0 => :string, # simple auth (password) 7 => :string # present filter }, :constructed => { 3 => :array # equality filter }, }, } def post_init $logger.info "Accepted LDAP connection" @authenticated = false end def receive_data data @data ||= ""; @data << data while pdu = @data.read_ber!(LdapServerAsnSyntax) begin handle_ldap_pdu pdu rescue $logger.error "closing connection due to error #{$!}" close_connection end end end def handle_ldap_pdu pdu tag_id = pdu[1].ber_identifier case tag_id when 0x60 handle_bind_request pdu when 0x63 handle_search_request pdu when 0x42 # bizarre thing, it's a null object (primitive application-2) # sent by ldapsearch to request an unbind (or a kiss-off, not sure which) close_connection_after_writing else $logger.error "received unknown packet-type #{tag_id}" close_connection_after_writing end end def handle_bind_request pdu # TODO, return a proper LDAP error instead of blowing up on version error if pdu[1][0] != 3 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(&:downcase) else :all end filters = pdu[1][6] if filters.length == 0 # NOTE, early exit. send_ldap_response 5, pdu[0].to_i, 53, "", "No filter specified" end # TODO, what if this returns nil? filter = Net::LDAP::Filter.parse_ldap_filter(filters) $ldif.each do |dn, entry| if filter.match(entry) attrs = [] entry.each do |k, v| if requested_attrs == :all || requested_attrs.include?(k.downcase) attrvals = v.map(&:to_ber).to_ber_set attrs << [k.to_ber, attrvals].to_ber_sequence end end appseq = [dn.to_ber, attrs.to_ber_sequence].to_ber_appsequence(4) pkt = [msgid.to_ber, appseq].to_ber_sequence send_data pkt end end send_ldap_response 5, pdu[0].to_i, 0, "", "Was that what you wanted?" end def send_ldap_response pkt_tag, msgid, code, dn, text send_data([msgid.to_ber, [code.to_ber, dn.to_ber, text.to_ber].to_ber_appsequence(pkt_tag)].to_ber) end end #------------------------------------------------ # Rather bogus, a global method, which reads a HARDCODED filename # parses out LDIF data. It will be used to serve LDAP queries out of this server. # def load_test_data ary = File.readlines("./testdata.ldif") hash = {} while (line = ary.shift) && line.chomp! if line =~ /^dn:[\s]*/i dn = $' hash[dn] = {} while (attr = ary.shift) && attr.chomp! && attr =~ /^([\w]+)[\s]*:[\s]*/ hash[dn][$1.downcase] ||= [] hash[dn][$1.downcase] << $' end end end hash end #------------------------------------------------ if __FILE__ == $0 require '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' LdapServerAsnSyntax = Net::BER.compile_syntax(LdapServerAsnSyntaxTemplate) EventMachine.run do $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 end ruby-net-ldap-0.17.1/testserver/testdata.ldif000066400000000000000000000061711424751475600211660ustar00rootroot00000000000000# $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