lom 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +152 -0
- data/lib/lom.rb +5 -0
- data/lib/lom/core.rb +49 -0
- data/lib/lom/filtered.rb +171 -0
- data/lib/lom/handler.rb +43 -0
- data/lib/lom/ldap.rb +33 -0
- data/lib/lom/ldap/converters.rb +132 -0
- data/lib/lom/ldap/extensions.rb +257 -0
- data/lib/lom/mapper.rb +296 -0
- data/lib/lom/version.rb +3 -0
- data/lom.gemspec +31 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 383b6cdf0a55888e40558ca5b24f46a7f5e5d033f27fc873e4625c42b385c571
|
4
|
+
data.tar.gz: cef1eef2f7ee9b2b39118e9f54b2d15f1ba5d35518ed04e98cb09a047cbbb34e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dd9eab41714ae145477e2756300b0041dd2d0a12f90485a3997e95266bbb121a33e8a3ed3660d8fd14e7372fd0cfa0fca6da6f800f61599a1c995da221b67b4f
|
7
|
+
data.tar.gz: f435226cb86088cfa48db588b105396b79fc0172eec4a967168b507dffb1039f201f6d7261d371edf63883c7fe519ca0d6167c649259f282a7d38e592ee929e1
|
data/README.md
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
LDAP Object Mapper
|
2
|
+
==================
|
3
|
+
|
4
|
+
Allow to map LDAP object to ruby object.
|
5
|
+
|
6
|
+
It is best used with dry-struct and dry-struct-setters libraries
|
7
|
+
|
8
|
+
|
9
|
+
Examples
|
10
|
+
========
|
11
|
+
|
12
|
+
~~~ruby
|
13
|
+
require 'net/ldap'
|
14
|
+
require 'lom/ldap'
|
15
|
+
|
16
|
+
using LOM::LDAP::Extensions
|
17
|
+
|
18
|
+
# Define LDAP handler used by LOM
|
19
|
+
LH = Net::LDAP.connect('ldap://127.0.0.1')
|
20
|
+
LH.auth 'uid=root,ou=Admins,dc=example,dc=com', 'foobar'
|
21
|
+
~~~
|
22
|
+
|
23
|
+
~~~ruby
|
24
|
+
# Defining mapping between LDAP and ruby using Dry::Struct
|
25
|
+
#
|
26
|
+
class User < Dry::Struct
|
27
|
+
include Dry::Struct::Setters
|
28
|
+
using LOM::LDAP::Extensions
|
29
|
+
|
30
|
+
ADMINS_BRANCH = 'ou=Admins,dc=example,dc=com'
|
31
|
+
TEAMS_BRANCH = 'ou=Team,dc=example,dc=com'
|
32
|
+
|
33
|
+
#
|
34
|
+
# Defining LDAP mapping
|
35
|
+
#
|
36
|
+
extend LOM::Mapper
|
37
|
+
|
38
|
+
ldap_branch "ou=People,dc=example,dc=com"
|
39
|
+
ldap_filter '(objectClass=inetOrgPerson)'
|
40
|
+
ldap_attrs '*', '+'
|
41
|
+
ldap_prefix :uid
|
42
|
+
|
43
|
+
ldap_from do
|
44
|
+
{
|
45
|
+
:firstname => first(:givenName, String ),
|
46
|
+
:lastname => first(:sn, String ),
|
47
|
+
:email => first(:mail, String ),
|
48
|
+
:homepage => first(:labeledURI, String ),
|
49
|
+
:address => first(:postalAddress, String ),
|
50
|
+
:title => first(:title, String ),
|
51
|
+
:type => all(:objectClass, String )
|
52
|
+
.map(&:downcase)
|
53
|
+
.include?('posixaccount') ? :full : :minimal,
|
54
|
+
:login => first(:uid, String ),
|
55
|
+
:password => nil,
|
56
|
+
:managers => all(:manager, String )
|
57
|
+
.map {|m| User.ldap_dn_to_id(m) },
|
58
|
+
:locked => first(:pwdAccountLockedTime, Time ),
|
59
|
+
:uid => first(:uidNumber, Integer ),
|
60
|
+
:gid => first(:gidNumber, Integer ),
|
61
|
+
:home => first(:homeDirectory, String ),
|
62
|
+
:teams => all(:memberOf, String ).map{|m|
|
63
|
+
LOM.id_from_dn(m, TEAMS_BRANCH, :cn)
|
64
|
+
}.compact,
|
65
|
+
}.compact
|
66
|
+
end
|
67
|
+
|
68
|
+
ldap_to do
|
69
|
+
oclass = [ 'inetOrgPerson' ]
|
70
|
+
if type == :full
|
71
|
+
oclass += [ 'posixAccount', 'sambaSamAccount', 'pwdPolicy' ]
|
72
|
+
{ :gecos => fullname,
|
73
|
+
:loginShell => '/bin/bash'
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
{ :givenName => firstname,
|
78
|
+
:sn => lastname,
|
79
|
+
:cn => fullname,
|
80
|
+
:mail => email,
|
81
|
+
:labeledURI => homepage,
|
82
|
+
:postalAddress => address,
|
83
|
+
:title => title,
|
84
|
+
:uid => login,
|
85
|
+
:manager => managers.map {|m| User.ldap_dn_from_id(m) },
|
86
|
+
:pwdAccountLockedTime => locked,
|
87
|
+
:uidNumber => uid,
|
88
|
+
:gidNumber => gid,
|
89
|
+
:homeDirectory => home.to_s,
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
ldap_list :locked, ->(predicate=true) do
|
94
|
+
Filtered.exists(:pwdAccountLockedTime, predicate: predicate)
|
95
|
+
end
|
96
|
+
|
97
|
+
ldap_list :manager, ->(manager) do
|
98
|
+
Filtered.has(:manager, manager) {|m|
|
99
|
+
case m
|
100
|
+
when true, nil then Filtered::ANY
|
101
|
+
when false, :none then Filtered::NONE
|
102
|
+
else User.ldap_dn_from_id(m.to_str)
|
103
|
+
end
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
#
|
109
|
+
# Object structure
|
110
|
+
#
|
111
|
+
|
112
|
+
transform_keys(&:to_sym)
|
113
|
+
|
114
|
+
attribute :firstname, Types::String
|
115
|
+
attribute :lastname, Types::String
|
116
|
+
attribute :email, Types::EMail
|
117
|
+
attribute? :homepage, Types::WebPage.optional
|
118
|
+
attribute? :address, Types::String.optional
|
119
|
+
attribute :title, Types::String
|
120
|
+
attribute :type, Types::Symbol.enum(:minimal, :full)
|
121
|
+
attribute :login, Types::Login
|
122
|
+
attribute? :password, Types::Password.optional
|
123
|
+
attribute? :managers, Types::Array.of(Types::Login)
|
124
|
+
attribute? :locked, Types::Time.optional
|
125
|
+
attribute? :uid, Types::Integer
|
126
|
+
attribute? :gid, Types::Integer
|
127
|
+
attribute? :home, Types::Pathname
|
128
|
+
attribute :teams, Types::Array.of(Types::Team)
|
129
|
+
|
130
|
+
# Various User representation that can be used in processing
|
131
|
+
# as string, in sql statement, as JSON
|
132
|
+
def to_s ; self.login ; end
|
133
|
+
def to_str ; self.login ; end
|
134
|
+
def sql_literal(ds) ; ds.literal(self.login) ; end
|
135
|
+
def to_json(*a) ; self.to_hash.compact.to_json(*a) ; end
|
136
|
+
|
137
|
+
# User full name.
|
138
|
+
def fullname
|
139
|
+
[ firstname, lastname ].join(' ')
|
140
|
+
end
|
141
|
+
end
|
142
|
+
~~~
|
143
|
+
|
144
|
+
|
145
|
+
~~~ruby
|
146
|
+
# Return user id of users for which account has been locked and
|
147
|
+
# with "John Doe" as manager
|
148
|
+
User.locked(true).manager('jdoe').list
|
149
|
+
|
150
|
+
# Return list of users (as User instance) without managers
|
151
|
+
User.manager(false).all
|
152
|
+
~~~
|
data/lib/lom.rb
ADDED
data/lib/lom/core.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative 'version'
|
2
|
+
|
3
|
+
class LOM
|
4
|
+
# Standard Error
|
5
|
+
class Error < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
# Entry not found
|
9
|
+
class EntryNotFound < Error
|
10
|
+
end
|
11
|
+
|
12
|
+
# Mapping error
|
13
|
+
class MappingError < Error
|
14
|
+
end
|
15
|
+
|
16
|
+
# Conversion error
|
17
|
+
class ConvertionError < Error
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# Time format used in ldap
|
22
|
+
TIME_FORMAT = "%Y%m%d%H%M%SZ"
|
23
|
+
|
24
|
+
|
25
|
+
# Convert a Date/Time object to an ldap string representation
|
26
|
+
#
|
27
|
+
# @param [Date, Time] ts
|
28
|
+
#
|
29
|
+
# @return [String] string representation of time in ldap
|
30
|
+
#
|
31
|
+
def self.to_ldap_time(ts)
|
32
|
+
case ts
|
33
|
+
when Date, Time then ts.strftime(TIME_FORMAT)
|
34
|
+
when nil then nil
|
35
|
+
else raise ArgumentError
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get debugging mode
|
40
|
+
def self.debug
|
41
|
+
@@debug ||= []
|
42
|
+
end
|
43
|
+
|
44
|
+
# Set debugging mode
|
45
|
+
# @param [Array<:dry,:verbose>] debugging options
|
46
|
+
def self.debug=(v)
|
47
|
+
@@debug = v
|
48
|
+
end
|
49
|
+
end
|
data/lib/lom/filtered.rb
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'date'
|
2
|
+
require_relative 'ldap/converters'
|
3
|
+
require_relative 'ldap/extensions'
|
4
|
+
|
5
|
+
class LOM
|
6
|
+
|
7
|
+
class Filtered
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
using LDAP::Extensions
|
11
|
+
using LDAP::Converters
|
12
|
+
|
13
|
+
NONE = Object.new.freeze
|
14
|
+
ANY = Object.new.freeze
|
15
|
+
|
16
|
+
def initialize(src, filter = nil, paged: nil)
|
17
|
+
@src = src
|
18
|
+
@filter = filter
|
19
|
+
@paged = paged
|
20
|
+
end
|
21
|
+
attr_reader :src, :filter, :paged
|
22
|
+
|
23
|
+
# Join two filter using a or operation
|
24
|
+
def |(o)
|
25
|
+
_operator_2('|', o)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Join two filter using a and operation
|
29
|
+
def &(o)
|
30
|
+
_operator_2('&', o)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Take the negation of this fileter
|
34
|
+
def ~@
|
35
|
+
_operator_1('!')
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# Ask for paginated data.
|
40
|
+
#
|
41
|
+
# @note That is not supported by net/ldap and is emulated by taking
|
42
|
+
# a slice of the retrieved data. Avoid using.
|
43
|
+
#
|
44
|
+
# @param [Integer] page index (starting from 1)
|
45
|
+
# @param [Integer] page size
|
46
|
+
#
|
47
|
+
# @return [self]
|
48
|
+
def paginate(page, page_size)
|
49
|
+
@paged = [ page, page_size ]
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Iterate over matching data
|
54
|
+
def each(*args, &block)
|
55
|
+
@src.each(*args, filter: @filter, paged: self.paged, &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Retrieve matching data as a list of object
|
59
|
+
#
|
60
|
+
# @return [Array<Object>]
|
61
|
+
#
|
62
|
+
def all
|
63
|
+
each(:object).to_a
|
64
|
+
end
|
65
|
+
|
66
|
+
# Retrieve matching data as a list of id
|
67
|
+
#
|
68
|
+
# @return [Array<String>]
|
69
|
+
#
|
70
|
+
def list
|
71
|
+
each(:id).to_a
|
72
|
+
end
|
73
|
+
|
74
|
+
# Escape (and convert) a value for correct processing.
|
75
|
+
#
|
76
|
+
# Before escaping, the value will be converted to string using
|
77
|
+
# if possible #to_ldap, #to_str, and #to_s in case of symbol
|
78
|
+
#
|
79
|
+
# @param [Object] val value to be escaped
|
80
|
+
#
|
81
|
+
def self.escape(val)
|
82
|
+
val = if val.respond_to?(:to_ldap) then val.to_ldap
|
83
|
+
elsif val.respond_to?(:to_str ) then val.to_str
|
84
|
+
elsif val.kind_of?(Symbol) then val.to_s
|
85
|
+
else raise ArgumentError, 'can\'t convert to string'
|
86
|
+
end
|
87
|
+
Net::LDAP::Filter.escape(val)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Test if an attribute exists
|
91
|
+
def self.exists(attr, predicate: true)
|
92
|
+
case predicate
|
93
|
+
when true, nil then "(#{attr}=*)"
|
94
|
+
when false, :none then "(!(#{attr}=*))"
|
95
|
+
else raise ArgumentError
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Test if an attribute is of the specified value
|
100
|
+
def self.is(attr, val, predicate: true)
|
101
|
+
case predicate
|
102
|
+
when true, nil then "(#{attr}=#{escape(val)})"
|
103
|
+
when false then "(!(#{attr}=#{escape(val)}))"
|
104
|
+
else raise ArgumentError
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Test if an attribute has the specified value.
|
109
|
+
# Using NONE will test for absence, ANY for existence
|
110
|
+
def self.has(attr, val)
|
111
|
+
val = yield(val) if block_given?
|
112
|
+
|
113
|
+
case val
|
114
|
+
when ANY then "(#{attr}=*)"
|
115
|
+
when NONE then "(!(#{attr}=*))"
|
116
|
+
else "(#{attr}=#{escape(val)})"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Test if an attribute as a time before the specified timestamp
|
121
|
+
# If an integer is given it is added to the today date
|
122
|
+
def self.before(attr, ts, predicate: true)
|
123
|
+
ts = Date.today + ts if ts.kind_of?(Integer)
|
124
|
+
ts = LOM.to_ldap_time(ts)
|
125
|
+
"(#{attr}<=#{ts})".then {|f| predicate ? f : "(!#{f})" }
|
126
|
+
end
|
127
|
+
|
128
|
+
# Test if an attribute as a time after the specified timestamp
|
129
|
+
# If an integer is given it is subtracted to the today date
|
130
|
+
def self.after(attr, ts, predicate: true)
|
131
|
+
ts = Date.today - ts if ts.kind_of?(Integer)
|
132
|
+
ts = LOM.to_ldap_time(ts)
|
133
|
+
"(#{attr}>=#{ts})".then {|f| predicate ? f : "(!#{f})" }
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
# Operation with 2 elements
|
139
|
+
def _operator_2(op, o)
|
140
|
+
if @src != o.src
|
141
|
+
raise ArgumentError, 'filter defined with different sources'
|
142
|
+
end
|
143
|
+
_filter = if !@filter.nil? && !o.filter.nil?
|
144
|
+
then Net::LDAP.filter(op, @filter, o.filter)
|
145
|
+
else @filter || o.filter
|
146
|
+
end
|
147
|
+
Filtered.new(@src, _filter, paged: o.paged || self.paged)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Operation with 1 element
|
151
|
+
def _operator_1(op)
|
152
|
+
Filtered.new(@src, Net::LDAP.filter(op, @filter),
|
153
|
+
paged: self.paged)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Check if an ldap_list has been defined with that name
|
157
|
+
def respond_to_missing?(method_name, include_private = false)
|
158
|
+
@src.ldap_listing.include?(method_name) || super
|
159
|
+
end
|
160
|
+
|
161
|
+
# Call the ldap_list defined with that name
|
162
|
+
def method_missing(method_name, *args, &block)
|
163
|
+
if @src.ldap_listing.include?(method_name)
|
164
|
+
self & @src.send(method_name, *args, &block)
|
165
|
+
else
|
166
|
+
super
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
data/lib/lom/handler.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
class LOM
|
4
|
+
using LDAP::Extensions
|
5
|
+
|
6
|
+
|
7
|
+
def self.lh=(lh)
|
8
|
+
@lh = lh
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get the LDAP handler to use
|
12
|
+
#
|
13
|
+
# In order of preference:
|
14
|
+
#
|
15
|
+
# * the handler set using lh=
|
16
|
+
# * the LH constant in this scope or parent scope
|
17
|
+
# * the one defined in $lh
|
18
|
+
#
|
19
|
+
def self.lh
|
20
|
+
@lh || const_get(:LH) || $lh
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# extend Forwardable
|
25
|
+
#
|
26
|
+
# def self.connect(*args)
|
27
|
+
# self.new(Net::LDAP.connect(*args))
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def initialize(lh)
|
31
|
+
# @lh = lh
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# def_delegator :@lh, :search
|
35
|
+
# def_delegator :@lh, :update
|
36
|
+
# def_delegator :@lh, :modify
|
37
|
+
# def_delegator :@lh, :add
|
38
|
+
# def_delegator :@lh, :delete
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
|
data/lib/lom/ldap.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'ldap/converters'
|
2
|
+
require_relative 'ldap/extensions'
|
3
|
+
|
4
|
+
class LOM
|
5
|
+
using LDAP::Extensions
|
6
|
+
|
7
|
+
# Retrieve the identifier.
|
8
|
+
#
|
9
|
+
# The given `dn` should be a direct child of the `branch`,
|
10
|
+
# and if `attr` is specified, the attribute name should also match.
|
11
|
+
#
|
12
|
+
# ~~~
|
13
|
+
# dn = "uid=jdoe,ou=People,dc=example,dc=com"
|
14
|
+
# LOM.id_from_dn(dn, "ou=People,dc=example,dc=com", :uid)
|
15
|
+
# ~~~
|
16
|
+
#
|
17
|
+
# @param [String] dn DN of the object
|
18
|
+
# @param [String] branch Branch the DN should belong
|
19
|
+
# @param [Symbol,String] attr Attribute name
|
20
|
+
#
|
21
|
+
# @return [String] Identifier
|
22
|
+
# @return [nil] Unable to extract identifier
|
23
|
+
#
|
24
|
+
def self.id_from_dn(dn, branch, attr = nil)
|
25
|
+
if sub = Net::LDAP::DN.sub?(dn, branch)
|
26
|
+
k, v, o = sub.to_a
|
27
|
+
if o.nil? && (!attr.nil? || (k == attr.to_s))
|
28
|
+
v
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
require_relative '../core'
|
5
|
+
|
6
|
+
|
7
|
+
module LOM::LDAP
|
8
|
+
module Converters
|
9
|
+
|
10
|
+
#
|
11
|
+
# Integer
|
12
|
+
#
|
13
|
+
|
14
|
+
refine Integer do
|
15
|
+
def to_ldap
|
16
|
+
self.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
refine Integer.singleton_class do
|
21
|
+
def from_ldap(v)
|
22
|
+
Integer(v)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
#
|
29
|
+
# String
|
30
|
+
#
|
31
|
+
|
32
|
+
refine String do
|
33
|
+
def to_ldap
|
34
|
+
self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
refine String.singleton_class do
|
39
|
+
def from_ldap(v)
|
40
|
+
v
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
#
|
47
|
+
# Date / Time
|
48
|
+
#
|
49
|
+
|
50
|
+
refine Date do
|
51
|
+
def to_ldap
|
52
|
+
self.to_time.to_ldap
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
refine Date.singleton_class do
|
57
|
+
def from_ldap(date)
|
58
|
+
return nil if date.nil?
|
59
|
+
Date.parse(date)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
refine Time do
|
64
|
+
def to_ldap
|
65
|
+
self.gmtime.strftime("%Y%m%d%H%M%SZ")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
refine Time.singleton_class do
|
70
|
+
def from_ldap(time)
|
71
|
+
return nil if time.nil?
|
72
|
+
self::gm(time[0,4].to_i, time[4,2].to_i, time[6,2].to_i,
|
73
|
+
time[8,2].to_i, time[10,2].to_i, time[12,2].to_i)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
#
|
80
|
+
# Boolean
|
81
|
+
#
|
82
|
+
|
83
|
+
refine TrueClass do
|
84
|
+
def to_ldap
|
85
|
+
'TRUE'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
refine TrueClass.singleton_class do
|
90
|
+
def from_ldap(v)
|
91
|
+
v == 'TRUE'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
refine FalseClass do
|
96
|
+
def to_ldap
|
97
|
+
'FALSE'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
#
|
104
|
+
# Array / Set
|
105
|
+
#
|
106
|
+
|
107
|
+
refine Set do
|
108
|
+
def to_ldap
|
109
|
+
self.to_a.to_ldap
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
refine Array do
|
114
|
+
def to_ldap
|
115
|
+
self.map { |val|
|
116
|
+
if val.respond_to?(:to_ldap) then val.to_ldap
|
117
|
+
elsif val.respond_to?(:to_str ) then val.to_str
|
118
|
+
elsif val.kind_of?(Symbol) then val.to_s
|
119
|
+
else raise LOM::ConvertionError,
|
120
|
+
"can't convert to string (#{val.class})"
|
121
|
+
end
|
122
|
+
}.tap {|list|
|
123
|
+
if err = list.find {|e| ! e.kind_of?(String) }
|
124
|
+
raise LOM::ConvertionError,
|
125
|
+
"detected a non-string element (#{err.class})"
|
126
|
+
end
|
127
|
+
}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'net/ldap'
|
4
|
+
require 'net/ldap/dn'
|
5
|
+
|
6
|
+
require_relative '../core'
|
7
|
+
|
8
|
+
|
9
|
+
module LOM::LDAP
|
10
|
+
|
11
|
+
# Ensure that the Converters module exists.
|
12
|
+
#
|
13
|
+
# NOTE: If the optional refinements provided by this modules are required,
|
14
|
+
# they need to be defined/loaded before requiring this file
|
15
|
+
# For example: require 'lom/ldap/converters'
|
16
|
+
module Converters
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Provide refinements to ease development with the net/ldap library:
|
21
|
+
#
|
22
|
+
# * Net::LDAP instance can be created from an URI
|
23
|
+
# using Net::LDAP.connect
|
24
|
+
#
|
25
|
+
# * Net::LDAP#search can use symbols for
|
26
|
+
# scope: :base, :one, :sub
|
27
|
+
# deref: :never, :search, :find, :always
|
28
|
+
#
|
29
|
+
# * Net::LDAP#get method allows retrieving the first entry of a DN
|
30
|
+
# (it is just a customized search query)
|
31
|
+
#
|
32
|
+
# * Net::LDAP#update method that try to intelligently update an
|
33
|
+
# LDAP attribute (to be used instead of Net::LDAP#modify)
|
34
|
+
#
|
35
|
+
# * Net::LDAP::Entry has been enhanced to easy casting of retrieved
|
36
|
+
# attributes
|
37
|
+
#
|
38
|
+
# * Net::LDAP::DN.sub? has been added to test if a DN is included
|
39
|
+
# in another, and will return the sub part
|
40
|
+
#
|
41
|
+
# * Net::LDAP::DN.escape and Net::LDAP:Filter.escape have been
|
42
|
+
# redefined to fix some issues
|
43
|
+
#
|
44
|
+
module Extensions
|
45
|
+
refine Net::LDAP.singleton_class do
|
46
|
+
def filter(op, *args)
|
47
|
+
op, check = case op
|
48
|
+
when :or, '|' then [ '|', 1.. ]
|
49
|
+
when :and, '&' then [ '&', 1.. ]
|
50
|
+
when :not, '!' then [ '!', 1 ]
|
51
|
+
when :ge, '>=' then [ '>=', 2 ]
|
52
|
+
when :eq, '=' then [ '=', 2 ]
|
53
|
+
when :le, '<=' then [ '<=', 2 ]
|
54
|
+
else raise ArgumentError, 'Unknown operation'
|
55
|
+
end
|
56
|
+
args = args.compact.map(&:strip).reject(&:empty?).map {|a|
|
57
|
+
if ( a[0] == '(' ) && ( a[-1] == ')' ) then a
|
58
|
+
elsif ( a[0] != '(' ) && ( a[-1] != ')' ) then "(#{a})"
|
59
|
+
else raise ArgumentError, "Bad LDAP filter: #{a}"
|
60
|
+
end
|
61
|
+
}
|
62
|
+
case args.size
|
63
|
+
when 0 then nil
|
64
|
+
when 1 then args[0]
|
65
|
+
else "(#{op}#{args.join})"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def connect(uri=nil, **opts)
|
70
|
+
if uri
|
71
|
+
uri = URI(uri)
|
72
|
+
case uri.scheme
|
73
|
+
when 'ldap' then
|
74
|
+
when 'ldaps' then opts[:encryption] = :simple_tls
|
75
|
+
else raise ArgumentError, "Unsupported protocol #{proto}";
|
76
|
+
end
|
77
|
+
opts[:host] = uri.host
|
78
|
+
opts[:port] = uri.port
|
79
|
+
end
|
80
|
+
self.new(opts)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
refine Net::LDAP do
|
85
|
+
def close
|
86
|
+
end
|
87
|
+
|
88
|
+
def search(args={}, &block)
|
89
|
+
if deref = case args[:deref]
|
90
|
+
when :never then Net::LDAP::DerefAliases_Never
|
91
|
+
when :search then Net::LDAP::DerefAliases_Search
|
92
|
+
when :find then Net::LDAP::DerefAliases_Find
|
93
|
+
when :always then Net::LDAP::DerefAliases_Always
|
94
|
+
end
|
95
|
+
args[:deref] = deref
|
96
|
+
end
|
97
|
+
if scope = case args[:scope]
|
98
|
+
when :base then Net::LDAP::SearchScope_BaseObject
|
99
|
+
when :one then Net::LDAP::SearchScope_SingleLevel
|
100
|
+
when :sub then Net::LDAP::SearchScope_WholeSubtree
|
101
|
+
end
|
102
|
+
args[:scope] = scope
|
103
|
+
end
|
104
|
+
super(args, &block)
|
105
|
+
end
|
106
|
+
|
107
|
+
def get(dn:, attributes: nil, attributes_only: false,
|
108
|
+
return_result: true, time: nil, deref: :never, &block)
|
109
|
+
search(:base => dn,
|
110
|
+
:scope => :base,
|
111
|
+
:attributes => attributes,
|
112
|
+
:attributes_only => attributes_only,
|
113
|
+
:return_result => return_result,
|
114
|
+
:time => time,
|
115
|
+
:deref => deref,
|
116
|
+
&block)
|
117
|
+
.then {|r| return_result ? r&.first : r }
|
118
|
+
end
|
119
|
+
|
120
|
+
# Update an existing dn entry.
|
121
|
+
# The necessary operation (add/modify/replace) will be built
|
122
|
+
# accordingly.
|
123
|
+
#
|
124
|
+
# @note the dn can be specified, either in the dn parameter
|
125
|
+
# or as a key in the attributes parameter
|
126
|
+
#
|
127
|
+
# @param dn
|
128
|
+
# @param attributes
|
129
|
+
#
|
130
|
+
# @return [nil] dn doesn't exist so it can't be updated
|
131
|
+
# @return [Boolean] operation success
|
132
|
+
#
|
133
|
+
# @raise [ArgumentError] if DN missing or incoherent
|
134
|
+
#
|
135
|
+
def update(dn: nil, attributes: {})
|
136
|
+
# Normalize keys
|
137
|
+
attributes = attributes.to_h.dup
|
138
|
+
attributes.transform_keys! {|k| k.downcase.to_sym }
|
139
|
+
attributes.transform_values! {|v| Array(v) }
|
140
|
+
attributes.transform_values! {|v| v.empty? ? nil : v }
|
141
|
+
|
142
|
+
# Sanitize
|
143
|
+
_dn = attributes[:dn]
|
144
|
+
if _dn && _dn.size > 1
|
145
|
+
raise ArgumentError, 'only one DN can be specified'
|
146
|
+
end
|
147
|
+
if dn.nil? && _dn.nil?
|
148
|
+
raise ArgumentError, 'missing DN'
|
149
|
+
elsif dn && _dn && dn != _dn.first
|
150
|
+
raise ArgumentError, 'attribute DN doesn\'t match provided DN'
|
151
|
+
end
|
152
|
+
|
153
|
+
dn ||= _dn.first
|
154
|
+
attributes[:dn] = [ dn ]
|
155
|
+
|
156
|
+
# Retrieve existing attributes
|
157
|
+
# Note: dn is always present in entries
|
158
|
+
entries = get(dn: dn, attributes: attributes.keys)
|
159
|
+
|
160
|
+
# Entry not found
|
161
|
+
return nil if entries.nil?
|
162
|
+
|
163
|
+
# Identify keys
|
164
|
+
changing = attributes.compact.keys
|
165
|
+
removing = attributes.select {|k, v| v.nil? }.keys
|
166
|
+
existing = entries.attribute_names
|
167
|
+
add = changing - existing
|
168
|
+
modify = changing & existing
|
169
|
+
delete = removing & existing
|
170
|
+
|
171
|
+
# Remove key from update if same content
|
172
|
+
modify.reject! {|k| attributes[k] == entries[k] }
|
173
|
+
|
174
|
+
# Build operations
|
175
|
+
# Note: order is delete/modify/add
|
176
|
+
# to avoid "Object Class Violation" due to possible
|
177
|
+
# modification of objectClass
|
178
|
+
ops = []
|
179
|
+
ops += delete.map {|k| [ :delete, k, nil ] }
|
180
|
+
ops += modify.map {|k| [ :replace, k, attributes[k] ] }
|
181
|
+
ops += add .map {|k| [ :add, k, attributes[k] ] }
|
182
|
+
|
183
|
+
# Apply
|
184
|
+
if LOM.debug.include?(:verbose)
|
185
|
+
$stderr.puts "Update: #{dn}"
|
186
|
+
$stderr.puts ops.inspect
|
187
|
+
end
|
188
|
+
if LOM.debug.include?(:dry)
|
189
|
+
return true
|
190
|
+
end
|
191
|
+
return true if op.empty? # That's a no-op
|
192
|
+
modify(:dn => dn, :operations => ops) # Apply modifications
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
refine Net::LDAP::Filter.singleton_class do
|
197
|
+
def escape(str)
|
198
|
+
str.gsub(/([\x00-\x1f*()\\])/) { '\\%02x' % $1[0].ord }
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
refine Net::LDAP::DN.singleton_class do
|
203
|
+
def sub?(dn, prefix)
|
204
|
+
_dn = Net::LDAP::DN.new(dn ).to_a
|
205
|
+
_prefix = Net::LDAP::DN.new(prefix).to_a
|
206
|
+
return nil if _dn.size <= _prefix.size
|
207
|
+
sub = _dn[0 .. - (_prefix.size + 1)]
|
208
|
+
return nil if sub.empty?
|
209
|
+
Net::LDAP::DN.new(*sub)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
refine Net::LDAP::DN.singleton_class do
|
214
|
+
def escape(str)
|
215
|
+
str.gsub(/([\x00-\x1f])/ ) { '\\%02x' % $1[0].ord } \
|
216
|
+
.gsub(/([\\+\"<>;,\#=])/ ) { '\\' + $1 }
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
refine Net::LDAP::Entry do
|
221
|
+
using LOM::LDAP::Converters
|
222
|
+
|
223
|
+
def _cast(val, cnv=nil, &block)
|
224
|
+
if cnv && block
|
225
|
+
raise ArgumentError,
|
226
|
+
'converter can\'t be pass as parameter and as block'
|
227
|
+
elsif block
|
228
|
+
cnv = block
|
229
|
+
end
|
230
|
+
|
231
|
+
case cnv
|
232
|
+
when Method, Proc then cnv.call(val)
|
233
|
+
when Class then cnv.from_ldap(val)
|
234
|
+
when nil then val
|
235
|
+
else raise ArgumentError, "unhandled converter type (#{cnv.class})"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
private :_cast
|
239
|
+
|
240
|
+
def [](name, cnv=nil, &block)
|
241
|
+
values = super(name)
|
242
|
+
if cnv.nil? && block.nil?
|
243
|
+
then values
|
244
|
+
else values.map {|e| _cast(e, cnv, &block) }
|
245
|
+
end
|
246
|
+
end
|
247
|
+
alias :all :[]
|
248
|
+
|
249
|
+
def first(name, cnv=nil, &block)
|
250
|
+
if value = super(name)
|
251
|
+
_cast(value, cnv, &block)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|
data/lib/lom/mapper.rb
ADDED
@@ -0,0 +1,296 @@
|
|
1
|
+
require_relative 'ldap/converters'
|
2
|
+
require_relative 'ldap/extensions'
|
3
|
+
require_relative 'filtered'
|
4
|
+
|
5
|
+
class LOM
|
6
|
+
using LDAP::Extensions
|
7
|
+
using LDAP::Converters
|
8
|
+
|
9
|
+
# This module is to be prepend to Entry instance when processing
|
10
|
+
# block `from_ldap`
|
11
|
+
#
|
12
|
+
# It will allow the use of refined methods #first, #[], #all
|
13
|
+
# without requiring an explicit import of LDAPExt refinement
|
14
|
+
# in the class being mapped.
|
15
|
+
#
|
16
|
+
module EntryEnhanced
|
17
|
+
def first(*args) ; super ; end
|
18
|
+
def [](*args) ; super ; end
|
19
|
+
alias :all :[]
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
# Instance methods to be injected in the class being mapped.
|
25
|
+
#
|
26
|
+
module Mapper
|
27
|
+
module InstanceMethods
|
28
|
+
# LDAP handler
|
29
|
+
def lh
|
30
|
+
self.class.lh
|
31
|
+
end
|
32
|
+
|
33
|
+
# Save object to ldap.
|
34
|
+
#
|
35
|
+
# If object already exists, it will be updated otherwise created.
|
36
|
+
#
|
37
|
+
# @return [true, false]
|
38
|
+
#
|
39
|
+
def save!
|
40
|
+
attrs = instance_exec(self, &self.class._ldap_to)
|
41
|
+
.transform_values {|v|
|
42
|
+
# Don't use Array(), not what you think on
|
43
|
+
# some classes such as Time
|
44
|
+
v = [ ] if v.nil?
|
45
|
+
v = [ v ] unless v.is_a?(Array)
|
46
|
+
v.to_ldap
|
47
|
+
}
|
48
|
+
id, _ = Array(attrs[self.class._ldap_prefix])
|
49
|
+
raise MappingError, 'prefix for dn has multiple values' if _
|
50
|
+
dn = self.class.ldap_dn_from_id(id)
|
51
|
+
|
52
|
+
lh.update(dn: dn, attributes: attrs).then {|res|
|
53
|
+
break res unless res.nil?
|
54
|
+
attrs.reject! {|k, v| Array(v).empty? }
|
55
|
+
lh.add(dn: dn, attributes: attrs)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
# Class methods to be injected in the class being mapped,
|
64
|
+
# and performs initialization thanks to #extend_object
|
65
|
+
#
|
66
|
+
module Mapper
|
67
|
+
def self.extend_object(o)
|
68
|
+
super
|
69
|
+
o.include Mapper::InstanceMethods
|
70
|
+
o.extend Enumerable
|
71
|
+
o.const_set(:Filtered, LOM::Filtered)
|
72
|
+
o.__ldap_init
|
73
|
+
end
|
74
|
+
|
75
|
+
def __ldap_init
|
76
|
+
@__ldap_branch = nil
|
77
|
+
@__ldap_prefix = nil
|
78
|
+
@__ldap_scope = :one
|
79
|
+
@__ldap_filter = nil
|
80
|
+
@__ldap_attrs = nil
|
81
|
+
@__ldap_from = nil
|
82
|
+
@__ldap_to = nil
|
83
|
+
@__ldap_list = []
|
84
|
+
@__ldap_lh = nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get the LDAP handler to use
|
88
|
+
#
|
89
|
+
# In order of preference:
|
90
|
+
#
|
91
|
+
# * the handler set using lh=
|
92
|
+
# * the LH constant in this scope or parent scope
|
93
|
+
# * the one provided by LOM.lh
|
94
|
+
#
|
95
|
+
def lh
|
96
|
+
@__ldap_lh || const_get(:LH) || LOM.lh
|
97
|
+
end
|
98
|
+
|
99
|
+
# Set the LDAP handler to use
|
100
|
+
def lh=(lh)
|
101
|
+
@__ldap_lh = lh
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def ldap_listing
|
106
|
+
@__ldap_list
|
107
|
+
end
|
108
|
+
|
109
|
+
def ldap_list(name, body=nil, &block)
|
110
|
+
if body && block
|
111
|
+
raise ArgumentError
|
112
|
+
elsif body.nil? && block.nil?
|
113
|
+
raise ArgumentError
|
114
|
+
elsif block
|
115
|
+
body = block
|
116
|
+
end
|
117
|
+
|
118
|
+
@__ldap_list << name
|
119
|
+
define_singleton_method(name) do |*args|
|
120
|
+
filter = body.call(*args)
|
121
|
+
LOM::Filtered.new(self, filter)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def ldap_branch(v)
|
127
|
+
@__ldap_branch = v
|
128
|
+
end
|
129
|
+
|
130
|
+
def ldap_prefix(v)
|
131
|
+
@__ldap_prefix = v
|
132
|
+
end
|
133
|
+
|
134
|
+
def ldap_scope(v)
|
135
|
+
@__ldap_scope = v
|
136
|
+
end
|
137
|
+
|
138
|
+
def ldap_filter(v)
|
139
|
+
@__ldap_filter = v[0] == '(' ? v : "(#{v})"
|
140
|
+
end
|
141
|
+
|
142
|
+
def ldap_attrs(*v)
|
143
|
+
@__ldap_attrs = v
|
144
|
+
end
|
145
|
+
|
146
|
+
# @note block will be executed in the Net::LDAP::Entry instance
|
147
|
+
def ldap_from(p=nil, &b)
|
148
|
+
if (! p.nil? ^ b.nil?) || (p && !p.kind_of?(Proc))
|
149
|
+
raise ArgumentError,
|
150
|
+
'one and only one of proc/lamba/block need to be defined'
|
151
|
+
end
|
152
|
+
@__ldap_from = p || b
|
153
|
+
end
|
154
|
+
|
155
|
+
# @note block will be executed in the mapped object instance
|
156
|
+
def ldap_to(p=nil, &b)
|
157
|
+
if (! p.nil? ^ b.nil?) || (p && !p.kind_of?(Proc))
|
158
|
+
raise ArgumentError,
|
159
|
+
'one and only one of proc/lamba/block need to be defined'
|
160
|
+
end
|
161
|
+
@__ldap_to = p || b
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
def ldap_dn_to_id(dn)
|
167
|
+
prefix = _ldap_prefix.to_s
|
168
|
+
branch = _ldap_branch
|
169
|
+
|
170
|
+
if sub = Net::LDAP::DN.sub?(dn, branch)
|
171
|
+
case prefix
|
172
|
+
when String, Symbol
|
173
|
+
k, v, _ = sub.to_a
|
174
|
+
raise ArgumentError, "not a direct child" if _
|
175
|
+
raise ArgumentError, "wrong prefix" if k.casecmp(prefix) != 0
|
176
|
+
v
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def ldap_dn_from_id(id)
|
182
|
+
Net::LDAP::DN.new(_ldap_prefix.to_s, id, _ldap_branch).to_s
|
183
|
+
end
|
184
|
+
|
185
|
+
def _ldap_to_obj(entry)
|
186
|
+
raise EntryNotFound if entry.nil?
|
187
|
+
entry.extend(EntryEnhanced)
|
188
|
+
args = entry.instance_exec(entry, &_ldap_from)
|
189
|
+
args = [ args ] unless args.kind_of?(Array)
|
190
|
+
self.new(*args)
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
def each(type = :object, filter: nil, paged: nil)
|
195
|
+
# Create Enumerator if no block given
|
196
|
+
unless block_given?
|
197
|
+
return enum_for(:each, type, filter: filter, paged: paged)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Merging filters
|
201
|
+
filters = [ filter, _ldap_filter ].compact
|
202
|
+
filter = filters.size == 2 ? "(&#{filters.join})" : filters.first
|
203
|
+
|
204
|
+
# Define attributes/converter according to selected type
|
205
|
+
attributes, converter =
|
206
|
+
case type
|
207
|
+
when :id then [ :dn, ->(e) { ldap_dn_to_id(e.dn) } ]
|
208
|
+
when :object then [ _ldap_attrs, ->(e) { _ldap_to_obj(e) } ]
|
209
|
+
else raise ArgumentError, 'type must be either :object or :id'
|
210
|
+
end
|
211
|
+
|
212
|
+
# Paginate
|
213
|
+
# XXX: pagination is emulated, should be avoided
|
214
|
+
skip, count = if paged
|
215
|
+
page, page_size = paged
|
216
|
+
[ (page - 1) * page_size, page_size ]
|
217
|
+
end
|
218
|
+
|
219
|
+
# Perform search
|
220
|
+
lh.search(:base => _ldap_branch,
|
221
|
+
:filter => filter,
|
222
|
+
:attributes => attributes,
|
223
|
+
:scope => _ldap_scope) {|entry|
|
224
|
+
|
225
|
+
if paged.nil?
|
226
|
+
yield(converter.(entry))
|
227
|
+
elsif skip > 0
|
228
|
+
skip -= 1
|
229
|
+
elsif count <= 0
|
230
|
+
break
|
231
|
+
else
|
232
|
+
count -= 1
|
233
|
+
yield(converter.(entry))
|
234
|
+
end
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
def paginate(page, page_size)
|
239
|
+
LOM::Filtered.new(self, paged: [ page, page_size ])
|
240
|
+
end
|
241
|
+
|
242
|
+
def all
|
243
|
+
each(:object).to_a
|
244
|
+
end
|
245
|
+
|
246
|
+
def list
|
247
|
+
each(:id).to_a
|
248
|
+
end
|
249
|
+
|
250
|
+
def get(name)
|
251
|
+
dn = ldap_dn_from_id(name)
|
252
|
+
attrs = _ldap_attrs
|
253
|
+
entry = lh.get(:dn => dn, :attributes => attrs)
|
254
|
+
|
255
|
+
_ldap_to_obj(entry)
|
256
|
+
end
|
257
|
+
|
258
|
+
def delete!(name)
|
259
|
+
dn = ldap_dn_from_id(name)
|
260
|
+
lh.delete(:dn => dn)
|
261
|
+
end
|
262
|
+
|
263
|
+
alias [] get
|
264
|
+
|
265
|
+
private
|
266
|
+
|
267
|
+
def _ldap_branch
|
268
|
+
@__ldap_branch || (raise MappingError, 'ldap_branch not defined')
|
269
|
+
end
|
270
|
+
|
271
|
+
def _ldap_prefix
|
272
|
+
@__ldap_prefix || (raise MappingError, 'ldap_prefix not defined')
|
273
|
+
end
|
274
|
+
|
275
|
+
def _ldap_scope
|
276
|
+
@__ldap_scope
|
277
|
+
end
|
278
|
+
|
279
|
+
def _ldap_filter
|
280
|
+
@__ldap_filter
|
281
|
+
end
|
282
|
+
|
283
|
+
def _ldap_attrs
|
284
|
+
@__ldap_attrs
|
285
|
+
end
|
286
|
+
|
287
|
+
def _ldap_from
|
288
|
+
@__ldap_from || (raise MappingError, 'ldap_from not defined' )
|
289
|
+
end
|
290
|
+
|
291
|
+
def _ldap_to
|
292
|
+
@__ldap_to || (raise MappingError, 'ldap_to not defined' )
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
end
|
data/lib/lom/version.rb
ADDED
data/lom.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require_relative 'lib/lom/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'lom'
|
7
|
+
s.version = LOM::VERSION
|
8
|
+
s.summary = "LDAP Object Mapper"
|
9
|
+
s.description = <<~EOF
|
10
|
+
|
11
|
+
Ease processing of parameters in Sinatra framework.
|
12
|
+
Integrates well with dry-types, sequel, ...
|
13
|
+
|
14
|
+
Example:
|
15
|
+
want! :user, Dry::Types::String, User
|
16
|
+
want? :expired, Dry::Types::Params::Bool.default(true)
|
17
|
+
EOF
|
18
|
+
|
19
|
+
s.homepage = 'https://gitlab.com/sdalu/lom'
|
20
|
+
s.license = 'MIT'
|
21
|
+
|
22
|
+
s.authors = [ "Stéphane D'Alu" ]
|
23
|
+
s.email = [ 'stephane.dalu@insa-lyon.fr' ]
|
24
|
+
|
25
|
+
s.files = %w[ README.md lom.gemspec ] +
|
26
|
+
Dir['lib/**/*.rb']
|
27
|
+
|
28
|
+
s.add_dependency 'net-ldap'
|
29
|
+
s.add_development_dependency 'yard', '~>0'
|
30
|
+
s.add_development_dependency 'rake', '~>13'
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lom
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stéphane D'Alu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-05-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-ldap
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '13'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '13'
|
55
|
+
description: |2
|
56
|
+
|
57
|
+
Ease processing of parameters in Sinatra framework.
|
58
|
+
Integrates well with dry-types, sequel, ...
|
59
|
+
|
60
|
+
Example:
|
61
|
+
want! :user, Dry::Types::String, User
|
62
|
+
want? :expired, Dry::Types::Params::Bool.default(true)
|
63
|
+
email:
|
64
|
+
- stephane.dalu@insa-lyon.fr
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- README.md
|
70
|
+
- lib/lom.rb
|
71
|
+
- lib/lom/core.rb
|
72
|
+
- lib/lom/filtered.rb
|
73
|
+
- lib/lom/handler.rb
|
74
|
+
- lib/lom/ldap.rb
|
75
|
+
- lib/lom/ldap/converters.rb
|
76
|
+
- lib/lom/ldap/extensions.rb
|
77
|
+
- lib/lom/mapper.rb
|
78
|
+
- lib/lom/version.rb
|
79
|
+
- lom.gemspec
|
80
|
+
homepage: https://gitlab.com/sdalu/lom
|
81
|
+
licenses:
|
82
|
+
- MIT
|
83
|
+
metadata: {}
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubygems_version: 3.0.8
|
100
|
+
signing_key:
|
101
|
+
specification_version: 4
|
102
|
+
summary: LDAP Object Mapper
|
103
|
+
test_files: []
|