diff --git a/.rubocop.yml b/.rubocop.yml index 85ffa202..7bdfa631 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,3 +3,18 @@ inherit_from: .rubocop_todo.yml AllCops: Exclude: - 'pkg/**/*' + +Style/ExtraSpacing: + Enabled: false + +Lint/AssignmentInCondition: + Enabled: false + +Style/ParallelAssignment: + Enabled: false + +Style/TrailingCommaInLiteral: + EnforcedStyleForMultiline: comma + +Style/TrailingCommaInArguments: + EnforcedStyleForMultiline: comma diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5a5dcbc7..50c86e74 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,41 +1,74 @@ -# This configuration was generated by `rubocop --auto-gen-config` -# on 2014-12-19 15:32:44 +1100 using RuboCop version 0.28.0. +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2016-08-17 14:58:12 -0700 using RuboCop version 0.42.0. # 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: 12 -# Configuration parameters: AllowSafeAssignment. -Lint/AssignmentInCondition: - Enabled: false - # Offense count: 1 -# Configuration parameters: AlignWith, SupportedStyles. +# Cop supports --auto-correct. +# Configuration parameters: AlignWith, SupportedStyles, AutoCorrect. +# SupportedStyles: keyword, variable, start_of_line Lint/EndAlignment: - Enabled: false + 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: - Enabled: false + Exclude: + - 'lib/net/ldap/pdu.rb' # Offense count: 1 Lint/ShadowingOuterLocalVariable: - Enabled: false + Exclude: + - 'lib/net/ldap/instrumentation.rb' -# Offense count: 9 +# Offense count: 10 # Cop supports --auto-correct. +# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. Lint/UnusedBlockArgument: - Enabled: false + Exclude: + - 'lib/net/ldap.rb' + - 'lib/net/snmp.rb' + - 'test/support/vm/openldap/Vagrantfile' -# Offense count: 3 +# Offense count: 7 # Cop supports --auto-correct. +# Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods. Lint/UnusedMethodArgument: - Enabled: false + 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: 7 +# Offense count: 1 +# Configuration parameters: ContextCreatingMethods. +Lint/UselessAccessModifier: + Exclude: + - 'lib/net/ldap/connection.rb' + +# Offense count: 9 Lint/UselessAssignment: - Enabled: false + Exclude: + - 'lib/net/ldap/connection.rb' + - 'lib/net/ldap/password.rb' + - 'test/integration/test_add.rb' + - 'test/test_ldap_connection.rb' + - 'test/test_search.rb' + - 'test/test_snmp.rb' # Offense count: 47 Metrics/AbcSize: @@ -45,418 +78,676 @@ Metrics/AbcSize: Metrics/BlockNesting: Max: 4 -# Offense count: 9 +# Offense count: 10 # Configuration parameters: CountComments. Metrics/ClassLength: - Max: 470 + Max: 431 -# Offense count: 20 +# Offense count: 22 Metrics/CyclomaticComplexity: Max: 41 -# Offense count: 193 -# Configuration parameters: AllowURI, URISchemes. +# Offense count: 225 +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes. +# URISchemes: http, https Metrics/LineLength: Max: 360 -# Offense count: 71 +# Offense count: 70 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 130 -# Offense count: 13 +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/ModuleLength: + Max: 104 + +# Offense count: 14 Metrics/PerceivedComplexity: - Max: 36 + Max: 37 # Offense count: 1 Style/AccessorMethodName: - Enabled: false + 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: 4 # Cop supports --auto-correct. Style/AlignArray: - Enabled: false + Exclude: + - 'lib/net/ldap.rb' + - 'lib/net/ldap/auth_adapter/sasl.rb' + - 'lib/net/ldap/connection.rb' -# Offense count: 3 +# Offense count: 10 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: with_first_parameter, with_fixed_indentation Style/AlignParameters: - Enabled: false + Exclude: + - 'test/ber/test_ber.rb' + - 'test/integration/test_ber.rb' + - 'test/integration/test_bind.rb' + - 'test/integration/test_password_modify.rb' -# Offense count: 36 +# Offense count: 37 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: always, conditionals Style/AndOr: - Enabled: false + 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' + - 'testserver/ldapserver.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: percent_q, bare_percent Style/BarePercentLiterals: - Enabled: false + Exclude: + - 'test/test_entry.rb' # Offense count: 1 # Cop supports --auto-correct. Style/BlockComments: - Enabled: false + Exclude: + - 'test/test_rename.rb' -# Offense count: 20 -# Cop supports --auto-correct. -Style/Blocks: - Enabled: false - -# Offense count: 2 +# Offense count: 6 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: braces, no_braces, context_dependent Style/BracesAroundHashParameters: - Enabled: false + Exclude: + - 'lib/net/ldap/auth_adapter/gss_spnego.rb' + - 'lib/net/snmp.rb' + - 'test/test_ldap.rb' # Offense count: 4 -# Configuration parameters: IndentWhenRelativeTo, SupportedStyles, IndentOneStep. +# Cop supports --auto-correct. +# Configuration parameters: IndentWhenRelativeTo, SupportedStyles, IndentOneStep, IndentationWidth. +# SupportedStyles: case, end Style/CaseIndentation: - Enabled: false + Exclude: + - 'lib/net/ldap/filter.rb' # Offense count: 4 # Cop supports --auto-correct. Style/CharacterLiteral: - Enabled: false + Exclude: + - 'lib/net/ldap/dataset.rb' + - 'lib/net/ldap/entry.rb' -# Offense count: 22 +# 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: 1 +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: is_a?, kind_of? Style/ClassCheck: - Enabled: false + Exclude: + - 'lib/net/ber/core_ext/array.rb' + - 'lib/net/ldap/error.rb' # Offense count: 13 # Cop supports --auto-correct. Style/ColonMethodCall: - Enabled: false + Exclude: + - 'test/test_ldif.rb' + - 'test/test_ssl_ber.rb' -# Offense count: 2 +# Offense count: 1 +# Cop supports --auto-correct. # Configuration parameters: Keywords. +# Keywords: TODO, FIXME, OPTIMIZE, HACK, REVIEW Style/CommentAnnotation: - Enabled: false + Exclude: + - 'lib/net/ber.rb' -# Offense count: 86 -Style/ConstantName: - Enabled: false - -# Offense count: 18 +# Offense count: 1 # Cop supports --auto-correct. -Style/DeprecatedHashMethods: - Enabled: false +# Configuration parameters: EnforcedStyle, SupportedStyles, SingleLineConditionsOnly. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Exclude: + - 'lib/net/ldap/dn.rb' -# Offense count: 46 +# Offense count: 88 +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: - Enabled: false - -# Offense count: 23 + 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: 19 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: leading, trailing Style/DotPosition: - Enabled: false + Exclude: + - 'test/test_ldap_connection.rb' + - 'test/test_ssl_ber.rb' # Offense count: 1 # Cop supports --auto-correct. Style/ElseAlignment: - Enabled: false + Exclude: + - 'testserver/ldapserver.rb' -# Offense count: 4 +# Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: AllowAdjacentOneLineDefs. Style/EmptyLineBetweenDefs: - Enabled: false + Exclude: + - 'lib/net/ldap.rb' + - 'lib/net/ldap/dataset.rb' + - 'lib/net/snmp.rb' -# Offense count: 9 +# Offense count: 8 # Cop supports --auto-correct. Style/EmptyLines: - Enabled: false + Exclude: + - 'lib/net/snmp.rb' + - 'testserver/ldapserver.rb' -# Offense count: 1 +# Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: empty_lines, no_empty_lines Style/EmptyLinesAroundClassBody: - Enabled: false + Exclude: + - 'lib/net/ldap.rb' + - 'test/test_snmp.rb' # Offense count: 2 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: empty_lines, no_empty_lines Style/EmptyLinesAroundModuleBody: - Enabled: false + Exclude: + - 'testserver/ldapserver.rb' # Offense count: 3 +# Cop supports --auto-correct. Style/EvenOdd: - Enabled: false + Exclude: + - 'lib/net/ldap/dn.rb' # Offense count: 1 -# Configuration parameters: Exclude. +# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts. Style/FileName: - Enabled: false + Exclude: + - 'lib/net-ldap.rb' # Offense count: 9 # Configuration parameters: AllowedVariables. Style/GlobalVars: - Enabled: false + Exclude: + - 'testserver/ldapserver.rb' -# Offense count: 3 -# Configuration parameters: MinBodyLength. -Style/GuardClause: - Enabled: false - -# Offense count: 150 +# Offense count: 161 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. +# Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. +# SupportedStyles: ruby19, ruby19_no_mixed_keys, hash_rockets Style/HashSyntax: - Enabled: false + 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/integration/test_bind.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: 8 +# Offense count: 1 +Style/IfInsideElse: + Exclude: + - 'lib/net/ldap/instrumentation.rb' + +# Offense count: 7 +# Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: - Enabled: false + 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' + - 'test/test_ldap_connection.rb' # Offense count: 2 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. +# Configuration parameters: SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_brackets +Style/IndentArray: + EnforcedStyle: consistent + +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: SupportedStyles, IndentationWidth. +# SupportedStyles: special_inside_parentheses, consistent, align_braces Style/IndentHash: - Enabled: false + EnforcedStyle: consistent -# Offense count: 6 +# Offense count: 10 # Cop supports --auto-correct. # Configuration parameters: Width. Style/IndentationWidth: - Enabled: false + Exclude: + - 'lib/net/ber.rb' + - 'lib/net/ldap/password.rb' + - 'lib/net/snmp.rb' + - 'test/test_snmp.rb' + - 'testserver/ldapserver.rb' -# Offense count: 2 +# Offense count: 3 # Cop supports --auto-correct. Style/LeadingCommentSpace: - Enabled: false + Exclude: + - 'lib/net/ber/core_ext/array.rb' + - 'lib/net/ldap.rb' + - 'lib/net/ldap/connection.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: - Enabled: false + 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: 1 # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: snake_case, camelCase Style/MethodName: - Enabled: false + Exclude: + - 'lib/net/ldap/filter.rb' -# Offense count: 5 +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. -Style/MultilineOperationIndentation: - Enabled: false +# SupportedStyles: symmetrical, new_line, same_line +Style/MultilineMethodCallBraceLayout: + Exclude: + - 'lib/net/ldap/filter.rb' + - 'test/test_entry.rb' + - 'test/test_ldap_connection.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, IndentationWidth. +# SupportedStyles: aligned, indented, indented_relative_to_receiver +Style/MultilineMethodCallIndentation: + Exclude: + - 'test/test_ldap_connection.rb' # Offense count: 1 Style/MultilineTernaryOperator: - Enabled: false + Exclude: + - 'lib/net/ldap/connection.rb' + +# Offense count: 26 +# 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/support/vm/openldap/Vagrantfile' + - 'test/test_ldif.rb' + - 'testserver/ldapserver.rb' # Offense count: 1 # Cop supports --auto-correct. Style/NegatedIf: - Enabled: false + Exclude: + - 'test/test_helper.rb' # Offense count: 1 # Cop supports --auto-correct. Style/NegatedWhile: - Enabled: false + 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: - Enabled: false + Exclude: + - 'lib/net/ldap/connection.rb' + - 'testserver/ldapserver.rb' # Offense count: 1 # Cop supports --auto-correct. Style/NilComparison: - Enabled: false + Exclude: + - 'lib/net/ldap/connection.rb' # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: IncludeSemanticChanges. Style/NonNilCheck: - Enabled: false + Exclude: + - 'lib/net/ber/ber_parser.rb' # Offense count: 1 # Cop supports --auto-correct. Style/Not: - Enabled: false + Exclude: + - 'lib/net/ldap/filter.rb' -# Offense count: 10 +# Offense count: 11 # Cop supports --auto-correct. Style/NumericLiterals: MinDigits: 8 +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: predicate, comparison +Style/NumericPredicate: + Exclude: + - 'lib/net/ber/core_ext/integer.rb' + - 'lib/net/ldap/dn.rb' + - 'testserver/ldapserver.rb' + # Offense count: 3 Style/OpMethod: - Enabled: false + Exclude: + - 'lib/net/ldap/filter.rb' # Offense count: 6 # Cop supports --auto-correct. # Configuration parameters: AllowSafeAssignment. Style/ParenthesesAroundCondition: - Enabled: false + 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: 3 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: - Enabled: false + Exclude: + - 'net-ldap.gemspec' + - 'test/test_entry.rb' # Offense count: 11 # Cop supports --auto-correct. Style/PerlBackrefs: - Enabled: false + Exclude: + - 'lib/net/ldap/dataset.rb' + - 'lib/net/ldap/filter.rb' + - 'testserver/ldapserver.rb' -# Offense count: 9 +# Offense count: 10 +# Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: compact, exploded Style/RaiseArgs: - Enabled: false + Exclude: + - 'lib/net/ldap/connection.rb' + - 'lib/net/ldap/pdu.rb' + - 'lib/net/snmp.rb' # Offense count: 1 # Cop supports --auto-correct. Style/RedundantBegin: - Enabled: false + Exclude: + - 'lib/net/snmp.rb' -# Offense count: 3 +# Offense count: 4 +# Cop supports --auto-correct. +Style/RedundantParentheses: + Exclude: + - 'lib/net/ldap/filter.rb' + - 'test/test_filter.rb' + +# Offense count: 4 # Cop supports --auto-correct. # Configuration parameters: AllowMultipleReturnValues. Style/RedundantReturn: - Enabled: false + Exclude: + - 'lib/net/ber/core_ext/string.rb' + - 'lib/net/ldap/auth_adapter.rb' + - 'lib/net/ldap/entry.rb' + - 'lib/net/ldap/password.rb' -# Offense count: 7 +# Offense count: 8 # Cop supports --auto-correct. Style/RedundantSelf: - Enabled: false + 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: 1 -# Configuration parameters: MaxSlashes. +# Offense count: 2 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes. +# SupportedStyles: slashes, percent_r, mixed Style/RegexpLiteral: - Enabled: false + Exclude: + - 'lib/net/ldap/filter.rb' + - 'net-ldap.gemspec' -# Offense count: 2 +# Offense count: 1 +# Cop supports --auto-correct. Style/RescueModifier: - Enabled: false + Exclude: + - 'test/ber/core_ext/test_string.rb' -# Offense count: 7 +# Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: AllowAsExpressionSeparator. Style/Semicolon: - Enabled: false - -# Offense count: 61 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/SignalException: - Enabled: false + Exclude: + - 'lib/net/ldap/dn.rb' + - 'lib/net/ldap/error.rb' + - 'testserver/ldapserver.rb' # Offense count: 2 # Configuration parameters: Methods. +# Methods: {"reduce"=>["a", "e"]}, {"inject"=>["a", "e"]} Style/SingleLineBlockParams: - Enabled: false - -# Offense count: 2 -# Cop supports --auto-correct. -Style/SingleSpaceBeforeFirstArg: - Enabled: false + Exclude: + - 'lib/net/ldap/filter.rb' -# Offense count: 24 -# Cop supports --auto-correct. -Style/SpaceAfterComma: - Enabled: false - -# Offense count: 2 +# Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space Style/SpaceAroundEqualsInParameterDefault: - Enabled: false + Exclude: + - 'lib/net/ldap/connection.rb' + - 'lib/net/snmp.rb' -# Offense count: 8 +# Offense count: 4 # Cop supports --auto-correct. -Style/SpaceAroundOperators: - Enabled: false +Style/SpaceAroundKeyword: + Exclude: + - 'lib/net/ldap/entry.rb' + - 'lib/net/snmp.rb' -# Offense count: 2 +# Offense count: 9 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/SpaceBeforeBlockBraces: - Enabled: false +# Configuration parameters: AllowForAlignment. +Style/SpaceAroundOperators: + Exclude: + - 'lib/net/ber/ber_parser.rb' + - 'lib/net/ldap/connection.rb' + - 'lib/net/ldap/entry.rb' + - 'lib/net/ldap/filter.rb' + - 'test/test_entry.rb' + - 'test/test_ldap_connection.rb' -# Offense count: 18 +# Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. +# SupportedStyles: space, no_space Style/SpaceInsideBlockBraces: - Enabled: false - -# Offense count: 37 -# Cop supports --auto-correct. -Style/SpaceInsideBrackets: - Enabled: false + Exclude: + - 'lib/net/ldap/dataset.rb' + - 'test/test_snmp.rb' + - 'testserver/ldapserver.rb' -# Offense count: 1 +# Offense count: 13 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles. +# SupportedStyles: space, no_space, compact Style/SpaceInsideHashLiteralBraces: - Enabled: false + Exclude: + - 'lib/net/ldap/dataset.rb' + - 'test/test_ldap.rb' # Offense count: 20 # Cop supports --auto-correct. Style/SpaceInsideParens: - Enabled: false + Exclude: + - 'lib/net/ldap/entry.rb' + - 'lib/net/snmp.rb' + - 'test/test_password.rb' + - 'testserver/ldapserver.rb' # Offense count: 5 # Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: use_perl_names, use_english_names Style/SpecialGlobalVars: - Enabled: false + Exclude: + - 'lib/net/snmp.rb' + - 'net-ldap.gemspec' + - 'testserver/ldapserver.rb' -# Offense count: 645 +# Offense count: 679 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. +# Configuration parameters: EnforcedStyle, SupportedStyles, ConsistentQuotesInMultiline. +# SupportedStyles: single_quotes, double_quotes Style/StringLiterals: Enabled: false -# Offense count: 10 -# Cop supports --auto-correct. -# Configuration parameters: IgnoredMethods. -Style/SymbolProc: - Enabled: false - # Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -Style/TrailingBlankLines: - Enabled: false +Style/StructInheritance: + Exclude: + - 'test/test_ldap.rb' -# Offense count: 9 +# Offense count: 4 # Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline, SupportedStyles. -Style/TrailingComma: - Enabled: false +# Configuration parameters: EnforcedStyle, SupportedStyles, AllowSafeAssignment. +# SupportedStyles: require_parentheses, require_no_parentheses +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, Whitelist. +# 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: - Enabled: false + Exclude: + - 'lib/net/ldap/connection.rb' # Offense count: 5 # Cop supports --auto-correct. Style/UnneededPercentQ: - Enabled: false + Exclude: + - 'net-ldap.gemspec' + - 'test/test_entry.rb' # Offense count: 1 +# Cop supports --auto-correct. # Configuration parameters: MaxLineLength. Style/WhileUntilModifier: - Enabled: false + Exclude: + - 'lib/net/ldap/filter.rb' # Offense count: 1 # Cop supports --auto-correct. -# Configuration parameters: WordRegex. +# Configuration parameters: SupportedStyles, WordRegex. +# SupportedStyles: percent, brackets Style/WordArray: - MinSize: 2 + EnforcedStyle: percent + MinSize: 3 + +# Offense count: 6 +# Cop supports --auto-correct. +Style/ZeroLengthPredicate: + Exclude: + - 'lib/net/ldap/connection.rb' + - 'lib/net/ldap/filter.rb' + - 'testserver/ldapserver.rb' diff --git a/.travis.yml b/.travis.yml index 4131d6e4..fc764963 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: ruby rvm: - - 1.9.3 - 2.0.0 - 2.1 - 2.2 @@ -13,6 +12,9 @@ rvm: env: - INTEGRATION=openldap +before_install: + - gem update bundler + install: - if [ "$INTEGRATION" = "openldap" ]; then sudo script/install-openldap; fi - bundle install diff --git a/Contributors.rdoc b/Contributors.rdoc index e40b20db..137394f8 100644 --- a/Contributors.rdoc +++ b/Contributors.rdoc @@ -22,3 +22,4 @@ Contributions since: * David J. Lee (DavidJLee) * Cody Cutrer (ccutrer) * WoodsBagotAndreMarquesLee +* Rufus Post (mynameisrufus) diff --git a/History.rdoc b/History.rdoc index fa7ff5a1..3fcc291b 100644 --- a/History.rdoc +++ b/History.rdoc @@ -1,3 +1,67 @@ +=== 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 diff --git a/README.rdoc b/README.rdoc index 89b2d7d7..f1b1ea36 100644 --- a/README.rdoc +++ b/README.rdoc @@ -25,7 +25,7 @@ See Net::LDAP for documentation and usage samples. == Requirements -Net::LDAP requires a Ruby 1.9.3 compatible interpreter or better. +Net::LDAP requires a Ruby 2.0.0 compatible interpreter or better. == Install @@ -37,6 +37,14 @@ sources. 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 @@ -44,21 +52,20 @@ This task will run the test suite and the rake rubotest -To run the integration tests against an LDAP server: - - cd test/support/vm/openldap - vagrant up - cd ../../../.. - INTEGRATION=openldap bundle exec rake rubotest +CI takes too long? If your local box supports +{Vagrant}[https://www.vagrantup.com/], you can run most of the tests +in a VM on your local box. For more details and setup instructions, see +{test/support/vm/openldap/README.md}[https://github.com/ruby-ldap/ruby-net-ldap/tree/master/test/support/vm/openldap/README.md] == 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 `git log --oneline vLAST_RELEASE..HEAD | grep Merge` - -* On the master branch, run `script/release` +* 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 diff --git a/lib/net/ber.rb b/lib/net/ber.rb index b8992a92..eb6f04b3 100644 --- a/lib/net/ber.rb +++ b/lib/net/ber.rb @@ -106,6 +106,7 @@ module Net # :nodoc: # CHARACTER STRINGC29: 61 (0x3d, 0b00111101) # BMPStringP30: 30 (0x1e, 0b00011110) # BMPStringC30: 62 (0x3e, 0b00111110) + # ExtendedResponseC107: 139 (0x8b, 0b010001011) # module BER VERSION = Net::LDAP::VERSION @@ -234,7 +235,7 @@ 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 + out = [nil] * 256 syntax.each do |tag_class_id, encodings| tag_class = TAG_CLASS[tag_class_id] encodings.each do |encoding_id, classes| @@ -269,7 +270,7 @@ class Net::BER::BerIdentifiedOid def initialize(oid) if oid.is_a?(String) - oid = oid.split(/\./).map {|s| s.to_i } + oid = oid.split(/\./).map(&:to_i) end @value = oid end @@ -293,12 +294,43 @@ def to_arr ## # 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 args - # LDAP uses UTF-8 encoded strings - self.encode('UTF-8') if self.respond_to?(:encoding) rescue self + 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 diff --git a/lib/net/ber/ber_parser.rb b/lib/net/ber/ber_parser.rb index 09de8c82..39d3737e 100644 --- a/lib/net/ber/ber_parser.rb +++ b/lib/net/ber/ber_parser.rb @@ -14,7 +14,7 @@ module Net::BER::BERParser } constructed = { 16 => :array, - 17 => :array + 17 => :array, } universal = { :primitive => primitive, :constructed => constructed } @@ -172,10 +172,10 @@ def read_ber(syntax = nil) yield id, content_length if block_given? if -1 == content_length - raise Net::BER::BerError, "Indeterminite BER content length not implemented." - else - data = read(content_length) + raise Net::BER::BerError, + "Indeterminite BER content length not implemented." end + data = read(content_length) parse_ber_object(syntax, id, data) end diff --git a/lib/net/ber/core_ext/array.rb b/lib/net/ber/core_ext/array.rb index 250fa243..9deb4a1e 100644 --- a/lib/net/ber/core_ext/array.rb +++ b/lib/net/ber/core_ext/array.rb @@ -89,7 +89,7 @@ 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{|element| element.to_ber}.to_ber_sequence.reject_empty_ber_arrays + control_sequence.collect(&:to_ber).to_ber_sequence.reject_empty_ber_arrays end ary.to_ber_sequence.reject_empty_ber_arrays end diff --git a/lib/net/ber/core_ext/integer.rb b/lib/net/ber/core_ext/integer.rb index b2149f9b..78313045 100644 --- a/lib/net/ber/core_ext/integer.rb +++ b/lib/net/ber/core_ext/integer.rb @@ -20,7 +20,7 @@ def to_ber_length_encoding if self <= 127 [self].pack('C') else - i = [self].pack('N').sub(/^[\0]+/,"") + i = [self].pack('N').sub(/^[\0]+/, "") [0x80 + i.length].pack('C') + i end end diff --git a/lib/net/ber/core_ext/string.rb b/lib/net/ber/core_ext/string.rb index e8a43e2c..995d26d4 100644 --- a/lib/net/ber/core_ext/string.rb +++ b/lib/net/ber/core_ext/string.rb @@ -75,6 +75,6 @@ def read_ber!(syntax = nil) end def reject_empty_ber_arrays - self.gsub(/0\000/n,'') + self.gsub(/0\000/n, '') end end diff --git a/lib/net/ldap.rb b/lib/net/ldap.rb index 75b463fb..f7a98ef5 100644 --- a/lib/net/ldap.rb +++ b/lib/net/ldap.rb @@ -27,6 +27,12 @@ class LDAP require 'net/ldap/connection' require 'net/ldap/version' require 'net/ldap/error' +require 'net/ldap/auth_adapter' +require 'net/ldap/auth_adapter/simple' +require 'net/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: @@ -73,6 +79,14 @@ class LDAP # # 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 # @@ -250,14 +264,14 @@ class Net::LDAP SearchScope_BaseObject = 0 SearchScope_SingleLevel = 1 SearchScope_WholeSubtree = 2 - SearchScopes = [ SearchScope_BaseObject, SearchScope_SingleLevel, - SearchScope_WholeSubtree ] + 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 ] + DerefAliasesArray = [DerefAliases_Never, DerefAliases_Search, DerefAliases_Find, DerefAliases_Always] primitive = { 2 => :null } # UnbindRequest body constructed = { @@ -309,7 +323,14 @@ class Net::LDAP :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" @@ -318,7 +339,8 @@ class Net::LDAP DefaultTreebase = "dc=com" DefaultForceNoPage = false - StartTlsOid = "1.3.6.1.4.1.1466.20037" + 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 @@ -367,14 +389,14 @@ class Net::LDAP ResultCodeCompareFalse, ResultCodeCompareTrue, ResultCodeReferral, - ResultCodeSaslBindInProgress + ResultCodeSaslBindInProgress, ] # nonstandard list of "successful" result codes for searches ResultCodesSearchSuccess = [ ResultCodeSuccess, ResultCodeTimeLimitExceeded, - ResultCodeSizeLimitExceeded + ResultCodeSizeLimitExceeded, ] # map of result code to human message @@ -416,7 +438,7 @@ class Net::LDAP ResultCodeEntryAlreadyExists => "Entry Already Exists", ResultCodeObjectClassModsProhibited => "ObjectClass Modifications Prohibited", ResultCodeAffectsMultipleDSAs => "Affects Multiple DSAs", - ResultCodeOther => "Other" + ResultCodeOther => "Other", } module LDAPControls @@ -432,6 +454,7 @@ def self.result2string(code) #:nodoc: attr_accessor :host attr_accessor :port + attr_accessor :hosts attr_accessor :base # Instantiate an object of type Net::LDAP to perform directory operations. @@ -440,6 +463,8 @@ def self.result2string(code) #:nodoc: # 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 } @@ -451,28 +476,83 @@ def self.result2string(code) #:nodoc: # 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. - # * :encryption => specifies the encryption to be used in communicating - # with the LDAP server. The value is either a Hash containing additional - # parameters, or the Symbol :simple_tls, which is equivalent to - # specifying the Hash {:method => :simple_tls}. There is a fairly large - # range of potential values that may be given for this parameter. See - # #encryption for details. # * :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. + # 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 args[:encryption] # may be nil + @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 @@ -523,7 +603,7 @@ def authenticate(username, password) @auth = { :method => :simple, :username => username, - :password => password + :password => password, } end alias_method :auth, :authenticate @@ -536,54 +616,12 @@ def authenticate(username, password) # additional capabilities are added, more configuration values will be # added here. # - # 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. - # It's intended for cases where you have an implicit level of trust in the - # authenticity of the LDAP server. No validation of the LDAP server's SSL - # certificate is performed. This means that :simple_tls will not produce - # errors if the LDAP server's encryption certificate is not signed by a - # well-known Certification Authority. 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. - # - # In order to verify certificates and enable other TLS options, the - # :tls_options hash can be passed alongside :simple_tls or :start_tls. - # This hash contains any options that can be passed to - # OpenSSL::SSL::SSLContext#set_params(). The most common options passed - # should be OpenSSL::SSL::SSLContext::DEFAULT_PARAMS, or the :ca_file option, - # which contains a path to a Certificate Authority file (PEM-encoded). - # - # Example for a default setup without custom settings: - # { - # :method => :simple_tls, - # :tls_options => OpenSSL::SSL::SSLContext::DEFAULT_PARAMS - # } - # - # Example for specifying a CA-File and only allowing TLSv1.1 connections: + # This method is deprecated. # - # { - # :method => :start_tls, - # :tls_options => { :ca_file => "/etc/cafile.pem", :ssl_version => "TLSv1_1" } - # } def encryption(args) - case args - when :simple_tls, :start_tls - args = { :method => args, :tls_options => {} } - end - @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 @@ -627,8 +665,11 @@ def self.open(args) #++ def get_operation_result result = @result - result = result.result if result.is_a?(Net::LDAP::PDU) 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. @@ -740,10 +781,10 @@ def search(args = {}) instrument "search.net_ldap", args do |payload| @result = use_connection(args) do |conn| - conn.search(args) { |entry| + conn.search(args) do |entry| result_set << entry if result_set yield entry if block_given? - } + end end if return_result_set @@ -882,7 +923,7 @@ def bind(auth = @auth) # end def bind_as(args = {}) result = false - open { |me| + open do |me| rs = search args if rs and rs.first and dn = rs.first.dn password = args[:password] @@ -890,7 +931,7 @@ def bind_as(args = {}) result = rs if bind(:method => :simple, :username => dn, :password => password) end - } + end result end @@ -1017,6 +1058,44 @@ def modify(args) 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 @@ -1135,7 +1214,7 @@ def search_root_dse :supportedExtension, :supportedFeatures, :supportedLdapVersion, - :supportedSASLMechanisms + :supportedSASLMechanisms, ]) (rs and rs.first) or Net::LDAP::Entry.new end @@ -1195,6 +1274,18 @@ def paged_searches_supported? @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 @@ -1207,11 +1298,9 @@ def use_connection(args) else begin conn = new_connection - if (result = conn.bind(args[:auth] || @auth)).result_code == Net::LDAP::ResultCodeSuccess - yield conn - else - return result - end + result = conn.bind(args[:auth] || @auth) + return result unless result.result_code == Net::LDAP::ResultCodeSuccess + yield conn ensure conn.close if conn end @@ -1220,10 +1309,35 @@ def use_connection(args) # Establish a new connection to the LDAP server def new_connection - Net::LDAP::Connection.new \ + connection = Net::LDAP::Connection.new \ :host => @host, :port => @port, + :hosts => @hosts, :encryption => @encryption, - :instrumentation_service => @instrumentation_service + :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, Net::LDAP::ConnectionRefusedError => 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 + end # class LDAP diff --git a/lib/net/ldap/auth_adapter.rb b/lib/net/ldap/auth_adapter.rb new file mode 100644 index 00000000..f74232d1 --- /dev/null +++ b/lib/net/ldap/auth_adapter.rb @@ -0,0 +1,29 @@ +module 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 diff --git a/lib/net/ldap/auth_adapter/gss_spnego.rb b/lib/net/ldap/auth_adapter/gss_spnego.rb new file mode 100644 index 00000000..9f773454 --- /dev/null +++ b/lib/net/ldap/auth_adapter/gss_spnego.rb @@ -0,0 +1,41 @@ +require 'net/ldap/auth_adapter' +require 'net/ldap/auth_adapter/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 diff --git a/lib/net/ldap/auth_adapter/sasl.rb b/lib/net/ldap/auth_adapter/sasl.rb new file mode 100644 index 00000000..139e8593 --- /dev/null +++ b/lib/net/ldap/auth_adapter/sasl.rb @@ -0,0 +1,62 @@ +require 'net/ldap/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 diff --git a/lib/net/ldap/auth_adapter/simple.rb b/lib/net/ldap/auth_adapter/simple.rb new file mode 100644 index 00000000..d01b57ae --- /dev/null +++ b/lib/net/ldap/auth_adapter/simple.rb @@ -0,0 +1,34 @@ +require 'net/ldap/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 diff --git a/lib/net/ldap/connection.rb b/lib/net/ldap/connection.rb index b51bcc10..b01984f4 100644 --- a/lib/net/ldap/connection.rb +++ b/lib/net/ldap/connection.rb @@ -3,29 +3,73 @@ class Net::LDAP::Connection #:nodoc: include Net::LDAP::Instrumentation + # Seconds before failing for socket connect timeout + DefaultConnectTimeout = 5 + LdapVersion = 3 - MaxSaslChallenges = 10 - def initialize(server) + # 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] - begin - @conn = server[:socket] || TCPSocket.new(server[:host], server[:port]) - rescue SocketError - raise Net::LDAP::Error, "No such address or other socket error." - rescue Errno::ECONNREFUSED - raise Net::LDAP::Error, "Server #{server[:host]} refused connection on port #{server[:port]}." - rescue Errno::EHOSTUNREACH => error - raise Net::LDAP::Error, "Host #{server[:host]} was unreachable (#{error.message})" - rescue Errno::ETIMEDOUT - raise Net::LDAP::Error, "Connection to #{server[:host]} timed out." - end + # Allows tests to parameterize what socket class to use + @socket_class = server.fetch(:socket_class, DefaultSocket) + + yield self if block_given? + end - if server[:encryption] - setup_encryption server[:encryption] + 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 - yield self if block_given? + raise Net::LDAP::ConnectionError.new(errors) end module GetbyteForSSLSocket @@ -41,7 +85,7 @@ def close end end - def self.wrap_with_ssl(io, tls_options = {}) + 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 @@ -51,7 +95,22 @@ def self.wrap_with_ssl(io, tls_options = {}) ctx.set_params(tls_options) unless tls_options.empty? conn = OpenSSL::SSL::SSLSocket.new(io, ctx) - conn.connect + + 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 @@ -63,18 +122,18 @@ def self.wrap_with_ssl(io, tls_options = {}) end #-- - # Helper method called only from new, 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. + # 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 @@ -88,17 +147,17 @@ def self.wrap_with_ssl(io, tls_options = {}) # communications, as with simple_tls. Thanks for Kouhei Sutou for # generously contributing the :start_tls path. #++ - def setup_encryption(args) + 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]) + @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) + Net::LDAP::StartTlsOid.to_ber_contextspecific(0), ].to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest) write(request, nil, message_id) @@ -108,11 +167,9 @@ def setup_encryption(args) raise Net::LDAP::NoStartTLSResultError, "no start_tls result" end - if pdu.result_code.zero? - @conn = self.class.wrap_with_ssl(@conn, args[:tls_options]) - else - raise Net::LDAP::StartTlSError, "start_tls failed: #{pdu.result_code}" - 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 @@ -124,6 +181,7 @@ def setup_encryption(args) # have to call it, but perhaps it will come in handy someday. #++ def close + return if @conn.nil? @conn.close @conn = nil end @@ -141,12 +199,10 @@ def queued_read(message_id) # read messages until we have a match for the given message_id while pdu = read - if pdu.message_id == message_id - return pdu - else - message_queue[pdu.message_id].push pdu - next - end + return pdu if pdu.message_id == message_id + + message_queue[pdu.message_id].push pdu + next end pdu @@ -175,7 +231,7 @@ def message_queue def read(syntax = Net::LDAP::AsnSyntax) ber_object = instrument "read.net_ldap_connection", :syntax => syntax do |payload| - @conn.read_ber(syntax) do |id, content_length| + socket.read_ber(syntax) do |id, content_length| payload[:object_type_id] = id payload[:content_length] = content_length end @@ -205,7 +261,7 @@ def read(syntax = Net::LDAP::AsnSyntax) 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] = @conn.write(packet) + payload[:content_length] = socket.write(packet) end end private :write @@ -218,130 +274,11 @@ def next_msgid def bind(auth) instrument "bind.net_ldap_connection" do |payload| payload[:method] = meth = auth[:method] - if [:simple, :anonymous, :anon].include?(meth) - bind_simple auth - elsif meth == :sasl - bind_sasl(auth) - elsif meth == :gss_spnego - bind_gss_spnego(auth) - else - raise Net::LDAP::AuthMethodUnsupportedError, "Unsupported auth method (#{meth})" - end + adapter = Net::LDAP::AuthAdapter[meth] + adapter.new(self).bind(auth) end end - #-- - # Implements a simple user/psw authentication. Accessed by calling #bind - # with a method of :simple or :anonymous. - #++ - def bind_simple(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 = next_msgid - request = [ - LdapVersion.to_ber, user.to_ber, - psw.to_ber_contextspecific(0) - ].to_ber_appsequence(Net::LDAP::PDU::BindRequest) - - write(request, nil, message_id) - pdu = queued_read(message_id) - - if !pdu || pdu.app_tag != Net::LDAP::PDU::BindResult - raise Net::LDAP::NoBindResultError, "no bind result" - end - - pdu - end - - #-- - # 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_sasl(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 = next_msgid - - n = 0 - loop { - sasl = [mech.to_ber, cred.to_ber].to_ber_contextspecific(3) - request = [ - LdapVersion.to_ber, "".to_ber, sasl - ].to_ber_appsequence(Net::LDAP::PDU::BindRequest) - - write(request, nil, message_id) - pdu = 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) > MaxSaslChallenges) - - cred = chall.call(pdu.result_server_sasl_creds) - } - - raise Net::LDAP::SASLChallengeOverflowError, "why are we here?" - end - private :bind_sasl - - #-- - # 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. - #++ - def bind_gss_spnego(auth) - require 'ntlm' - - user, psw = [auth[:username] || auth[:dn], auth[:password]] - raise Net::LDAP::BindingInformationInvalidError, "Invalid binding information" unless (user && psw) - - nego = proc { |challenge| - t2_msg = NTLM::Message.parse(challenge) - t3_msg = t2_msg.response({ :user => user, :password => psw }, - { :ntlmv2 => true }) - t3_msg.serialize - } - - bind_sasl(:method => :sasl, :mechanism => "GSS-SPNEGO", - :initial_credential => NTLM::Message::Type1.new.serialize, - :challenge_response => nego) - end - private :bind_gss_spnego - - #-- # Allow the caller to specify a sort control # @@ -366,7 +303,7 @@ def encode_sort_controls(sort_definitions) sort_control = [ Net::LDAP::LDAPControls::SORT_REQUEST.to_ber, false.to_ber, - sort_control_values.to_ber_sequence.to_s.to_ber + sort_control_values.to_ber_sequence.to_s.to_ber, ].to_ber_sequence end @@ -463,12 +400,11 @@ def search(args = nil) # should collect this into a private helper to clarify the structure query_limit = 0 if size > 0 - if paged - query_limit = (((size - n_results) < 126) ? (size - - n_results) : 0) - else - query_limit = size - end + query_limit = if paged + (((size - n_results) < 126) ? (size - n_results) : 0) + else + size + end end request = [ @@ -479,7 +415,7 @@ def search(args = nil) time.to_ber, attrs_only.to_ber, filter.to_ber, - ber_attrs.to_ber_sequence + ber_attrs.to_ber_sequence, ].to_ber_appsequence(Net::LDAP::PDU::SearchRequest) # rfc2696_cookie sometimes contains binary data from Microsoft Active Directory @@ -492,7 +428,7 @@ def search(args = nil) Net::LDAP::LDAPControls::PAGED_RESULTS.to_ber, # Criticality MUST be false to interoperate with normal LDAPs. false.to_ber, - rfc2696_cookie.map{ |v| v.to_ber}.to_ber_sequence.to_s.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) @@ -531,6 +467,10 @@ def search(args = nil) 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 @@ -586,20 +526,20 @@ def search(args = nil) MODIFY_OPERATIONS = { #:nodoc: :add => 0, :delete => 1, - :replace => 2 + :replace => 2, } def self.modify_ops(operations) ops = [] if operations - operations.each { |op, attrib, values| + 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 - } + 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 @@ -618,7 +558,7 @@ def modify(args) message_id = next_msgid request = [ modify_dn.to_ber, - ops.to_ber_sequence + ops.to_ber_sequence, ].to_ber_appsequence(Net::LDAP::PDU::ModifyRequest) write(request, nil, message_id) @@ -631,6 +571,51 @@ def modify(args) 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::ResponseMissingError, "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 @@ -641,9 +626,9 @@ def modify(args) 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 { |k, v| - add_attrs << [ k.to_s.to_ber, Array(v).map { |m| m.to_ber}.to_ber_set ].to_ber_sequence - } + 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) @@ -699,4 +684,33 @@ def delete(args) 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 + + # 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 diff --git a/lib/net/ldap/dataset.rb b/lib/net/ldap/dataset.rb index 54fc1a07..9027ed28 100644 --- a/lib/net/ldap/dataset.rb +++ b/lib/net/ldap/dataset.rb @@ -29,7 +29,7 @@ def to_ldif keys.sort.each do |dn| ary << "dn: #{dn}" - attributes = self[dn].keys.map { |attr| attr.to_s }.sort + 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) @@ -141,7 +141,7 @@ def read_ldif(io) # $' 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] = [] } + ds[dn] = Hash.new { |k, v| k[v] = [] } yield :dn, dn if block_given? elsif line.empty? dn = nil diff --git a/lib/net/ldap/dn.rb b/lib/net/ldap/dn.rb index 3037eefd..e314b80e 100644 --- a/lib/net/ldap/dn.rb +++ b/lib/net/ldap/dn.rb @@ -169,11 +169,10 @@ def each_pair end # Last pair - if [:value, :value_normal, :value_hexstring, :value_end].include? state - yield key.string.strip, value.string.rstrip - else - raise "DN badly formed" - end + raise "DN badly formed" unless + [:value, :value_normal, :value_hexstring, :value_end].include? state + + yield key.string.strip, value.string.rstrip end ## diff --git a/lib/net/ldap/entry.rb b/lib/net/ldap/entry.rb index c2615268..10965c7c 100644 --- a/lib/net/ldap/entry.rb +++ b/lib/net/ldap/entry.rb @@ -140,11 +140,10 @@ def attribute_names # arguments to the block: a Symbol giving the name of the attribute, and a # (possibly empty) \Array of data values. def each # :yields: attribute-name, data-values-array - if block_given? - attribute_names.each {|a| - attr_name,values = a,self[a] - yield attr_name, values - } + 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 diff --git a/lib/net/ldap/error.rb b/lib/net/ldap/error.rb index c9a25f90..50442d06 100644 --- a/lib/net/ldap/error.rb +++ b/lib/net/ldap/error.rb @@ -9,7 +9,42 @@ class Error < StandardError; end class AlreadyOpenedError < Error; end class SocketError < Error; end - class ConnectionRefusedError < Error; end + class ConnectionRefusedError < Error; + def initialize(*args) + warn_deprecation_message + super + end + + def message + warn_deprecation_message + super + end + + private + + def warn_deprecation_message + warn "Deprecation warning: Net::LDAP::ConnectionRefused will be deprecated. Use Errno::ECONNREFUSED instead." + end + end + class ConnectionError < Error + def self.new(errors) + error = errors.first.first + if errors.size == 1 + if error.kind_of? Errno::ECONNREFUSED + return Net::LDAP::ConnectionRefusedError.new(error.message) + end + + 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 diff --git a/lib/net/ldap/filter.rb b/lib/net/ldap/filter.rb index 0ab847b8..6f064488 100644 --- a/lib/net/ldap/filter.rb +++ b/lib/net/ldap/filter.rb @@ -23,7 +23,7 @@ class Net::LDAP::Filter ## # Known filter types. - FilterTypes = [ :ne, :eq, :ge, :le, :and, :or, :not, :ex, :bineq ] + FilterTypes = [:ne, :eq, :ge, :le, :and, :or, :not, :ex, :bineq] def initialize(op, left, right) #:nodoc: unless FilterTypes.include?(op) @@ -287,7 +287,7 @@ def parse_ber(ber) when 0xa4 # context-specific constructed 4, "substring" str = "" final = false - ber.last.each { |b| + 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 @@ -298,7 +298,7 @@ def parse_ber(ber) str += "*#{escape(b)}" final = true end - } + end str += "*" unless final eq(ber.first.to_s, str) when 0xa5 # context-specific constructed 5, "greaterOrEqual" @@ -550,10 +550,10 @@ def to_ber [self.class.eq(@left, @right).to_ber].to_ber_contextspecific(2) when :and ary = [@left.coalesce(:and), @right.coalesce(:and)].flatten - ary.map {|a| a.to_ber}.to_ber_contextspecific(0) + ary.map(&:to_ber).to_ber_contextspecific(0) when :or ary = [@left.coalesce(:or), @right.coalesce(:or)].flatten - ary.map {|a| a.to_ber}.to_ber_contextspecific(1) + ary.map(&:to_ber).to_ber_contextspecific(1) when :not [@left.to_ber].to_ber_contextspecific(2) end @@ -645,8 +645,15 @@ def match(entry) ## # 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.to_s.gsub(/\\([a-fA-F\d]{2})/) { [$1.hex].pack("U") } + 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 @@ -752,7 +759,7 @@ def parse_filter_branch(scanner) 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) + 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. diff --git a/lib/net/ldap/pdu.rb b/lib/net/ldap/pdu.rb index f749f669..382c7acb 100644 --- a/lib/net/ldap/pdu.rb +++ b/lib/net/ldap/pdu.rb @@ -74,6 +74,7 @@ class Error < RuntimeError; end attr_reader :search_referrals attr_reader :search_parameters attr_reader :bind_parameters + attr_reader :extended_response ## # Returns RFC-2251 Controls if any. @@ -120,7 +121,7 @@ def initialize(ber_object) when UnbindRequest parse_unbind_request(ber_object[1]) when ExtendedResponse - parse_ldap_result(ber_object[1]) + parse_extended_response(ber_object[1]) else raise LdapPduError.new("unknown pdu-type: #{@app_tag}") end @@ -174,12 +175,35 @@ def parse_ldap_result(sequence) @ldap_result = { :resultCode => sequence[0], :matchedDN => sequence[1], - :errorMessage => sequence[2] + :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. diff --git a/lib/net/ldap/version.rb b/lib/net/ldap/version.rb index 98d557cf..0a57d621 100644 --- a/lib/net/ldap/version.rb +++ b/lib/net/ldap/version.rb @@ -1,5 +1,5 @@ module Net class LDAP - VERSION = "0.11" + VERSION = "0.16.1" end end diff --git a/lib/net/snmp.rb b/lib/net/snmp.rb index 501df851..258e8060 100644 --- a/lib/net/snmp.rb +++ b/lib/net/snmp.rb @@ -12,7 +12,7 @@ class SNMP 2 => :integer, # Gauge32 or Unsigned32, (RFC2578 sec 2) 3 => :integer # TimeTicks32, (RFC2578 sec 2) }, - :constructed => {} + :constructed => {}, }, :context_specific => { :primitive => {}, @@ -20,8 +20,8 @@ class SNMP 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. @@ -70,7 +70,7 @@ class Error < StandardError; end :get_next_request, :get_response, :set_request, - :trap + :trap, ] ErrorStatusCodes = { # Per RFC1157, pgh 4.1.1 0 => "noError", @@ -78,7 +78,7 @@ class Error < StandardError; end 2 => "noSuchName", 3 => "badValue", 4 => "readOnly", - 5 => "genErr" + 5 => "genErr", } class << self @@ -148,7 +148,7 @@ def parse_get_request data # data[2] is error_index, always zero. send :error_status=, 0 send :error_index=, 0 - data[3].each do |n,v| + 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. @@ -166,7 +166,7 @@ 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| + 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. @@ -177,7 +177,7 @@ def parse_get_response data def version= ver - unless [0,2].include?(ver) + unless [0, 2].include?(ver) raise Error.new("unknown snmp-version: #{ver}") end @version = ver @@ -191,7 +191,7 @@ def pdu_type= t end def error_status= es - unless ErrorStatusCodes.has_key?(es) + unless ErrorStatusCodes.key?(es) raise Error.new("unknown error-status: #{es}") end @error_status = es @@ -227,10 +227,10 @@ def pdu_to_ber_string error_status.to_ber, error_index.to_ber, [ - @variables.map {|n,v| + @variables.map do|n, v| [n.to_ber_oid, Net::BER::BerIdentifiedNull.new.to_ber].to_ber_sequence - } - ].to_ber_sequence + end, + ].to_ber_sequence, ].to_ber_contextspecific(0) when :get_next_request [ @@ -238,10 +238,10 @@ def pdu_to_ber_string error_status.to_ber, error_index.to_ber, [ - @variables.map {|n,v| + @variables.map do|n, v| [n.to_ber_oid, Net::BER::BerIdentifiedNull.new.to_ber].to_ber_sequence - } - ].to_ber_sequence + end, + ].to_ber_sequence, ].to_ber_contextspecific(1) when :get_response [ @@ -249,10 +249,10 @@ def pdu_to_ber_string error_status.to_ber, error_index.to_ber, [ - @variables.map {|n,v| + @variables.map do|n, v| [n.to_ber_oid, v.to_ber].to_ber_sequence - } - ].to_ber_sequence + end, + ].to_ber_sequence, ].to_ber_contextspecific(2) else raise Error.new( "unknown pdu-type: #{pdu_type}" ) diff --git a/net-ldap.gemspec b/net-ldap.gemspec index 97c12906..7516759b 100644 --- a/net-ldap.gemspec +++ b/net-ldap.gemspec @@ -26,11 +26,12 @@ the most recent LDAP RFCs (4510-4519, plutions of 4520-4532).} 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 = ">= 1.9.3" + 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", "~> 10.0") - s.add_development_dependency("rubocop", "~> 0.28.0") + s.add_development_dependency("rubocop", "~> 0.42.0") s.add_development_dependency("test-unit") + s.add_development_dependency("byebug") end diff --git a/script/changelog b/script/changelog new file mode 100755 index 00000000..cda2ad83 --- /dev/null +++ b/script/changelog @@ -0,0 +1,47 @@ +#!/bin/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 diff --git a/script/generate-fixture-ca b/script/generate-fixture-ca new file mode 100755 index 00000000..89eb3d8d --- /dev/null +++ b/script/generate-fixture-ca @@ -0,0 +1,48 @@ +#!/bin/bash + +BASE_PATH=$( cd "`dirname $0`/../test/fixtures/ca" && pwd ) +cd "${BASE_PATH}" || exit 4 + +USAGE=$( cat << EOS +Usage: + $0 --regenerate + +Generates a new self-signed CA, for integration testing. This should only need +to be run if you are writing new TLS/SSL tests, and need to generate +additional fixtuer CAs. + +This script uses the GnuTLS certtool CLI. If you are on macOS, +'brew install gnutls', and it will be installed as 'gnutls-certtool'. +Apple unfortunately ships with an incompatible /usr/bin/certtool that does +different things. +EOS +) + +if [ "x$1" != 'x--regenerate' ]; then + echo "${USAGE}" + exit 1 +fi + +TOOL=`type -p certtool` +if [ "$(uname)" = "Darwin" ]; then + TOOL=`type -p gnutls-certtool` + if [ ! -x "${TOOL}" ]; then + echo "Sorry, Darwin requires gnutls-certtool; try `brew install gnutls`" + exit 2 + fi +fi + +if [ ! -x "${TOOL}" ]; then + echo "Sorry, no certtool found!" + exit 3 +fi +export TOOL + + +${TOOL} --generate-privkey > ./cakey.pem +${TOOL} --generate-self-signed \ + --load-privkey ./cakey.pem \ + --template ./ca.info \ + --outfile ./cacert.pem + +echo "cert and private key generated! Don't forget to check them in" diff --git a/script/install-openldap b/script/install-openldap index b9efac98..3e391d87 100755 --- a/script/install-openldap +++ b/script/install-openldap @@ -2,8 +2,8 @@ set -e set -x -BASE_PATH="$( cd `dirname $0`/../test/fixtures/openldap && pwd )" -SEED_PATH="$( cd `dirname $0`/../test/fixtures && pwd )" +BASE_PATH=$( cd "`dirname $0`/../test/fixtures/openldap" && pwd ) +SEED_PATH=$( cd "`dirname $0`/../test/fixtures" && pwd ) dpkg -s slapd time ldap-utils gnutls-bin ssl-cert > /dev/null ||\ DEBIAN_FRONTEND=noninteractive apt-get update -y --force-yes && \ @@ -48,47 +48,58 @@ chown -R openldap.openldap /var/lib/ldap rm -rf $TMPDIR # SSL +export CA_CERT="/usr/local/share/ca-certificates/rubyldap-ca.crt" +export CA_KEY="/etc/ssl/private/rubyldap-ca.key" -sh -c "certtool --generate-privkey > /etc/ssl/private/cakey.pem" +# The self-signed fixture CA cert & key are generated by +# `script/generate-fiuxture-ca` and checked into version control. +# You shouldn't need to muck with these unless you're writing more +# TLS/SSL integration tests, and need special magic values in the cert. -sh -c "cat > /etc/ssl/ca.info < /etc/ssl/ldap01.info <> /etc/ssl/ldap01.info +done + # Create the server certificate certtool --generate-certificate \ --load-privkey /etc/ssl/private/ldap01_slapd_key.pem \ - --load-ca-certificate /etc/ssl/certs/cacert.pem \ - --load-ca-privkey /etc/ssl/private/cakey.pem \ + --load-ca-certificate "${CA_CERT}" \ + --load-ca-privkey "${CA_KEY}" \ --template /etc/ssl/ldap01.info \ --outfile /etc/ssl/certs/ldap01_slapd_cert.pem ldapmodify -Y EXTERNAL -H ldapi:/// <> /etc/hosts +grep ldap02 /etc/hosts || echo "127.0.0.1 ldap02.example.com" >> /etc/hosts +grep bogus /etc/hosts || echo "127.0.0.1 bogus.example.com" >> /etc/hosts + service slapd restart diff --git a/test/ber/core_ext/test_array.rb b/test/ber/core_ext/test_array.rb index 308fffc5..2d1e957a 100644 --- a/test/ber/core_ext/test_array.rb +++ b/test/ber/core_ext/test_array.rb @@ -6,7 +6,7 @@ def test_control_code_array 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 + res = [['1.2.3', true], ['1.7.9', false]].to_ber_control assert_equal control_codes, res end diff --git a/test/ber/test_ber.rb b/test/ber/test_ber.rb index 92b3902d..5d5c1266 100644 --- a/test/ber/test_ber.rb +++ b/test/ber/test_ber.rb @@ -6,8 +6,8 @@ def test_empty_array end def test_array - ary = [1,2,3] - encoded_ary = ary.map { |el| el.to_ber }.to_ber + ary = [1, 2, 3] + encoded_ary = ary.map(&:to_ber).to_ber assert_equal ary, encoded_ary.read_ber end @@ -135,7 +135,15 @@ def test_ascii_data_in_utf8 assert_equal "UTF-8", bis.encoding.name end - def test_ut8_data_in_utf8 + 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) diff --git a/test/fixtures/ca/ca.info b/test/fixtures/ca/ca.info new file mode 100644 index 00000000..c0fd3629 --- /dev/null +++ b/test/fixtures/ca/ca.info @@ -0,0 +1,4 @@ +cn = rubyldap +ca +cert_signing_key +expiration_days = 7200 diff --git a/test/fixtures/ca/cacert.pem b/test/fixtures/ca/cacert.pem new file mode 100644 index 00000000..0218dd8a --- /dev/null +++ b/test/fixtures/ca/cacert.pem @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIID7zCCAlegAwIBAgIMV7zWei6SNfABx6jMMA0GCSqGSIb3DQEBCwUAMBMxETAP +BgNVBAMTCHJ1YnlsZGFwMB4XDTE2MDgyMzIzMDQyNloXDTM2MDUxMDIzMDQyNlow +EzERMA8GA1UEAxMIcnVieWxkYXAwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGK +AoIBgQDGe9wziGHZJhIf+IEKSk1tpT9Mu7YgsUwjrlutvkoO1Q6K+amTAVDXizPf +1DVSDpZP5+CfBOznhgLMsPvrQ02w4qx5/6X9L+zJcMk8jTNYSKj5uIKpK52E7Uok +aygMXeaqroPONGkoJIZiVGgdbWfTvcffTm8FOhztXUbMrMXJNinFsocGHEoMNN8b +vqgAyG4+DFHoK4L0c6eQjE4nZBChieZdShUhaBpV7r2qSNbPw67cvAKuEzml58mV +1ZF1F73Ua8gPWXHEfUe2GEfG0NnRq6sGbsDYe/DIKxC7AZ89udZF3WZXNrPhvXKj +ZT7njwcMQemns4dNPQ0k2V4vAQ8pD8r8Qvb65FiSopUhVaGQswAnIMS1DnFq88AQ +KJTKIXbBuMwuaNNSs6R/qTS2RDk1w+CGpRXAg7+1SX5NKdrEsu1IaABA/tQ/zKKk +OLLJaD0giX1weBVmNeFcKxIoT34VS59eEt5APmPcguJnx+aBrA9TLzSO788apBN0 +4lGAmR0CAwEAAaNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwQA +MB0GA1UdDgQWBBRTvXSkge03oqLu7UUjFI+oLYwnujANBgkqhkiG9w0BAQsFAAOC +AYEATSZQWH+uSN5GvOUvJ8LHWkeVovn0UhboK0K7GzmMeGz+dp/Xrj6eQ4ONK0zI +RCJyoo/nCR7CfQ5ujVXr03XD2SUgyD565ulXuhw336DasL5//fucmQYDeqhwbKML +FTzsF9H9dO4J5TjxJs7e5dRJ0wrP/XEY+WFhXXdSHTl8vGCI6QqWc7TvDpmbS4iX +uTzjJswu9Murt9JUJNMN2DlDi/vBBeruaj4c2cMMnKMvkfj14kd8wMocmzj+gVQl +r+fRQbKAJNec65lA4/Zeb6sD9SAi0ZIVgxA4a7g8/sdNWHIAxPicpJkIJf30TsyY +F+8+Hd5mBtCbvFfAVkT6bHBP1OiAgNke+Rh/j/sQbyWbKCKw0+jpFJgO9KUNGfC0 +O/CqX+J4G7HqL8VJqrLnBvOdhfetAvNQtf1gcw5ZwpeEFM+Kvx/lsILaIYdAUSjX +ePOc5gI2Bi9WXq+T9AuhSf+TWUR874m/rdTWe5fM8mXCNl7C4I5zCqLltEDkSoMP +jDj/ +-----END CERTIFICATE----- diff --git a/test/fixtures/ca/cakey.pem b/test/fixtures/ca/cakey.pem new file mode 100644 index 00000000..d75ab299 --- /dev/null +++ b/test/fixtures/ca/cakey.pem @@ -0,0 +1,190 @@ +Public Key Info: + Public Key Algorithm: RSA + Key Security Level: High (3072 bits) + +modulus: + 00:c6:7b:dc:33:88:61:d9:26:12:1f:f8:81:0a:4a:4d + 6d:a5:3f:4c:bb:b6:20:b1:4c:23:ae:5b:ad:be:4a:0e + d5:0e:8a:f9:a9:93:01:50:d7:8b:33:df:d4:35:52:0e + 96:4f:e7:e0:9f:04:ec:e7:86:02:cc:b0:fb:eb:43:4d + b0:e2:ac:79:ff:a5:fd:2f:ec:c9:70:c9:3c:8d:33:58 + 48:a8:f9:b8:82:a9:2b:9d:84:ed:4a:24:6b:28:0c:5d + e6:aa:ae:83:ce:34:69:28:24:86:62:54:68:1d:6d:67 + d3:bd:c7:df:4e:6f:05:3a:1c:ed:5d:46:cc:ac:c5:c9 + 36:29:c5:b2:87:06:1c:4a:0c:34:df:1b:be:a8:00:c8 + 6e:3e:0c:51:e8:2b:82:f4:73:a7:90:8c:4e:27:64:10 + a1:89:e6:5d:4a:15:21:68:1a:55:ee:bd:aa:48:d6:cf + c3:ae:dc:bc:02:ae:13:39:a5:e7:c9:95:d5:91:75:17 + bd:d4:6b:c8:0f:59:71:c4:7d:47:b6:18:47:c6:d0:d9 + d1:ab:ab:06:6e:c0:d8:7b:f0:c8:2b:10:bb:01:9f:3d + b9:d6:45:dd:66:57:36:b3:e1:bd:72:a3:65:3e:e7:8f + 07:0c:41:e9:a7:b3:87:4d:3d:0d:24:d9:5e:2f:01:0f + 29:0f:ca:fc:42:f6:fa:e4:58:92:a2:95:21:55:a1:90 + b3:00:27:20:c4:b5:0e:71:6a:f3:c0:10:28:94:ca:21 + 76:c1:b8:cc:2e:68:d3:52:b3:a4:7f:a9:34:b6:44:39 + 35:c3:e0:86:a5:15:c0:83:bf:b5:49:7e:4d:29:da:c4 + b2:ed:48:68:00:40:fe:d4:3f:cc:a2:a4:38:b2:c9:68 + 3d:20:89:7d:70:78:15:66:35:e1:5c:2b:12:28:4f:7e + 15:4b:9f:5e:12:de:40:3e:63:dc:82:e2:67:c7:e6:81 + ac:0f:53:2f:34:8e:ef:cf:1a:a4:13:74:e2:51:80:99 + 1d: + +public exponent: + 01:00:01: + +private exponent: + 1d:0d:9a:50:ec:c0:ad:e1:75:bb:ba:4b:61:2f:39:20 + 38:95:08:6d:5d:9e:71:75:5c:af:b3:f9:bd:a5:e7:7f + e6:4e:0f:77:73:ee:38:60:24:9f:26:3f:50:c2:bf:21 + df:76:68:99:be:45:d3:29:f9:94:ee:bf:21:53:cb:b6 + 7d:a7:93:80:09:53:03:45:dc:c2:a6:a2:37:64:f1:a2 + 49:21:ac:91:6b:a3:d7:bd:d2:62:0c:ec:a6:83:10:e7 + a7:ca:3d:be:dc:4b:1c:36:24:79:96:33:5b:43:5d:74 + 50:0e:46:b0:9b:6d:9f:71:06:89:a5:c8:65:ed:d9:a3 + 15:00:3c:3e:a9:75:50:9d:72:cb:c9:aa:e1:ba:a3:9c + 07:77:14:32:30:d4:4d:65:f4:7c:23:1d:79:84:9b:2e + 9a:19:df:43:ed:cd:e3:08:1f:d5:ff:6b:42:98:36:f7 + 44:cc:48:b4:f7:b8:16:b3:23:37:8d:b8:22:3f:8a:86 + db:71:b3:85:2d:6d:42:44:b7:dc:c1:36:e0:c4:0f:fe + cb:76:84:81:e2:83:f5:82:76:a9:7b:35:d5:44:00:d1 + 1a:fc:ef:b9:a4:2b:62:aa:f8:56:eb:60:e5:16:33:f1 + 28:e1:da:91:50:e3:a4:c7:d6:30:21:cf:04:07:cd:8c + b6:9e:b0:a7:6c:96:57:2e:09:5b:39:26:d0:60:be:e3 + 90:59:a3:8e:e7:6e:3f:62:7e:b4:2a:e1:8f:00:37:7a + 83:9e:7a:9c:d2:ae:ba:50:84:73:65:3a:64:95:d8:48 + f9:fd:0e:c3:5b:6e:08:3b:c5:c9:1c:29:55:bb:67:e8 + fa:50:40:30:2a:d1:b7:cf:54:a8:f0:f0:76:89:ad:19 + e7:a0:3a:56:6c:75:c5:bc:d8:46:ce:1e:66:f2:61:96 + 11:e4:57:cc:52:ff:e4:ed:6b:2c:ce:78:15:ba:b7:ed + 31:f2:68:88:79:bf:7c:29:3c:2f:66:71:0b:09:b7:41 + + +prime1: + 00:fd:c2:37:b9:6f:77:88:51:a2:f7:4f:c2:3c:a4:57 + bf:ba:71:14:f3:61:f4:39:78:22:3d:bc:d8:d2:4e:c0 + 4b:9e:c2:6d:38:a8:21:e2:70:1a:96:48:95:18:85:01 + 46:fb:62:a4:81:09:f8:2a:3a:87:78:07:5d:93:54:ce + 2a:51:b3:51:6f:61:0a:2e:9d:b0:51:37:e3:13:bd:81 + 23:2b:61:53:fa:ac:08:dc:a0:e6:63:a3:b0:cc:cf:73 + 1d:65:b7:11:bc:29:70:fb:72:ea:63:9d:67:02:d6:35 + 24:13:1d:bc:72:fb:9e:3d:ab:0b:57:6e:bd:a1:51:56 + f9:bc:96:15:74:a3:31:16:c6:b8:98:1b:0a:a2:59:7c + c8:b7:14:b8:5b:f3:2e:26:b4:f0:46:c4:3d:27:dd:41 + 31:52:a7:15:a8:af:6a:98:a5:9c:20:17:f9:1d:54:54 + ff:10:91:a3:a5:ca:ac:63:e7:16:2b:71:3c:3a:cd:4f + ed: + +prime2: + 00:c8:3c:a8:9f:8a:db:42:b5:8d:cf:2a:a1:2f:e5:73 + 05:de:30:d8:17:b9:5c:9d:08:60:02:c9:66:9d:88:50 + ac:cd:0f:b5:47:b4:a8:73:3b:7d:65:79:bf:4c:6f:d0 + e2:03:ed:d4:28:4e:00:07:23:00:01:4f:05:de:9b:44 + 1a:84:ae:09:4a:d6:ed:61:5d:77:e2:fa:13:99:4c:b7 + 76:72:3d:f8:53:93:69:78:e8:bd:26:cb:b0:f9:01:f4 + 1d:20:4f:60:f5:ab:3c:19:85:73:34:f3:ec:d2:67:ef + 56:b8:5d:93:73:8e:d9:3e:28:ff:87:f5:4a:26:fa:b1 + ae:c6:d3:9d:03:e3:fd:c2:24:48:af:85:2a:8e:3b:5b + 93:07:38:91:21:ae:49:cb:6d:e3:30:81:15:ed:65:eb + dc:01:df:3b:9d:43:fd:a6:e1:df:ef:ad:22:42:34:f1 + 3f:81:5e:57:0a:e0:56:94:f2:2a:00:d0:cc:c5:50:67 + f1: + +coefficient: + 00:bd:23:8c:2e:a7:7b:6b:1e:85:77:db:7d:77:f6:e5 + b0:15:c6:e1:9e:35:57:72:df:35:6d:93:89:7f:83:9f + 63:7f:08:0a:b3:d4:ba:63:9b:10:7f:0f:d3:55:e9:38 + cf:90:37:3d:85:3d:a7:97:8c:33:f2:c2:b1:38:2b:db + 39:ca:a8:d0:23:d7:89:cc:8d:02:7d:61:9b:b6:04:69 + 14:e8:c9:84:34:36:6c:fb:84:58:cc:9a:53:74:a4:42 + bd:1d:25:1b:ba:82:c0:fb:23:2c:90:bb:35:4b:5b:b0 + 98:d0:ab:9d:61:6e:ea:e8:84:e7:a7:6c:ae:1b:2c:00 + cb:0f:1a:f8:e2:7c:fd:42:1a:e2:13:52:c7:50:fa:65 + c9:5f:ed:40:a8:7f:46:0e:ce:f6:56:83:6f:0e:8e:39 + f8:33:5f:83:de:be:be:ef:8c:66:ad:16:c8:ec:98:d4 + b2:b2:55:66:a2:9e:27:6a:84:f1:31:07:e8:bf:a7:a7 + bd: + +exp1: + 00:b6:50:0c:53:19:07:8b:14:03:fe:a4:fa:0b:31:93 + ad:b7:18:b9:91:a6:c5:9d:68:77:49:5d:dd:75:33:89 + 2a:8b:54:6a:be:32:e5:ad:57:17:72:f3:90:d2:fd:f4 + 0d:f8:5c:45:8e:44:08:5c:e6:92:1f:a5:43:10:af:f4 + 33:29:61:a8:d7:59:a3:c4:1c:1c:ea:2d:39:e3:1b:da + a4:d6:ec:e5:36:0a:d5:8f:15:b6:90:cd:b1:1f:64:c7 + f2:cd:fa:3a:2e:b2:a3:6e:b4:80:3b:b3:81:a7:e3:18 + 68:e3:a7:10:96:97:ba:77:d9:e4:9b:1b:7f:f8:5f:85 + 1a:85:e8:5a:5f:e3:43:48:76:db:76:c4:ae:de:37:66 + d4:99:dc:b4:1b:b3:da:6b:8a:c1:ba:46:11:1e:0b:f3 + 63:a9:5b:4b:cf:56:c0:42:0d:71:df:08:fa:3c:9d:33 + 37:d1:c2:a1:0d:63:50:79:b2:34:16:60:13:82:b7:b1 + 7d: + +exp2: + 00:98:38:2c:c4:24:4e:2c:b7:52:17:a4:43:a6:e2:99 + ff:62:fa:e4:bb:9c:49:40:83:66:61:97:f3:af:5c:3a + 60:32:ff:77:03:0c:de:65:c3:5a:bf:72:bf:2f:7f:6d + 5e:f4:37:af:69:f8:69:e3:03:03:74:fb:3a:ee:10:40 + c4:9c:0a:a5:bb:c4:09:ef:53:9b:d8:eb:dd:4c:53:da + c0:6b:76:9a:ba:06:3d:4f:12:37:01:30:25:d8:16:59 + 1a:6f:3e:88:ea:19:83:75:af:52:76:75:dc:99:d3:33 + 4a:4c:9b:ae:85:51:99:ea:bc:46:0d:78:36:27:cd:ba + 97:b0:44:9c:7f:a1:a9:7e:16:11:3f:85:4f:65:92:d0 + 39:c4:6a:87:42:00:79:ce:f1:39:9d:dc:f3:eb:65:e8 + d8:76:7f:da:94:e2:64:08:a2:7b:97:7b:99:a8:95:10 + b5:03:46:d1:8a:ce:22:63:d6:78:81:e8:39:52:e2:9e + 31: + + +Public Key ID: 53:BD:74:A4:81:ED:37:A2:A2:EE:ED:45:23:14:8F:A8:2D:8C:27:BA +Public key's random art: ++--[ RSA 3072]----+ +| . o. . | +| . +...+ | +| . o o.+ . | +| o o . . .ooo | +| o = . S o..o . | +| . o . .+.. | +|. . .. | +| . .. . | +|E oo.o | ++-----------------+ + +-----BEGIN RSA PRIVATE KEY----- +MIIG5QIBAAKCAYEAxnvcM4hh2SYSH/iBCkpNbaU/TLu2ILFMI65brb5KDtUOivmp +kwFQ14sz39Q1Ug6WT+fgnwTs54YCzLD760NNsOKsef+l/S/syXDJPI0zWEio+biC +qSudhO1KJGsoDF3mqq6DzjRpKCSGYlRoHW1n073H305vBToc7V1GzKzFyTYpxbKH +BhxKDDTfG76oAMhuPgxR6CuC9HOnkIxOJ2QQoYnmXUoVIWgaVe69qkjWz8Ou3LwC +rhM5pefJldWRdRe91GvID1lxxH1HthhHxtDZ0aurBm7A2HvwyCsQuwGfPbnWRd1m +Vzaz4b1yo2U+548HDEHpp7OHTT0NJNleLwEPKQ/K/EL2+uRYkqKVIVWhkLMAJyDE +tQ5xavPAECiUyiF2wbjMLmjTUrOkf6k0tkQ5NcPghqUVwIO/tUl+TSnaxLLtSGgA +QP7UP8yipDiyyWg9IIl9cHgVZjXhXCsSKE9+FUufXhLeQD5j3ILiZ8fmgawPUy80 +ju/PGqQTdOJRgJkdAgMBAAECggGAHQ2aUOzAreF1u7pLYS85IDiVCG1dnnF1XK+z ++b2l53/mTg93c+44YCSfJj9Qwr8h33Zomb5F0yn5lO6/IVPLtn2nk4AJUwNF3MKm +ojdk8aJJIayRa6PXvdJiDOymgxDnp8o9vtxLHDYkeZYzW0NddFAORrCbbZ9xBoml +yGXt2aMVADw+qXVQnXLLyarhuqOcB3cUMjDUTWX0fCMdeYSbLpoZ30PtzeMIH9X/ +a0KYNvdEzEi097gWsyM3jbgiP4qG23GzhS1tQkS33ME24MQP/st2hIHig/WCdql7 +NdVEANEa/O+5pCtiqvhW62DlFjPxKOHakVDjpMfWMCHPBAfNjLaesKdsllcuCVs5 +JtBgvuOQWaOO524/Yn60KuGPADd6g556nNKuulCEc2U6ZJXYSPn9DsNbbgg7xckc +KVW7Z+j6UEAwKtG3z1So8PB2ia0Z56A6Vmx1xbzYRs4eZvJhlhHkV8xS/+TtayzO +eBW6t+0x8miIeb98KTwvZnELCbdBAoHBAP3CN7lvd4hRovdPwjykV7+6cRTzYfQ5 +eCI9vNjSTsBLnsJtOKgh4nAalkiVGIUBRvtipIEJ+Co6h3gHXZNUzipRs1FvYQou +nbBRN+MTvYEjK2FT+qwI3KDmY6OwzM9zHWW3EbwpcPty6mOdZwLWNSQTHbxy+549 +qwtXbr2hUVb5vJYVdKMxFsa4mBsKoll8yLcUuFvzLia08EbEPSfdQTFSpxWor2qY +pZwgF/kdVFT/EJGjpcqsY+cWK3E8Os1P7QKBwQDIPKifittCtY3PKqEv5XMF3jDY +F7lcnQhgAslmnYhQrM0PtUe0qHM7fWV5v0xv0OID7dQoTgAHIwABTwXem0QahK4J +StbtYV134voTmUy3dnI9+FOTaXjovSbLsPkB9B0gT2D1qzwZhXM08+zSZ+9WuF2T +c47ZPij/h/VKJvqxrsbTnQPj/cIkSK+FKo47W5MHOJEhrknLbeMwgRXtZevcAd87 +nUP9puHf760iQjTxP4FeVwrgVpTyKgDQzMVQZ/ECgcEAtlAMUxkHixQD/qT6CzGT +rbcYuZGmxZ1od0ld3XUziSqLVGq+MuWtVxdy85DS/fQN+FxFjkQIXOaSH6VDEK/0 +MylhqNdZo8QcHOotOeMb2qTW7OU2CtWPFbaQzbEfZMfyzfo6LrKjbrSAO7OBp+MY +aOOnEJaXunfZ5Jsbf/hfhRqF6Fpf40NIdtt2xK7eN2bUmdy0G7Paa4rBukYRHgvz +Y6lbS89WwEINcd8I+jydMzfRwqENY1B5sjQWYBOCt7F9AoHBAJg4LMQkTiy3Uhek +Q6bimf9i+uS7nElAg2Zhl/OvXDpgMv93AwzeZcNav3K/L39tXvQ3r2n4aeMDA3T7 +Ou4QQMScCqW7xAnvU5vY691MU9rAa3aaugY9TxI3ATAl2BZZGm8+iOoZg3WvUnZ1 +3JnTM0pMm66FUZnqvEYNeDYnzbqXsEScf6GpfhYRP4VPZZLQOcRqh0IAec7xOZ3c +8+tl6Nh2f9qU4mQIonuXe5molRC1A0bRis4iY9Z4geg5UuKeMQKBwQC9I4wup3tr +HoV323139uWwFcbhnjVXct81bZOJf4OfY38ICrPUumObEH8P01XpOM+QNz2FPaeX +jDPywrE4K9s5yqjQI9eJzI0CfWGbtgRpFOjJhDQ2bPuEWMyaU3SkQr0dJRu6gsD7 +IyyQuzVLW7CY0KudYW7q6ITnp2yuGywAyw8a+OJ8/UIa4hNSx1D6Zclf7UCof0YO +zvZWg28Ojjn4M1+D3r6+74xmrRbI7JjUsrJVZqKeJ2qE8TEH6L+np70= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/cacert.pem b/test/fixtures/cacert.pem deleted file mode 100644 index f8b134e1..00000000 --- a/test/fixtures/cacert.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDRzCCAf+gAwIBAgIEVHpbmjANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDEwhy -dWJ5bGRhcDAeFw0xNDExMjkyMzQ5NDZaFw0xNTExMjkyMzQ5NDZaMBMxETAPBgNV -BAMTCHJ1YnlsZGFwMIIBUjANBgkqhkiG9w0BAQEFAAOCAT8AMIIBOgKCATEA4pKe -cDCNuL53fkpO/WSAS+gmMTsOs+oOK71kZlk2QT/MBz8TxC6m358qCADjnXcMVVxa -ySQbQlVKZMkIvLNciZbiLDgC5II0NbHACNa8rqenoKRjS4J9W3OhA8EmnXn/Me+8 -uMCI9tfnKNRZYdkQZlra4I+Idn+xYfl/5q5b/7ZjPS2zY/585hFEYE+5vfOZVBSU -3HMNSeuJvTehLv7dD7aQfXNM4cRgHXequkJQ/HLLFAO4AgJ+LJrFWpj7GWz3crgr -9G5px4T78wJH3NQiOsG6UBXPw8c4T+Z6GAWX2l1zs1gZsaiCVbAraqK3404lL7yp -+ThbsW3ifzgNPhmjScXBLdbEDrrAKosW7kkTOGzxiMCBmNlj2SKhcztoduAtfF1f -Fs2Jk8MRTHwO8ThD7wIDAQABo0MwQTAPBgNVHRMBAf8EBTADAQH/MA8GA1UdDwEB -/wQFAwMHBAAwHQYDVR0OBBYEFJDm67ekyFu4/Z7VcO6Vk/5pinGcMA0GCSqGSIb3 -DQEBCwUAA4IBMQDHeEPzfYRtjynpUKyrtxx/6ZVOfCLuz4eHkBZggz/pJacDCv/a -I//W03XCk8RWq/fWVVUzvxXgPwnYcw992PLM7XW81zp6ruRUDWooYnjHZZz3bRhe -kC4QvM2mZhcsMVmhmWWKZn81qXgVdUY1XNRhk87cuXjF/UTpEieFvWAsCUkFZkqB -AmySCuI/FuPaauT1YAltkIlYAEIGNJGZDMf2BTVUQpXhTXeS9/AZWLNDBwiq+fwo -YYnsr9MnBXCEmg1gVSR/Ay2AZmbYfiYtb5kU8uq2lSWAUb4LX6HZl82wo3OilrJ2 -WXl6Qf+Fcy4qqkRt4AKHjtzizpEDCOVYuuG0Zoy+QnxNXRsEzpb8ymnJFrcgYfk/ -6Lv2gWAFl5FqCZp7gBWg55eL2coT4C+mbNTF ------END CERTIFICATE----- diff --git a/test/fixtures/openldap/slapd.conf.ldif b/test/fixtures/openldap/slapd.conf.ldif index 6ba5cf77..77a6af09 100644 --- a/test/fixtures/openldap/slapd.conf.ldif +++ b/test/fixtures/openldap/slapd.conf.ldif @@ -3,7 +3,7 @@ objectClass: olcGlobal cn: config olcPidFile: /var/run/slapd/slapd.pid olcArgsFile: /var/run/slapd/slapd.args -olcLogLevel: none +olcLogLevel: -1 olcToolThreads: 1 dn: olcDatabase={-1}frontend,cn=config diff --git a/test/integration/test_add.rb b/test/integration/test_add.rb index 3cddb18a..dcac6149 100644 --- a/test/integration/test_add.rb +++ b/test/integration/test_add.rb @@ -14,7 +14,7 @@ def test_add uid: "added-user1", cn: "added-user1", sn: "added-user1", - mail: "added-user1@rubyldap.com" + mail: "added-user1@rubyldap.com", } assert @ldap.add(dn: @dn, attributes: attrs), @ldap.get_operation_result.inspect diff --git a/test/integration/test_ber.rb b/test/integration/test_ber.rb index 8fb4d374..51e93334 100644 --- a/test/integration/test_ber.rb +++ b/test/integration/test_ber.rb @@ -12,7 +12,7 @@ def test_true_ber_encoding filter: "(uid=user1)", size: 1, attributes: attrs, - attributes_only: true + attributes_only: true, ).first # matches attributes we requested diff --git a/test/integration/test_bind.rb b/test/integration/test_bind.rb index bea6b034..bd1281e2 100644 --- a/test/integration/test_bind.rb +++ b/test/integration/test_bind.rb @@ -2,11 +2,23 @@ class TestBindIntegration < LDAPIntegrationTestCase def test_bind_success - assert @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1"), @ldap.get_operation_result.inspect + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect + end + + def test_bind_timeout + @ldap.port = 8389 + 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(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: ""), @ldap.get_operation_result.inspect + refute @ldap.bind(BIND_CREDS.merge(password: '')), + @ldap.get_operation_result.inspect result = @ldap.get_operation_result assert_equal Net::LDAP::ResultCodeUnwillingToPerform, result.code @@ -17,18 +29,216 @@ def test_bind_anonymous_fail end def test_bind_fail - refute @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "not my password"), @ldap.get_operation_result.inspect + refute @ldap.bind(BIND_CREDS.merge(password: "not my password")), + @ldap.get_operation_result.inspect end def test_bind_tls_with_cafile - tls_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.merge(:ca_file => CA_FILE) - @ldap.encryption(method: :start_tls, tls_options: tls_options) - assert @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1"), @ldap.get_operation_result.inspect + @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 = '127.0.0.1' + @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 = '127.0.0.1' + @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 + @ldap.host = '127.0.0.1' + @ldap.encryption( + method: :start_tls, + tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER, + ca_file: CA_FILE }, + ) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionRefusedError 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 + @ldap.host = '127.0.0.1' + @ldap.encryption( + method: :start_tls, + tls_options: TLS_OPTS.merge(ca_file: CA_FILE), + ) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionRefusedError 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 + @ldap.host = '127.0.0.1' + @ldap.encryption( + method: :start_tls, + tls_options: { ca_file: CA_FILE }, + ) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionRefusedError 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 + @ldap.host = 'localhost' + @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 + @ldap.host = 'localhost' + @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 = '127.0.0.1' + @ldap.encryption(method: :start_tls, tls_options: {}) + error = assert_raise Net::LDAP::Error, + Net::LDAP::ConnectionRefusedError 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_verify_none - tls_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.merge(:verify_mode => OpenSSL::SSL::VERIFY_NONE) - @ldap.encryption(method: :start_tls, tls_options: tls_options) - assert @ldap.bind(method: :simple, username: "uid=user1,ou=People,dc=rubyldap,dc=com", password: "passworD1"), @ldap.get_operation_result.inspect + # The following depend on /etc/hosts hacking. + # We can do that on CI, but it's less than cool on people's dev boxes + def test_bind_tls_with_multiple_hosts + omit_unless ENV['TRAVIS'] == 'true' + + @ldap.host = nil + @ldap.hosts = [['ldap01.example.com', 389], ['ldap02.example.com', 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_unless ENV['TRAVIS'] == 'true' + + @ldap.host = nil + @ldap.hosts = [['127.0.0.1', 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_unless ENV['TRAVIS'] == 'true' + + @ldap.host = nil + @ldap.hosts = [['127.0.0.1', 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 + omit_unless ENV['TRAVIS'] == 'true' + + @ldap.host = nil + @ldap.hosts = [['127.0.0.1', 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_unless ENV['TRAVIS'] == 'true' + + @ldap.encryption( + method: :start_tls, + tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER }, + ) + assert @ldap.bind(BIND_CREDS), + @ldap.get_operation_result.inspect + end + + # Inverse of the above! Don't run this on Travis, only on Vagrant. + # Since Vagrant's hypervisor *won't* have the CA in the system + # x509 store, we can assume validation will fail + def test_bind_tls_valid_hostname_system_on_vagrant_fails + omit_if ENV['TRAVIS'] == 'true' + + @ldap.encryption( + method: :start_tls, + tls_options: { verify_mode: OpenSSL::SSL::VERIFY_PEER }, + ) + error = assert_raise Net::LDAP::Error do + @ldap.bind BIND_CREDS + end + assert_equal( + "SSL_connect returned=1 errno=0 state=error: certificate verify failed", + error.message, + ) end end diff --git a/test/integration/test_delete.rb b/test/integration/test_delete.rb index 355df7b9..0cca32a9 100644 --- a/test/integration/test_delete.rb +++ b/test/integration/test_delete.rb @@ -12,7 +12,7 @@ def setup uid: "delete-user1", cn: "delete-user1", sn: "delete-user1", - mail: "delete-user1@rubyldap.com" + 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 diff --git a/test/integration/test_open.rb b/test/integration/test_open.rb index 36724f5d..a7ac09da 100644 --- a/test/integration/test_open.rb +++ b/test/integration/test_open.rb @@ -63,7 +63,7 @@ def test_nested_add_with_open uid: "nested-open-added-user1", cn: "nested-open-added-user1", sn: "nested-open-added-user1", - mail: "nested-open-added-user1@rubyldap.com" + mail: "nested-open-added-user1@rubyldap.com", } @ldap.authenticate "cn=admin,dc=rubyldap,dc=com", "passworD1" diff --git a/test/integration/test_password_modify.rb b/test/integration/test_password_modify.rb new file mode 100644 index 00000000..ed8d4f5b --- /dev/null +++ b/test/integration/test_password_modify.rb @@ -0,0 +1,93 @@ +require_relative '../test_helper' + +class TestPasswordModifyIntegration < LDAPIntegrationTestCase + def setup + super + @admin_account = {dn: 'cn=admin,dc=rubyldap,dc=com', password: 'passworD1', method: :simple} + @ldap.authenticate @admin_account[:dn], @admin_account[:password] + + @dn = 'uid=modify-password-user1,ou=People,dc=rubyldap,dc=com' + + 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: 'passworD1', + } + 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: 'passworD1', + } + end + + def test_password_modify + assert @ldap.password_modify(dn: @dn, + auth: @auth, + old_password: 'passworD1', + new_password: 'passworD2') + + assert @ldap.get_operation_result.extended_response.nil?, + 'Should not have generated a new password' + + refute @ldap.bind(username: @dn, password: 'passworD1', 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: 'passworD1') + + generated_password = @ldap.get_operation_result.extended_response[0][0] + + assert generated_password, 'Should have generated a password' + + refute @ldap.bind(username: @dn, password: 'passworD1', 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: 'passworD1', 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: 'passworD1', 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 diff --git a/test/integration/test_search.rb b/test/integration/test_search.rb index b56052ce..96f9ff42 100644 --- a/test/integration/test_search.rb +++ b/test/integration/test_search.rb @@ -57,7 +57,7 @@ def test_search_timeout entries << entry end - payload, _ = events.pop + payload, = events.pop assert_equal 5, payload[:time] assert_equal entries, result end diff --git a/test/support/vm/openldap/README.md b/test/support/vm/openldap/README.md index a2769567..f79f4dc6 100644 --- a/test/support/vm/openldap/README.md +++ b/test/support/vm/openldap/README.md @@ -1,8 +1,31 @@ # Local OpenLDAP Integration Testing -Set up a [Vagrant](http://www.vagrantup.com/) VM to run integration tests against OpenLDAP locally. +Set up a [Vagrant](http://www.vagrantup.com/) VM to run integration +tests against OpenLDAP locally. *NOTE*: To support some of the SSL tests, +Vagrant forwards localhost port 9389 to VM host port 9389. The port mapping +goes away when you run `vagrant destroy`. -To run integration tests locally: +## Install Vagrant + +*NOTE*: The Vagrant gem (`gem install vagrant`) is +[no longer supported](https://www.vagrantup.com/docs/installation/). If you've +previously installed it, run `gem uninstall vagrant`. If you're an rbenv +user, you probably want to follow that up with `rbenv rehash; hash -r`. + +If you use Homebrew on macOS: +``` bash +$ brew update +$ brew cask install virtualbox +$ brew cask install vagrant +$ brew cask install vagrant-manager +$ vagrant plugin install vagrant-vbguest +``` + +Installing Vagrant and virtualbox on other operating systems is left +as an exercise to the reader. Note the `vagrant-vbguest` plugin is required +to update the VirtualBox guest extensions in the guest VM image. + +## Run the tests ``` bash # start VM (from the correct directory) @@ -15,6 +38,9 @@ $ ip=$(vagrant ssh -- "ifconfig eth1 | grep -o -E '[0-9]+\.[0-9]+\.[0-9]+\.[0-9] # change back to root project directory $ cd ../../../.. +# set the TCP port for testing +$ export INTEGRATION_PORT=9389 + # run all tests, including integration tests $ time INTEGRATION=openldap INTEGRATION_HOST=$ip bundle exec rake @@ -27,6 +53,12 @@ $ export INTEGRATION_HOST=$ip # now run tests without having to set ENV variables $ time bundle exec rake + +# Once you're all done +$ cd test/support/vm/openldap +$ vagrant destroy ``` -You may need to `gem install vagrant` first in order to provision the VM. +If at any point your VM appears to have broken itself, `vagrant destroy` +from the `test/support/vm/openldap` directory will blow it away. You can +then do `vagrant up` and start over. diff --git a/test/support/vm/openldap/Vagrantfile b/test/support/vm/openldap/Vagrantfile index 96233e92..1f375e76 100644 --- a/test/support/vm/openldap/Vagrantfile +++ b/test/support/vm/openldap/Vagrantfile @@ -10,6 +10,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| config.vm.box = "hashicorp/precise64" config.vm.network "private_network", type: :dhcp + config.vm.network "forwarded_port", guest: 389, host: 9389 config.ssh.forward_agent = true diff --git a/test/test_auth_adapter.rb b/test/test_auth_adapter.rb new file mode 100644 index 00000000..9e4c6002 --- /dev/null +++ b/test/test_auth_adapter.rb @@ -0,0 +1,15 @@ +require '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 diff --git a/test/test_dn.rb b/test/test_dn.rb index 0cb2ec5a..5fff6ae8 100644 --- a/test/test_dn.rb +++ b/test/test_dn.rb @@ -13,17 +13,17 @@ def test_escape_on_initialize def test_to_a dn = Net::LDAP::DN.new('cn=James, ou=Company\\,\\20LLC') - assert_equal ['cn','James','ou','Company, LLC'], dn.to_a + 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 + 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 + assert_equal ['1.23.4', '#A3B4D5', 'ou', 'Company'], dn.to_a end # TODO: raise a more specific exception than RuntimeError diff --git a/test/test_filter.rb b/test/test_filter.rb index 2bcccd92..807c86dd 100644 --- a/test/test_filter.rb +++ b/test/test_filter.rb @@ -13,11 +13,11 @@ def test_invalid_filter_string end def test_invalid_filter - assert_raises(Net::LDAP::OperatorError) { + 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 @@ -144,7 +144,7 @@ def test_ber_conversion '(: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))' + '(&(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) @@ -195,7 +195,7 @@ def test_well_known_ber_string "foo" "\\2A\\5C" "bar", "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}") diff --git a/test/test_filter_parser.rb b/test/test_filter_parser.rb index 210e0218..6f1ca48b 100644 --- a/test/test_filter_parser.rb +++ b/test/test_filter_parser.rb @@ -14,6 +14,10 @@ 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 diff --git a/test/test_helper.rb b/test/test_helper.rb index 640b0e23..0a976be4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -14,10 +14,18 @@ if File.exist?("/etc/ssl/certs/cacert.pem") "/etc/ssl/certs/cacert.pem" else - File.expand_path("fixtures/cacert.pem", File.dirname(__FILE__)) + File.expand_path("fixtures/ca/cacert.pem", File.dirname(__FILE__)) end end +BIND_CREDS = { + method: :simple, + username: "uid=user1,ou=People,dc=rubyldap,dc=com", + password: "passworD1", +}.freeze + +TLS_OPTS = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.merge({}).freeze + if RUBY_VERSION < "2.0" class String def b @@ -56,7 +64,7 @@ def setup @service = MockInstrumentationService.new @ldap = Net::LDAP.new \ host: ENV.fetch('INTEGRATION_HOST', 'localhost'), - port: 389, + port: ENV.fetch('INTEGRATION_PORT', 389), admin_user: 'uid=admin,dc=rubyldap,dc=com', admin_password: 'passworD1', search_domains: %w(dc=rubyldap,dc=com), diff --git a/test/test_ldap.rb b/test/test_ldap.rb index 9704b346..8d6a9a72 100644 --- a/test/test_ldap.rb +++ b/test/test_ldap.rb @@ -1,6 +1,28 @@ require '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) @@ -15,8 +37,9 @@ def setup def test_instrument_bind events = @service.subscribe "bind.net_ldap" - bind_result = flexmock(:bind_result, :success? => true) - flexmock(@connection).should_receive(:bind).with(Hash).and_return(bind_result) + fake_connection = FakeConnection.new + @subject.connection = fake_connection + bind_result = fake_connection.bind assert @subject.bind @@ -28,10 +51,9 @@ def test_instrument_bind def test_instrument_search events = @service.subscribe "search.net_ldap" - flexmock(@connection).should_receive(:bind).and_return(flexmock(:bind_result, :result_code => Net::LDAP::ResultCodeSuccess)) - flexmock(@connection).should_receive(:search).with(Hash, Proc). - yields(entry = Net::LDAP::Entry.new("uid=user1,ou=users,dc=example,dc=com")). - and_return(flexmock(:search_result, :success? => true, :result_code => Net::LDAP::ResultCodeSuccess)) + fake_connection = FakeConnection.new + @subject.connection = fake_connection + entry = fake_connection.search refute_nil @subject.search(:filter => "(uid=user1)") @@ -44,10 +66,9 @@ def test_instrument_search def test_instrument_search_with_size events = @service.subscribe "search.net_ldap" - flexmock(@connection).should_receive(:bind).and_return(flexmock(:bind_result, :result_code => Net::LDAP::ResultCodeSuccess)) - flexmock(@connection).should_receive(:search).with(Hash, Proc). - yields(entry = Net::LDAP::Entry.new("uid=user1,ou=users,dc=example,dc=com")). - and_return(flexmock(:search_result, :success? => true, :result_code => Net::LDAP::ResultCodeSizeLimitExceeded)) + fake_connection = FakeConnection.new + @subject.connection = fake_connection + entry = fake_connection.search refute_nil @subject.search(:filter => "(uid=user1)", :size => 1) @@ -57,4 +78,37 @@ def test_instrument_search_with_size 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 diff --git a/test/test_ldap_connection.rb b/test/test_ldap_connection.rb index 96b542ac..8489c377 100644 --- a/test/test_ldap_connection.rb +++ b/test/test_ldap_connection.rb @@ -1,45 +1,132 @@ require_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 Net::LDAP::ConnectionRefusedError 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 - Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636) + connection.socket end end def test_blocked_port - flexmock(TCPSocket).should_receive(:new).and_raise(SocketError) + connection = Net::LDAP::Connection.new(:host => "fail.SocketError", :port => 636, :socket_class => FakeTCPSocket) assert_raise Net::LDAP::Error do - Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636) + 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 Net::LDAP::ConnectionRefusedError do + connection.socket + end + end + assert_equal("Deprecation warning: Net::LDAP::ConnectionRefused will be deprecated. Use Errno::ECONNREFUSED instead.\n", stderr) + end + + def test_connection_timeout + connection = Net::LDAP::Connection.new(:host => "fail.Errno::ETIMEDOUT", :port => 636, :socket_class => FakeTCPSocket) + stderr = capture_stderr do + assert_raise Net::LDAP::Error do + connection.socket + end end end def test_raises_unknown_exceptions - error = Class.new(StandardError) - flexmock(TCPSocket).should_receive(:new).and_raise(error) - assert_raise error do - Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636) + 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" ] ] } + args = { :operations => [[:delete, "mail"]] } result = Net::LDAP::Connection.modify_ops(args[:operations]) - expected = [ "0\r\n\x01\x010\b\x04\x04mail1\x00" ] + 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" ] ] } + 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" ] + 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" ] ] } + 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" ] + expected = ["0#\n\x01\x020\x1E\x04\x04mail1\x16\x04\x14testuser@example.com"] assert_equal(expected, result) end @@ -73,7 +160,7 @@ def make_message(message_id, options = {}) app_tag: Net::LDAP::PDU::SearchResult, code: Net::LDAP::ResultCodeSuccess, matched_dn: "", - error_message: "" + error_message: "", }.merge(options) result = Net::BER::BerIdentifiedArray.new([options[:code], options[:matched_dn], options[:error_message]]) result.ber_identifier = options[:app_tag] @@ -168,7 +255,7 @@ def test_queued_read_rename assert result = conn.rename( olddn: "uid=renamable-user1,ou=People,dc=rubyldap,dc=com", - newrdn: "uid=renamed-user1" + newrdn: "uid=renamed-user1", ) assert result.success? assert_equal 2, result.message_id @@ -202,7 +289,7 @@ def test_queued_read_setup_encryption_with_start_tls 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, {}). + flexmock(Net::LDAP::Connection).should_receive(:wrap_with_ssl).with(mock, {}, nil). and_return(mock) conn.next_msgid # simulates ongoing query @@ -259,7 +346,7 @@ class TestLDAPConnectionErrors < Test::Unit::TestCase def setup @tcp_socket = flexmock(:connection) @tcp_socket.should_receive(:write) - flexmock(TCPSocket).should_receive(:new).and_return(@tcp_socket) + flexmock(Socket).should_receive(:tcp).and_return(@tcp_socket) @connection = Net::LDAP::Connection.new(:host => 'test.mocked.com', :port => 636) end @@ -288,7 +375,7 @@ class TestLDAPConnectionInstrumentation < Test::Unit::TestCase def setup @tcp_socket = flexmock(:connection) @tcp_socket.should_receive(:write) - flexmock(TCPSocket).should_receive(:new).and_return(@tcp_socket) + flexmock(Socket).should_receive(:tcp).and_return(@tcp_socket) @service = MockInstrumentationService.new @connection = Net::LDAP::Connection.new \ @@ -310,8 +397,8 @@ def test_write_net_ldap_connection_event # a write event payload, result = events.pop - assert payload.has_key?(:result) - assert payload.has_key?(:content_length) + assert payload.key?(:result) + assert payload.key?(:content_length) end def test_read_net_ldap_connection_event @@ -327,7 +414,7 @@ def test_read_net_ldap_connection_event # a read event payload, result = events.pop - assert payload.has_key?(:result) + assert payload.key?(:result) assert_equal read_result, result end @@ -344,9 +431,9 @@ def test_parse_pdu_net_ldap_connection_event # a parse_pdu event payload, result = events.pop - assert payload.has_key?(:pdu) - assert payload.has_key?(:app_tag) - assert payload.has_key?(:message_id) + 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] @@ -366,7 +453,7 @@ def test_bind_net_ldap_connection_event # a read event payload, result = events.pop - assert payload.has_key?(:result) + assert payload.key?(:result) assert result.success?, "should be success" end @@ -374,7 +461,7 @@ 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"]] ] + [["uid", ["user1"]]], ]]) search_data_ber.ber_identifier = Net::LDAP::PDU::SearchReturnedData search_data = [1, search_data_ber] @@ -393,8 +480,8 @@ def test_search_net_ldap_connection_event # a search event payload, result = events.pop - assert payload.has_key?(:result) - assert payload.has_key?(:filter) + assert payload.key?(:result) + assert payload.key?(:filter) assert_equal "(uid=user1)", payload[:filter].to_s assert result diff --git a/test/test_ldif.rb b/test/test_ldif.rb index 988c3155..cc1ee2bf 100644 --- a/test/test_ldif.rb +++ b/test/test_ldif.rb @@ -38,45 +38,45 @@ def test_ldif_with_password def test_ldif_with_continuation_lines ds = Net::LDAP::Dataset::read_ldif(StringIO.new("dn: abcdefg\r\n hijklmn\r\n\r\n")) - assert_equal(true, ds.has_key?("abcdefghijklmn")) + 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.has_key?("abcdefg hijklmn")) + 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.has_key?("abcdefghij klmn")) + 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.has_key?("key")) + 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.has_key?("CN=Base64 dn test,OU=Test,OU=Units,DC=example,DC=com")) + 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.has_key?("CN=Base64 dn test with continuation line,OU=Test,OU=Units,DC=example,DC=com")) + 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") {|f| + 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") { |f| f.read } + data = File.open(TestLdifFilename, "rb", &:read) io = StringIO.new(data) # added .lines to turn to array because 1.9 doesn't have @@ -84,13 +84,13 @@ def test_to_ldif entries = data.lines.grep(/^dn:\s*/) { $'.chomp } dn_entries = entries.dup - ds = Net::LDAP::Dataset::read_ldif(io) { |type, value| + 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 diff --git a/test/test_search.rb b/test/test_search.rb index e349d0b8..c577a6a2 100644 --- a/test/test_search.rb +++ b/test/test_search.rb @@ -32,8 +32,8 @@ def test_instrumentation_publishes_event @connection.search(:filter => "test") payload, result = events.pop - assert payload.has_key?(:result) - assert payload.has_key?(:filter) + assert payload.key?(:result) + assert payload.key?(:filter) assert_equal "test", payload[:filter] end end diff --git a/test/test_snmp.rb b/test/test_snmp.rb index fe1ee168..6a809a80 100644 --- a/test/test_snmp.rb +++ b/test/test_snmp.rb @@ -16,9 +16,9 @@ def self.raw_string(s) def test_invalid_packet data = "xxxx" - assert_raise(Net::BER::BerError) { + assert_raise(Net::BER::BerError) do ary = data.read_ber(Net::SNMP::AsnSyntax) - } + end end # The method String#read_ber! added by Net::BER consumes a well-formed BER @@ -40,9 +40,9 @@ def _test_consume_string end def test_weird_packet - assert_raise(Net::SnmpPdu::Error) { + assert_raise(Net::SnmpPdu::Error) do Net::SnmpPdu.parse("aaaaaaaaaaaaaa") - } + end end def test_get_request diff --git a/testserver/ldapserver.rb b/testserver/ldapserver.rb index eba130ce..809f9e7e 100644 --- a/testserver/ldapserver.rb +++ b/testserver/ldapserver.rb @@ -24,7 +24,7 @@ module LdapServer }, :primitive => { 2 => :string, # ldapsearch sends this to unbind - } + }, }, :context_specific => { :primitive => { @@ -34,7 +34,7 @@ module LdapServer :constructed => { 3 => :array # equality filter }, - } + }, } def post_init @@ -119,7 +119,7 @@ def handle_search_request pdu # pdu[1][7] is the list of requested attributes. # If it's an empty array, that means that *all* attributes were requested. requested_attrs = if pdu[1][7].length > 0 - pdu[1][7].map {|a| a.downcase} + pdu[1][7].map(&:downcase) else :all end @@ -133,21 +133,21 @@ def handle_search_request pdu # TODO, what if this returns nil? filter = Net::LDAP::Filter.parse_ldap_filter( filters ) - $ldif.each {|dn, entry| + $ldif.each do |dn, entry| if filter.match( entry ) attrs = [] - entry.each {|k, v| + entry.each do |k, v| if requested_attrs == :all or requested_attrs.include?(k.downcase) - attrvals = v.map {|v1| v1.to_ber}.to_ber_set + 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?" @@ -156,7 +156,7 @@ def handle_search_request pdu 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 ) + send_data( [msgid.to_ber, [code.to_ber, dn.to_ber, text.to_ber].to_ber_appsequence(pkt_tag)].to_ber ) end end @@ -201,10 +201,9 @@ def load_test_data require 'net/ldap' - EventMachine.run { + 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 -