lom 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|