Skip to content

Commit

Permalink
Add support for ldapwhoami (RFC4532) (now with tests) (#425)
Browse files Browse the repository at this point in the history
* Add support for ldapwhoami (RFC4532)

* Do not break Net::LDAP#modify_password

* Return the extended response data

* Add test for connection.ldapwhoami

* Fix processing password modify responses

Per RFC4511 section 4.12, the responseValue field of an ExtendedResponse object is an optional string.
Per RFC3062 section 2, the response to a passsword modify request is a sequence.
This means the extended response must be parsed.

---------

Co-authored-by: a7b81a9086 <>
Co-authored-by: Kevin McCormack <kevin@mccormack.tech>
  • Loading branch information
zeroSteiner and HarlemSquirrel authored Nov 24, 2024
1 parent 75c0bcb commit a562790
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ Metrics/BlockNesting:
# Offense count: 11
# Configuration parameters: CountComments, CountAsOne.
Metrics/ClassLength:
Max: 443
Max: 451

# Offense count: 20
# Configuration parameters: AllowedMethods, AllowedPatterns.
Expand Down
22 changes: 20 additions & 2 deletions lib/net/ldap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ class Net::LDAP
0 => :array, # RFC-2251 Control and Filter-AND
1 => :array, # SearchFilter-OR
2 => :array, # SearchFilter-NOT
3 => :array, # Seach referral
3 => :array, # Search referral
4 => :array, # unknown use in Microsoft Outlook
5 => :array, # SearchFilter-GE
6 => :array, # SearchFilter-LE
Expand All @@ -325,7 +325,7 @@ class Net::LDAP

universal = {
constructed: {
107 => :array, #ExtendedResponse (PasswdModifyResponseValue)
107 => :string, # ExtendedResponse
},
}

Expand All @@ -341,6 +341,7 @@ class Net::LDAP

StartTlsOid = '1.3.6.1.4.1.1466.20037'
PasswdModifyOid = '1.3.6.1.4.1.4203.1.11.1'
WhoamiOid = '1.3.6.1.4.1.4203.1.11.3'

# https://tools.ietf.org/html/rfc4511#section-4.1.9
# https://tools.ietf.org/html/rfc4511#appendix-A
Expand Down Expand Up @@ -1200,6 +1201,23 @@ def delete_tree(args)
end
end

# Return the authorization identity of the client that issues the
# ldapwhoami request. The method does not support any arguments.
#
# Returns True or False to indicate whether the request was successfull.
# The result is available in the extended status information when calling
# #get_operation_result.
#
# ldap.ldapwhoami
# puts ldap.get_operation_result.extended_response
def ldapwhoami(args = {})
instrument "ldapwhoami.net_ldap", args do |payload|
@result = use_connection(args, &:ldapwhoami)
@result.success? ? @result.extended_response : nil
end
end
alias_method :whoami, :ldapwhoami

# This method is experimental and subject to change. Return the rootDSE
# record from the LDAP server as a Net::LDAP::Entry, or an empty Entry if
# the server doesn't return the record.
Expand Down
16 changes: 16 additions & 0 deletions lib/net/ldap/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,22 @@ def delete(args)
pdu
end

def ldapwhoami
ext_seq = [Net::LDAP::WhoamiOid.to_ber_contextspecific(0)]
request = ext_seq.to_ber_appsequence(Net::LDAP::PDU::ExtendedRequest)

message_id = next_msgid

write(request, nil, message_id)
pdu = queued_read(message_id)

if !pdu || pdu.app_tag != Net::LDAP::PDU::ExtendedResponse
raise Net::LDAP::ResponseMissingOrInvalidError, "response missing or invalid"
end

pdu
end

# Internal: Returns a Socket like object used internally to communicate with
# LDAP server.
#
Expand Down
4 changes: 2 additions & 2 deletions lib/net/ldap/pdu.rb
Original file line number Diff line number Diff line change
Expand Up @@ -194,13 +194,13 @@ def parse_ldap_result(sequence)
# requestValue [1] OCTET STRING OPTIONAL }

def parse_extended_response(sequence)
sequence.length >= 3 or raise Net::LDAP::PDU::Error, "Invalid LDAP result length."
sequence.length.between?(3, 5) 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]
@extended_response = sequence.length == 3 ? nil : sequence.last
end
private :parse_extended_response

Expand Down
24 changes: 21 additions & 3 deletions test/integration/test_password_modify.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
require_relative '../test_helper'

class TestPasswordModifyIntegration < LDAPIntegrationTestCase
# see: https://www.rfc-editor.org/rfc/rfc3062#section-2
PASSWORD_MODIFY_SYNTAX = Net::BER.compile_syntax(
application: {},
universal: {},
context_specific: { primitive: { 0 => :string } },
)

def setup
super
@admin_account = { dn: 'cn=admin,dc=example,dc=org', password: 'admin', method: :simple }
Expand Down Expand Up @@ -49,7 +56,13 @@ def test_password_modify_generate
auth: @auth,
old_password: 'admin')

generated_password = @ldap.get_operation_result.extended_response[0][0]
passwd_modify_response_value = @ldap.get_operation_result.extended_response
seq = Net::BER::BerIdentifiedArray.new
sio = StringIO.new(passwd_modify_response_value)
until (e = sio.read_ber(PASSWORD_MODIFY_SYNTAX)).nil?
seq << e
end
generated_password = seq[0][0]

assert generated_password, 'Should have generated a password'

Expand All @@ -64,8 +77,13 @@ 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]

passwd_modify_response_value = @ldap.get_operation_result.extended_response
seq = Net::BER::BerIdentifiedArray.new
sio = StringIO.new(passwd_modify_response_value)
until (e = sio.read_ber(PASSWORD_MODIFY_SYNTAX)).nil?
seq << e
end
generated_password = seq[0][0]
assert generated_password, 'Should have generated a password'

refute @ldap.bind(username: @dn, password: 'admin', method: :simple),
Expand Down
11 changes: 11 additions & 0 deletions test/test_ldap_connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -574,4 +574,15 @@ def test_search_with_controls
# ensure no unread
assert unread.empty?, "should not have any leftover unread messages"
end

def test_ldapwhoami
ber = Net::BER::BerIdentifiedArray.new([Net::LDAP::ResultCodeSuccess, '', '', 0, 'dn:uid=zerosteiner,ou=users,dc=example,dc=org'])
ber.ber_identifier = Net::LDAP::PDU::ExtendedResponse
response = [1, ber]

@tcp_socket.should_receive(:read_ber).and_return(response)

result = @connection.ldapwhoami
assert result.extended_response == 'dn:uid=zerosteiner,ou=users,dc=example,dc=org'
end
end

0 comments on commit a562790

Please sign in to comment.