ldap

Version, currently 0.9.13 versions

github.com/spider-gazelle/crystal-ldap

a Crystal lang LDAP client

20 stars
0 dependents
License: MIT

Nothing has been indexed for 0.9.1 yet. The tag is recorded, its shard.yml has not been read, so the manifest and dependency list below are empty because they are unknown rather than because they are absent.

Installation

# Add this to your shard.yml
dependencies:
  ldap:
    github: spider-gazelle/crystal-ldap
    version: ~> 0.9.1

Then run:

shards install

shard.yml

No shard.yml has been indexed for 0.9.1. You can read it on the repository.

Dependencies

Unknown: the shard.yml for this version has not been read yet.

README

This README is the one indexed from the repository at its latest ref, not from the tag for this version.

# LDAP Support for Crystal Lang

[![CI](https://github.com/spider-gazelle/crystal-ldap/actions/workflows/ci.yml/badge.svg)](https://github.com/spider-gazelle/crystal-ldap/actions/workflows/ci.yml)

## Installation

Add the dependency to your `shard.yml`:

   ```yaml
   dependencies:
     ldap:
       github: spider-gazelle/crystal-ldap
   ```

## Usage

### Connecting and Binding

Passing a TLS context will upgrade the connection using [start tls](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol#StartTLS)

```crystal
require "ldap"

host = "ldap.forumsys.com"
port = 389
user = "cn=read-only-admin,dc=example,dc=com"
pass = "password"

# Standard LDAP port with unencrypted socket
socket = TCPSocket.new(host, port)

# Providing a context will upgrade to encrypted comms using start tls (official method)
tls = OpenSSL::SSL::Context::Client.new
tls.verify_mode = OpenSSL::SSL::VerifyMode::NONE

# Bind to the server
client = LDAP::Client.new(socket, tls)
client.authenticate(user, pass)

# Can now perform LDAP operations
```

To use the non-standard `LDAPS` (LDAP Secure, commonly known as LDAP over SSL) protocol then pass in a `OpenSSL::SSL::Socket::Client` directly: `LDAP::Client.new(tls_socket)`

```crystal
# LDAPS method
socket = TCPSocket.new(host, port)
tls = OpenSSL::SSL::Context::Client.new
tls.verify_mode = OpenSSL::SSL::VerifyMode::NONE
socket = OpenSSL::SSL::Socket::Client.new(socket, context: tls, sync_close: true, hostname: host)

# Bind to the server
client = LDAP::Client.new(socket)
client.authenticate(user, pass)

# Can now perform LDAP operations
```


### Querying

You can perform search requests

```crystal

# You can use LDAP string filters directly
client.search(base: "dc=example,dc=com", filter: "(|(uid=einstein)(uid=training))")

# There are options to select particular attributes and limit response sizes
filter = LDAP::Request::Filter.equal("objectClass", "person")
client.search(
  base: "dc=example,dc=com",
  filter: filter,
  size: 6,
  attributes: ["hasSubordinates", "objectClass"]
)

# Filters can be combined using standard operations
filter = (
          LDAP::Request::Filter.equal("objectClass", "person") &
          LDAP::Request::Filter.equal("sn", "training")) |
          LDAP::Request::FilterParser.parse("(uid=einstein)"
         )
client.search(base: "dc=example,dc=com", filter: filter)

```

A search returns `Array(LDAP::Entry)`. Each `Entry` exposes its `dn` separately
from its `attributes`; attribute values are stored as raw `Bytes` (LDAP octet
strings are not necessarily UTF-8):

```crystal
entries = client.search(base: "dc=example,dc=com", filter: "(uid=einstein)")

entry = entries.first
entry.dn                  # => "uid=einstein,dc=example,dc=com"
entry["cn"]               # => ["Albert Einstein"]   (values decoded as String)
entry["mail"]?            # => ["einstein@ldap.forumsys.com"] or nil if absent
entry.bytes("objectGUID") # => Array(Bytes)          (raw, for binary attributes)
entry.keys                # => ["objectClass", "cn", "sn", "uid", "mail", ...]
```

A server-side size or time limit raises `LDAP::Client::SearchLimitError`, which
carries the partial `entries` the server returned before stopping:

```crystal
begin
  entries = client.search(base: "dc=example,dc=com")
rescue ex : LDAP::Client::SearchLimitError
  partial = ex.entries # incomplete, but usable
end
```