Ruby Ruby/LDAP Tutorial
Ruby and LDAP with Net::LDAP
Section titled “Ruby and LDAP with Net::LDAP”LDAP (Lightweight Directory Access Protocol) is a protocol for accessing and maintaining distributed directory information services. Ruby can interact with LDAP servers using the net-ldap gem, which is a pure Ruby LDAP client library.
The net-ldap gem supports common LDAP operations like connect, bind, search, add, modify, and delete entries. It aims to be compatible with RFCs related to LDAP.
net-ldap Installation:
Section titled “net-ldap Installation:”Before using net-ldap, you need to install it. This is typically done using RubyGems:
$ gem install net-ldapEnsure you have a running LDAP server (e.g., OpenLDAP, Active Directory, or a cloud-based LDAP service) for testing the examples. The examples will assume a local OpenLDAP server, but connection details can be easily adjusted.
Establishing an LDAP Connection:
Section titled “Establishing an LDAP Connection:”Connecting to an LDAP server involves creating a Net::LDAP object and then binding to the directory.
Step 1: Create Connection Object
Section titled “Step 1: Create Connection Object”You initialize a Net::LDAP object with server details:
require 'net/ldap'
ldap = Net::LDAP.new host: '127.0.0.1', # Your LDAP server IP or hostname port: 389, # Standard LDAP port (636 for LDAPS) auth: { method: :simple, username: "cn=admin,dc=example,dc=com", # Your admin DN password: "password" # Your admin password }host: The hostname or IP address of the LDAP server.port: The port number for LDAP service (default is 389, or 636 for LDAPS/SSL).auth: A hash specifying the authentication method and credentials.:simpleis common.
This creates an object but doesn’t immediately connect or bind.
Step 2: Binding
Section titled “Step 2: Binding”Binding authenticates your session. net-ldap often performs an implicit bind when an operation is attempted if auth details are provided during initialization. You can also explicitly bind using ldap.bind.
if ldap.bind # Successfully bound to LDAP server puts "Authentication successful!" # ... perform operations ...else # Bind failed puts "Authentication failed!" puts "LDAP Error Code: #{ldap.get_operation_result.code}" puts "LDAP Error Message: #{ldap.get_operation_result.message}"endIt’s good practice to check the result of ldap.bind or any other operation. ldap.get_operation_result provides details about the last operation.
Example: Basic Connection and Bind
Section titled “Example: Basic Connection and Bind”#!/usr/bin/env rubyrequire 'net/ldap'
ldap_config = { host: 'localhost', # Or your LDAP server IP/hostname port: 389, auth: { method: :simple, username: 'cn=admin,dc=example,dc=com', # Replace with your bind DN password: 'admin_password' # Replace with your password }}
ldap = Net::LDAP.new(ldap_config)
if ldap.bind puts "Successfully connected and bound to the LDAP server." # Operations like search, add, modify, delete would go here.else puts "Connection or bind failed." puts "Error code: #{ldap.get_operation_result.code}" puts "Error message: #{ldap.get_operation_result.message}"endAdding an LDAP Entry:
Section titled “Adding an LDAP Entry:”To add an entry, you specify its Distinguished Name (DN) and attributes.
dn = "uid=user1,ou=users,dc=example,dc=com"attributes = { objectclass: ["top", "person", "inetOrgPerson"], cn: "User One", sn: "One", givenName: "User", uid: "user1", mail: "user1@example.com", userPassword: "new_password"}
if ldap.add(dn: dn, attributes: attributes) puts "Entry '#{dn}' added successfully."else puts "Failed to add entry '#{dn}'." puts "Error: #{ldap.get_operation_result.message}"endEnsure the objectclass attributes are correct for your LDAP schema and the entry type you are creating.
Modifying an LDAP Entry:
Section titled “Modifying an LDAP Entry:”To modify an entry, specify the DN and an array of operations (add, delete, replace attribute values).
dn_to_modify = "uid=user1,ou=users,dc=example,dc=com"operations = [ [:replace, :mail, "user.one@example.com"], # Replace existing mail [:add, :telephoneNumber, "+1234567890"] # Add new attribute]
if ldap.modify(dn: dn_to_modify, operations: operations) puts "Entry '#{dn_to_modify}' modified successfully."else puts "Failed to modify entry '#{dn_to_modify}'." puts "Error: #{ldap.get_operation_result.message}"endDeleting an LDAP Entry:
Section titled “Deleting an LDAP Entry:”To delete an entry, provide its DN.
dn_to_delete = "uid=user1,ou=users,dc=example,dc=com"
if ldap.delete(dn: dn_to_delete) puts "Entry '#{dn_to_delete}' deleted successfully."else puts "Failed to delete entry '#{dn_to_delete}'." puts "Error: #{ldap.get_operation_result.message}"endModifying the Distinguished Name (RDN):
Section titled “Modifying the Distinguished Name (RDN):”Modifying the RDN (Relative Distinguished Name) of an entry is done using ldap.rename. This operation might not be supported by all LDAP servers or might have restrictions.
old_dn = "uid=user1,ou=users,dc=example,dc=com"new_rdn = "uid=newuser1"# Optional: new_superior_dn = "ou=new_users,dc=example,dc=com"# delete_old_rdn defaults to true
if ldap.rename(olddn: old_dn, newrdn: new_rdn, delete_attributes: true) # The new DN will be uid=newuser1,ou=users,dc=example,dc=com puts "Entry RDN modified successfully."else puts "Failed to modify RDN." puts "Error: #{ldap.get_operation_result.message}"endPerforming a Search:
Section titled “Performing a Search:”Searching requires a base DN, scope, filter, and optionally, attributes to retrieve.
base_dn = "ou=users,dc=example,dc=com"filter = Net::LDAP::Filter.eq("objectclass", "person") # Find all persons# More complex filter: Net::LDAP::Filter.pres("mail") & Net::LDAP::Filter.eq("sn", "Doe")
attributes_to_fetch = ["cn", "sn", "mail", "uid"]
ldap.search(base: base_dn, filter: filter, attributes: attributes_to_fetch, return_result: false) do |entry| puts "DN: #{entry.dn}" entry.each do |attribute, values| puts " #{attribute}: #{values.join(', ')}" endend
# Check for search errors after the block or if return_result: trueresult = ldap.get_operation_resultif result.code != 0 puts "Search failed: #{result.message}"endbase: The DN to start the search from.filter: An LDAP filter string orNet::LDAP::Filterobject. E.g.,"(uid=user1)"orNet::LDAP::Filter.eq("uid", "user1").attributes: An array of attribute names to return. If empty or nil, typically returns all user attributes.scope: Can beNet::LDAP::SearchScope_BaseObject,Net::LDAP::SearchScope_SingleLevel, orNet::LDAP::SearchScope_WholeSubtree(default).return_result: false(default with block) processes entries in a block. Iftrue,ldap.searchreturns an array of entries.
Handling Errors:
Section titled “Handling Errors:”net-ldap methods usually return true on success or false/nil on failure. After an operation, ldap.get_operation_result provides a Net::LDAP::Result object containing the operation’s code, message, and matched DN.
A common pattern for error checking:
if ldap.some_operation(...) # Successelse op_result = ldap.get_operation_result puts "Operation failed!" puts "Code: #{op_result.code}" puts "Message: #{op_result.message}" # op_result.error_message and op_result.matched_dn might also be usefulendNet::LDAP::Error can be raised for some critical issues, so begin...rescue Net::LDAP::Error => e can also be used for broader error handling.
Further Reading:
Section titled “Further Reading:”For comprehensive details on net-ldap and its features:
net-ldapGitHub repository and documentation: https://github.com/ruby-ldap/net-ldap- Official LDAP RFCs for protocol details.