LDAP Object Mapper

Allow to map LDAP object to ruby object.

It is best used with dry-struct and dry-struct-setters libraries

Examples

require 'net/ldap'
require 'lom/ldap'

using LOM::LDAP::Extensions

# Define LDAP handler used by LOM
LH = Net::LDAP.connect('ldap://127.0.0.1')
LH.auth 'uid=root,ou=Admins,dc=example,dc=com', 'foobar'
# Defining mapping between LDAP and ruby using Dry::Struct
#
class User < Dry::Struct
    include Dry::Struct::Setters
    using LOM::LDAP::Extensions

    ADMINS_BRANCH     = 'ou=Admins,dc=example,dc=com'
    TEAMS_BRANCH      = 'ou=Team,dc=example,dc=com'

    #
    # Defining LDAP mapping
    # 
    extend LOM::Mapper

    ldap_branch  "ou=People,dc=example,dc=com"
    ldap_filter  '(objectClass=inetOrgPerson)'
    ldap_attrs   '*', '+'
    ldap_prefix  :uid

    ldap_from   do
        {
            :firstname       => first(:givenName,             String   ),
            :lastname        => first(:sn,                    String   ),
            :email           => first(:mail,                  String   ),
            :homepage        => first(:labeledURI,            String   ),
            :address         => first(:postalAddress,         String   ),
            :title           => first(:title,                 String   ),
            :type            =>   all(:objectClass,           String   )
                                    .map(&:downcase)
                                    .include?('posixaccount') ? :full : :minimal,
            :login           => first(:uid,                   String   ),
            :password        => nil,
            :managers        =>   all(:manager,               String   )
                                    .map {|m| User.ldap_dn_to_id(m) },
            :locked          => first(:pwdAccountLockedTime,  Time     ),
            :uid             => first(:uidNumber,             Integer  ),
            :gid             => first(:gidNumber,             Integer  ),
            :home            => first(:homeDirectory,         String   ),
            :teams           =>   all(:memberOf,              String   ).map{|m|
                    LOM.id_from_dn(m, TEAMS_BRANCH, :cn)
                }.compact,
        }.compact
    end

    ldap_to do
        oclass = [ 'inetOrgPerson' ]
        if type == :full
            oclass += [ 'posixAccount', 'sambaSamAccount', 'pwdPolicy' ]
            { :gecos      => fullname,
              :loginShell => '/bin/bash'
            }
        end

        { :givenName        => firstname,
          :sn               => lastname,
          :cn               => fullname,
          :mail             => email,
          :labeledURI       => homepage,
          :postalAddress    => address,
          :title            => title,
          :uid              => ,
          :manager          => managers.map {|m| User.ldap_dn_from_id(m) },
          :pwdAccountLockedTime => locked,
          :uidNumber        => uid,
          :gidNumber        => gid,
          :homeDirectory    => home.to_s,
        }
    end

    ldap_list    :locked,  ->(predicate=true) do
        Filtered.exists(:pwdAccountLockedTime, predicate: predicate)
    end

    ldap_list   :manager,  ->(manager) do
        Filtered.has(:manager, manager) {|m|
            case m
            when true,  nil   then Filtered::ANY
            when false, :none then Filtered::NONE
            else User.ldap_dn_from_id(m.to_str)
            end
        }
    end


    #
    # Object structure
    #

    transform_keys(&:to_sym)

    attribute  :firstname,       Types::String
    attribute  :lastname,        Types::String
    attribute  :email,           Types::EMail
    attribute? :homepage,        Types::WebPage.optional
    attribute? :address,         Types::String.optional
    attribute  :title,           Types::String
    attribute  :type,            Types::Symbol.enum(:minimal, :full)
    attribute  :login,           Types::Login
    attribute? :password,        Types::Password.optional
    attribute? :managers,        Types::Array.of(Types::Login)
    attribute? :locked,          Types::Time.optional
    attribute? :uid,             Types::Integer
    attribute? :gid,             Types::Integer
    attribute? :home,            Types::Pathname
    attribute  :teams,           Types::Array.of(Types::Team)

    # Various User representation that can be used in processing
    # as string, in sql statement, as JSON
    def to_s            ; self.                       ; end
    def to_str          ; self.                       ; end
    def sql_literal(ds) ; ds.literal(self.)           ; end
    def to_json(*a)     ; self.to_hash.compact.to_json(*a) ; end

    # User full name.
    def fullname
        [ firstname, lastname ].join(' ')
    end
end
# Return user id of users for which account has been locked and 
# with "John Doe" as manager 
User.locked(true).manager('jdoe').list

# Return list of users (as User instance)  without managers
User.manager(false).all