dm-ldap-adapter 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,10 @@
1
+ version 0.4.3
2
+ =============
3
+
4
+ * test several environments: ruby-1.8.7/1.9.2, jruby-1.5.6/1.6.2 (1.8 and 1.9 where possible), DM-1.0.x/1.1.x, net-ldap and ruby-ldap gem as backend. run the specs against all possible combinations.
5
+
6
+ * improvements with LdapArray properties
7
+
1
8
  version 0.4.2
2
9
  =============
3
10
 
data/README-bundler.md ADDED
@@ -0,0 +1,34 @@
1
+ # no Gemfile.lock on git #
2
+
3
+ there are two references for different DM versions
4
+
5
+ * ~> 1.1.0
6
+
7
+ * ~> 1.0.0
8
+
9
+ with a simple symbolic link you can switch between these two profiles.
10
+
11
+ `$ ln -s Gemfile.lock.1.1.0 Gemfile.lock`
12
+
13
+ for that reason Gemfile.lock in not on github, so the script run-all.sh
14
+
15
+ # run specs #
16
+
17
+ `$ bundle exec spec spec`
18
+
19
+ or for some jruby versions (which needs jruby and ruby-maven gem)
20
+
21
+ `$ rmvn test`
22
+ `$ rmvn test --Djruby.versions=1.5.6,1.6.2 -Djruby.18and19
23
+
24
+ # build gem
25
+
26
+ for the ruby platform
27
+
28
+ `$ gem build dm-ldap-adapter.gemspec`
29
+
30
+ or for java platform (needs jruby and ruby-maven gem)
31
+
32
+ `$ rmvn package`
33
+
34
+ gem will be inside target/dm-ldap-adapter directory.
File without changes
@@ -1,20 +1,32 @@
1
- = dm-ldap-adapter
1
+ # dm-ldap-adapter
2
2
 
3
- *Homepage*: [http://dm-ldap-adapter.rubyforge.org]
3
+ *Homepage*: [http://github.com/mkristian/dm-ldap-adapter](http://github.com/mkristian/dm-ldap-adapter)
4
4
 
5
- *Git*: [http://github.com/mkristian/dm-ldap-adapter]
5
+ *Git*: [git://github.com/mkristian/dm-ldap-adapter.git](git://github.com/mkristian/dm-ldap-adapter.git)
6
6
 
7
7
  *Author*: Kristian Meier
8
8
 
9
9
  *Copyright*: 2008-2009
10
10
 
11
- == DESCRIPTION:
11
+ ## Note on Patches/Pull Requests
12
12
 
13
- === usecase
13
+ * Fork the project.
14
+
15
+ * Make your feature addition or bug fix.
16
+
17
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
18
+
19
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
20
+
21
+ * Send me a pull request.
22
+
23
+ ## DESCRIPTION:
24
+
25
+ ### usecase
14
26
 
15
27
  the usecase for that implementation was using an ldap server for user authentication and authorization. the ldap server is configured to have posixAccounts and posixGroups. on the datamapper side these accounts/groups are modeled with many-to-many relationship. further more the model classes should be in such a way that they can be used with another repository as well, i.e. they carry some ldap related configuration but this is only relevant for the ldap-adapter.
16
28
 
17
- === low level ldap library
29
+ ### low level ldap library
18
30
 
19
31
  the ldap library which does the actual ldap protocol stuff is [http://rubyforge.org/projects/net-ldap] which is the default. the other ldap library is [http://rubyforge.org/projects/ruby-ldap]. just add a facade parameter when setting up DataMapper
20
32
 
@@ -30,7 +42,7 @@ or
30
42
  :facade => :net_ldap,
31
43
  .... })
32
44
 
33
- === setup DataMapper
45
+ ### setup DataMapper
34
46
 
35
47
  DataMapper.setup(:ldap, {
36
48
  :adapter => 'ldap',
@@ -42,19 +54,19 @@ or
42
54
  :password => "behappy"
43
55
  })
44
56
 
45
- === examples
57
+ ### examples
46
58
 
47
59
  see 'example/posix.rb' for user/group setup works with default installation of openldap on ubuntu (just change your password as needed in the code)
48
60
 
49
- == FEATURES/PROBLEMS:
61
+ ## FEATURES/PROBLEMS:
50
62
 
51
63
  * the net-ldap has some issues with not closing the connections when an exception/error got raised, with limit the search result to 126 entries which gets fixed by making consecutives searches and collect the result.
52
64
 
53
65
  * error from the ldap server are only logged and do not raise any exceptions (to be changed in next release) with one exception: when creating a new ldap entry a duplicated entry will raise DataMapper::PersistenceError
54
66
 
55
- == SYNOPSIS:
67
+ ## SYNOPSIS:
56
68
 
57
- === distinguished name (DN) of a model
69
+ ### distinguished name (DN) of a model
58
70
 
59
71
  there are three parts which makes the DN of a model, the base from the ldap conncetion, the `treebase` of the model and `dn_prefix` of an instance.
60
72
 
@@ -69,7 +81,7 @@ with a base `dc=example,dc=com` we get a DN like the user 'admin'
69
81
 
70
82
  uid=admin,ou=people,dc=example,dc=com
71
83
 
72
- === ldap entities are bigger than the model
84
+ ### ldap entities are bigger than the model
73
85
 
74
86
  for example the ldap posixGroup has more attributes than the model class, it needs the `objectclass` attribute set to `posixGroup`.
75
87
 
@@ -84,11 +96,11 @@ for example the ldap posixGroup has more attributes than the model class, it nee
84
96
 
85
97
  so with the help of the `ldap_properties` you can define a block which returns an hash with extra attributes. with such block you can make some calculations if needed, i.e. :homedirectory => "/home/#{login}" for the posixAccount.
86
98
 
87
- === authentication
99
+ ### authentication
88
100
 
89
101
  this uses the underlying bind of a ldap connection. so on any model where you have the `dn_prefix` and the `treebase` configured, you can call the method `authenticate(password)`. this will forward the request to the ldap server.
90
102
 
91
- === queries
103
+ ### queries
92
104
 
93
105
  conditions in ldap depend on the attributes definition in the ldap schema. here is the list of what is working with that ldap adapter side and the usual AND between the conditions:
94
106
 
@@ -110,13 +122,13 @@ and
110
122
 
111
123
  gives the same result when *all* names are `NULL` !!!
112
124
 
113
- === OR conditions
125
+ ### OR conditions
114
126
 
115
127
  or-conditions can be done with :conditions option but only of the form "<property_name> <comparator> <value> [or <property_name> <comparator> <value>]*" where the comparator is one of "=", "like". it can be also combined with extra ANDs like this example
116
128
 
117
129
  Contact.all(:name.like => "A%", :conditions => ["phone like '+49%' or mobile like '+49%'"])
118
130
 
119
- === multiple repositories
131
+ ### multiple repositories
120
132
 
121
133
  most probably you have to work with ldap as one repository and a database as a second repository. for me it worked best to define the `default_repository` for each model in the model itself:
122
134
 
@@ -155,13 +167,13 @@ and to let the ldap resources use the ldap respository it is best to bind it to
155
167
  end
156
168
  end
157
169
 
158
- === transactions
170
+ ### transactions
159
171
 
160
172
  the adapter offers a noop transaction, i.e. you can wrap everything into a transaction but the ldap part has no functionality.
161
173
 
162
174
  *note*: the ldap protocol does not know transactions
163
175
 
164
- === many-to-many associations
176
+ ### many-to-many associations
165
177
 
166
178
  staying with posix example there the groups has a memberuid attribute BUT unlike with relational databases it can have multiple values. to achieve a relationship with these values the underlying adapter needs to know that this specific attribute needs to be handled differently. for this `multivalue_field` comes into play. the ldap adapter clones the model and places the each memberuid in its own clone.
167
179
 
@@ -179,7 +191,7 @@ staying with posix example there the groups has a memberuid attribute BUT unlike
179
191
 
180
192
  end
181
193
 
182
- === ldap attributes with many values
194
+ ### ldap attributes with many values
183
195
 
184
196
  let's say your LDAP has multiple email values for a users then you can define your resource class like that using the type *LdapArray* for such multivalue fields
185
197
 
@@ -197,7 +209,7 @@ let's say your LDAP has multiple email values for a users then you can define yo
197
209
  end
198
210
  end
199
211
 
200
- == REQUIREMENTS:
212
+ ## REQUIREMENTS:
201
213
 
202
214
  * slf4r the logging facade
203
215
  * net-ldap pure ruby ldap library
@@ -205,11 +217,11 @@ let's say your LDAP has multiple email values for a users then you can define yo
205
217
  * logging (optional) if logging via logging is desired
206
218
  * log4r (optional) if logging via log4r is desired
207
219
 
208
- == INSTALL:
220
+ ## INSTALL:
209
221
 
210
222
  * sudo gem install dm-ldap-adapter
211
223
 
212
- == LICENSE:
224
+ ## LICENSE:
213
225
 
214
226
  (The MIT License)
215
227
 
data/example/posix.rb CHANGED
@@ -15,14 +15,10 @@ $LOAD_PATH << Pathname(__FILE__).dirname.parent.expand_path + 'lib'
15
15
  # logger.level = :debug
16
16
  # logger.info "initialized logger . . ."
17
17
 
18
- dummy = true #uncomment this to use dummy, i.e. a database instead of ldap
19
- dummy = false # uncomment this to use ldap
18
+ dummy = false # uncomment this to use ldap, dummy = false uses the database
20
19
  unless dummy
21
20
  require 'ldap_resource'
22
21
 
23
- # comment this out if you want to use "net/ldap"
24
- require 'ldap/ruby_ldap_facade'
25
-
26
22
  require 'adapters/ldap_adapter'
27
23
 
28
24
  DataMapper.setup(:default, {
@@ -82,8 +82,10 @@ module DataMapper
82
82
  end
83
83
 
84
84
  def sort_records_case_insensitive(records)
85
+ #Return unsorted records unless we have order defined
86
+ return records unless order
85
87
  sort_order = order.map { |direction| [ direction.target, direction.operator == :asc ] }
86
-
88
+
87
89
  records.sort_by do |record|
88
90
  sort_order.map do |(property, ascending)|
89
91
  SortCaseInsensitive.new(record_value(record, property), ascending)
@@ -171,9 +173,7 @@ module DataMapper
171
173
 
172
174
  def create(resources)
173
175
  resources.select do |resource|
174
-
175
176
  create_resource(resource)
176
-
177
177
  end.size # just return the number of create resources
178
178
  end
179
179
 
@@ -306,6 +306,7 @@ module DataMapper
306
306
  # @see AbstractAdapter#read
307
307
  def read(query)
308
308
  result = []
309
+ #get data values out of the query
309
310
  resources = read_resources(query)
310
311
  resources.each do |resource|
311
312
  map = {}
@@ -325,8 +326,10 @@ module DataMapper
325
326
  end
326
327
 
327
328
  def read_resources(query)
328
- order_by = query.order.first.target.field
329
- order_by_sym = order_by.to_sym
329
+ query_order = query.order.first.target.field if query.order
330
+ #Use empty string as default for order in query
331
+ order_by = query_order || ''
332
+
330
333
  field_names = query.fields.collect {|f| f.field }
331
334
  result = ldap.read_objects(query.model.treebase,
332
335
  query.model.key.collect { |k| k.field },
@@ -0,0 +1 @@
1
+ require 'adapter/ldap-adapter'
data/lib/ldap/array.rb CHANGED
@@ -74,32 +74,49 @@ module Ldap
74
74
  end
75
75
  end
76
76
 
77
- def initialize(*args)
77
+ # keep the *args so it works for both DM-1.1.x and DM-1.0.x
78
+ def initialize(_model = nil, _name = nil, options = {}, *args)
78
79
  super
80
+
81
+ add_writer(model,name) unless options[:writer] == :private || options[:accessor] == :private
82
+ add_reader(model,name) unless options[:reader] == :private || options[:accessor] == :private
83
+ end
84
+
85
+ private
86
+
87
+ def add_reader(model, name)
88
+ #Creates instance method for reader
79
89
  model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
80
- def #{name.to_s}=(v)
81
- case v
82
- when Ldap::Array
83
- v.setup(self, properties[:#{name}])
84
- else
85
- vv = Ldap::Array.new(self, properties[:#{name}])
86
- vv.replace(v || [])
87
- v = vv
88
- end
89
- attribute_set(:#{name}, v)
90
+ def #{name}
91
+ attr_data = attribute_get(:#{name})
92
+
93
+ case attr_data
94
+ when Ldap::Array
95
+ attr_data.setup(self, properties[:#{name}])
96
+ else
97
+ new_ldap_array = Ldap::Array.new(self, properties[:#{name}])
98
+ new_ldap_array.replace(attr_data || [])
99
+ end
90
100
  end
101
+ RUBY
102
+ end
91
103
 
92
- def #{name.to_s}
93
- v = attribute_get(:#{name})
94
- case v
104
+ def add_writer(model, name)
105
+ #Creates instance method for writer
106
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
107
+ def #{name}=(input)
108
+ data = case input
95
109
  when Ldap::Array
96
- v.setup(self, properties[:#{name}])
110
+ input.setup(self, properties[:#{name}])
97
111
  else
98
- vv = Ldap::Array.new(self, properties[:#{name}])
99
- vv.replace(v || [])
112
+ new_ldap_array = Ldap::Array.new(self, properties[:#{name}])
113
+ new_ldap_array.replace(input || [])
100
114
  end
115
+
116
+ attribute_set(:#{name}, data)
101
117
  end
102
118
  RUBY
103
119
  end
120
+
104
121
  end
105
122
  end
data/lib/ldap/digest.rb CHANGED
@@ -1,4 +1,11 @@
1
- require 'sha1'
1
+ begin
2
+ require 'sha1'
3
+ rescue LoadError
4
+ # ruby1.9.x
5
+ require 'digest/sha1'
6
+ SHA1 = Digest::SHA1
7
+ end
8
+
2
9
  require 'base64'
3
10
  module Ldap
4
11
  class Digest
@@ -83,12 +83,18 @@ module Ldap
83
83
  # @param Array of conditions for the search
84
84
  # @return Array of Hashes with a name/values pair for each attribute
85
85
  def read_objects(treebase, key_fields, conditions, field_names, order_field = '')
86
- filter = Conditions2Filter.convert(conditions)
86
+
87
+ if !conditions.nil? and conditions.size > 0
88
+ filter = Conditions2Filter.convert(conditions).to_s
89
+ else
90
+ filter = "(objectclass=*)"
91
+ end
92
+
87
93
  result = []
88
94
  begin
89
95
  @ldap2.search("#{treebase},#{@ldap2.base}",
90
96
  LDAP::LDAP_SCOPE_SUBTREE,
91
- filter.to_s == "" ? "(objectclass=*)" : filter.to_s.gsub(/\(\(/, "(").gsub(/\)\)/, ")"),
97
+ filter,
92
98
  field_names, false, 0, 0, order_field) do |res|
93
99
  mapp = to_map(field_names, res)
94
100
  # TODO maybe make filter which removes this unless
@@ -148,15 +154,21 @@ module Ldap
148
154
  # @param dn String for identifying the ldap object
149
155
  # @param password String to be used for authenticate to the dn
150
156
  def authenticate(dn, password)
151
- Net::LDAP.new( { :host => @ldap2.host,
152
- :port => @ldap2.port,
153
- :auth => {
154
- :method => :simple,
155
- :username => dn,
156
- :password => password
157
- },
158
- :base => @ldap2.base
159
- } ).bind
157
+ bound = false
158
+ ldap_con = LDAP::Conn.new(@ldap2.host, @ldap2.port)
159
+ ldap_con.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 )
160
+ begin
161
+ ldap_con.bind(dn, password, LDAP::LDAP_AUTH_SIMPLE) do
162
+ bound = true
163
+ end
164
+ rescue LDAP::ResultError => msg
165
+ if msg.to_s =~ /Invalid\ credentials/i
166
+ logger.info("Invalid Credentials: #{dn}")
167
+ else
168
+ logger.warn "Authentication Error: #{msg.to_s}"
169
+ end
170
+ end
171
+ bound
160
172
  end
161
173
 
162
174
  # helper to concat the dn from the various parts
@@ -0,0 +1,2 @@
1
+ require 'dm-transactions'
2
+ require 'adapters/noop_transaction'
@@ -0,0 +1,188 @@
1
+ require 'ldap'
2
+ require 'slf4r'
3
+ require 'ldap/conditions_2_filter'
4
+
5
+ module Ldap
6
+ # class Connection < LDAP::Conn
7
+
8
+ # attr_reader :base, :host, :port
9
+
10
+ # def initialize(config)
11
+ # super(config[:host], config[:port])
12
+ # @base = config[:base]
13
+ # @port = config[:port]
14
+ # @host = config[:host]
15
+ # set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
16
+ # end
17
+
18
+ # end
19
+
20
+ class RubyLdapFacade
21
+
22
+ # @param config Hash for the ldap connection
23
+ def self.open(config)
24
+ ldap3 = com.unboundid.ldap.sdk.LDAPConnection.new(config[:host], config[:port], config[:base] + "." + config[:auth][:username], config[:auth][:password])
25
+
26
+ yield ldap
27
+ end
28
+
29
+ include ::Slf4r::Logger
30
+
31
+ # @param config Hash for the ldap connection
32
+ def initialize(config)
33
+ if config.is_a? Hash
34
+ @ldap2 = Connection.new(config)
35
+ @ldap2.bind(config[:auth][:username], config[:auth][:password])
36
+ else
37
+ @ldap2 = config
38
+ end
39
+ end
40
+
41
+ def retrieve_next_id(treebase, key_field)
42
+ max = 0
43
+ @ldap2.search("#{treebase},#{@ldap2.base}",
44
+ LDAP::LDAP_SCOPE_SUBTREE,
45
+ "(objectclass=*)",
46
+ [key_field]) do |entry|
47
+ n = (entry.vals(key_field) || [0]).first.to_i
48
+ max = n if max < n
49
+ end
50
+ max + 1
51
+ end
52
+
53
+ # @param dn_prefix String the prefix of the dn
54
+ # @param treebase the treebase of the dn or any search
55
+ # @param key_field field which carries the integer unique id of the entity
56
+ # @param props Hash of the ldap attributes of the new ldap object
57
+ # @return nil in case of an error or the new id of the created object
58
+ def create_object(dn_prefix, treebase, key_field, props, silence = false)
59
+ base = "#{treebase},#{@ldap2.base}"
60
+ mods = props.collect do |k,v|
61
+ LDAP.mod(LDAP::LDAP_MOD_ADD, k.to_s, v.is_a?(::Array) ? v : [v.to_s] )
62
+ end
63
+ if @ldap2.add( dn(dn_prefix, treebase), mods)
64
+ # :attributes => props) and @ldap.get_operation_result.code.to_s == "0"
65
+ props[key_field.downcase.to_sym]
66
+ else
67
+ unless silence
68
+ msg = ldap_error("create",
69
+ dn(dn_prefix, treebase)) + "\n\t#{props.inspect}"
70
+ # TODO maybe raise always an error
71
+ if @ldap2.get_operation_result.code.to_s == "68"
72
+ raise ::DataMapper::PersistenceError.new(msg)
73
+ else
74
+ logger.warn(msg)
75
+ end
76
+ end
77
+ nil
78
+ end
79
+ end
80
+
81
+ # @param treebase the treebase of the search
82
+ # @param key_fields Array of fields which carries the integer unique id(s) of the entity
83
+ # @param Array of conditions for the search
84
+ # @return Array of Hashes with a name/values pair for each attribute
85
+ def read_objects(treebase, key_fields, conditions, field_names, order_field = '')
86
+ filter = Conditions2Filter.convert(conditions)
87
+ result = []
88
+ begin
89
+ @ldap2.search("#{treebase},#{@ldap2.base}",
90
+ LDAP::LDAP_SCOPE_SUBTREE,
91
+ filter.to_s == "" ? "(objectclass=*)" : filter.to_s.gsub(/\(\(/, "(").gsub(/\)\)/, ")"),
92
+ field_names, false, 0, 0, order_field) do |res|
93
+
94
+ map = to_map(res)
95
+ #puts map[key_field.to_sym]
96
+ # TODO maybe make filter which removes this unless
97
+ # TODO move this into the ldap_Adapter to make it more general, so that
98
+ # all field with Integer gets converted, etc
99
+ result << map if key_fields.detect do |key_field|
100
+ map.member? key_field.to_sym
101
+ end
102
+ end
103
+ end
104
+ result
105
+ end
106
+
107
+
108
+ # @param dn_prefix String the prefix of the dn
109
+ # @param treebase the treebase of the dn or any search
110
+ # @param actions the add/replace/delete actions on the attributes
111
+ # @return nil in case of an error or true
112
+ def update_object(dn_prefix, treebase, actions)
113
+ mods = actions.collect do |act|
114
+ mod_op = case act[0]
115
+ when :add
116
+ LDAP::LDAP_MOD_ADD
117
+ when :replace
118
+ LDAP::LDAP_MOD_REPLACE
119
+ when :delete
120
+ LDAP::LDAP_MOD_DELETE
121
+ end
122
+ LDAP.mod(mod_op, act[1].to_s, act[2] == [] ? [] : [act[2].to_s])
123
+ end
124
+ if @ldap2.modify( dn(dn_prefix, treebase),
125
+ mods )
126
+ true
127
+ else
128
+ logger.warn(ldap_error("update",
129
+ dn(dn_prefix, treebase) + "\n\t#{actions.inspect}"))
130
+ nil
131
+ end
132
+ end
133
+
134
+ # @param dn_prefix String the prefix of the dn
135
+ # @param treebase the treebase of the dn or any search
136
+ # @return nil in case of an error or true
137
+ def delete_object(dn_prefix, treebase)
138
+ if @ldap2.delete( dn(dn_prefix, treebase) )
139
+ true
140
+ else
141
+ logger.warn(ldap_error("delete",
142
+ dn(dn_prefix, treebase)))
143
+
144
+ nil
145
+ end
146
+ end
147
+
148
+
149
+ # @param dn String for identifying the ldap object
150
+ # @param password String to be used for authenticate to the dn
151
+ def authenticate(dn, password)
152
+ Net::LDAP.new( { :host => @ldap2.host,
153
+ :port => @ldap2.port,
154
+ :auth => {
155
+ :method => :simple,
156
+ :username => dn,
157
+ :password => password
158
+ },
159
+ :base => @ldap2.base
160
+ } ).bind
161
+ end
162
+
163
+ # helper to concat the dn from the various parts
164
+ # @param dn_prefix String the prefix of the dn
165
+ # @param treebase the treebase of the dn or any search
166
+ # @return the complete dn String
167
+ def dn(dn_prefix, treebase)
168
+ "#{dn_prefix},#{treebase},#{@ldap2.base}"
169
+ end
170
+
171
+ private
172
+
173
+ # helper to extract the Hash from the ldap search result
174
+ # @param Entry from the ldap_search
175
+ # @return Hash with name/value pairs of the entry
176
+ def to_map(entry)
177
+ map = {}
178
+ LDAP::entry2hash(entry).each do |k,v|
179
+ map[k.downcase.to_sym] = v
180
+ end
181
+ map
182
+ end
183
+
184
+ def ldap_error(method, dn)
185
+ "#{method} error: (#{@ldap2.get_operation_result.code}) #{@ldap2.get_operation_result.message}\n\tDN: #{dn}"
186
+ end
187
+ end
188
+ end
data/lib/ldap_resource.rb CHANGED
@@ -12,7 +12,8 @@ module DataMapper
12
12
  discriminator = properties(repository_name).discriminator
13
13
  no_reload = !query.reload?
14
14
 
15
- field_map = fields.map { |property| [ property, property.field ] }.to_hash
15
+ field_map = {}
16
+ fields.each { |property| field_map[property] = property.field }
16
17
 
17
18
  records.map do |record|
18
19
  identity_map = nil
@@ -4,18 +4,42 @@ require 'spec_helper'
4
4
  describe DataMapper::Adapters::LdapAdapter do
5
5
 
6
6
  before do
7
-
8
7
  DataMapper.repository(:ldap) do
9
8
  User.all(:login.like => "b%").destroy!
10
9
  Group.all(:name.like => "test_%").destroy!
11
- @user1 = User.create(:login => "black", :name => 'Black', :age => 0)
12
- @user2 = User.create(:login => "brown", :name => 'Brown', :age => 25)
13
- @user3 = User.create(:login => "blue", :name => 'Blue', :age => nil)
10
+
11
+ #First we create some items.
12
+ user1 = User.create(:login => "black", :name => 'Black', :age => 0)
13
+ user2 = User.create(:login => "brown", :name => 'Brown', :age => 25)
14
+ user3 = User.create(:login => "blue", :name => 'Blue', :age => nil)
14
15
 
15
- @group1 = Group.create(:name => "test_root_group")
16
- @group2 = Group.create(:name => "test_admin_group")
16
+ group1 = Group.create(:name => "test_root_group")
17
+ group2 = Group.create(:name => "test_admin_group")
18
+
19
+ #Then we retrive the items we created earlier and use them for tests.
20
+ @user1 = User.get!(user1.id)
21
+ @user2 = User.get!(user2.id)
22
+ @user3 = User.get!(user3.id)
23
+
24
+ @group1 = Group.get!(group1.id)
25
+ @group2 = Group.get!(group2.id)
26
+ end
27
+ end
28
+
29
+ after(:all) do
30
+ DataMapper.repository(:ldap) do
31
+ User.all(:login.like => "b%").destroy!
32
+ Group.all(:name.like => "test_%").destroy!
17
33
  end
18
34
  end
35
+
36
+ it 'should have valid testing data' do
37
+ @user1.should be_a_kind_of(User)
38
+ @user2.should be_a_kind_of(User)
39
+ @user3.should be_a_kind_of(User)
40
+ @group1.should be_a_kind_of(Group)
41
+ @group2.should be_a_kind_of(Group)
42
+ end
19
43
 
20
44
  it 'should successfully save an object' do
21
45
  DataMapper.repository(:ldap) do
@@ -129,12 +153,16 @@ describe DataMapper::Adapters::LdapAdapter do
129
153
 
130
154
  it 'should be able to delete a user from a group' do
131
155
  DataMapper.repository(:ldap) do
132
- size = GroupUser.all.size
133
- @user1 = User.get!(@user1.id)
156
+ size_before = GroupUser.all.size
157
+
134
158
  @user1.groups << @group1
159
+ GroupUser.all.size.should == size_before+1
160
+
135
161
  @user1.groups << @group2
162
+ GroupUser.all.size.should == size_before+2
163
+
136
164
  @user2.groups << @group1
137
- GroupUser.all.size.should == size + 3
165
+ GroupUser.all.size.should == size_before+3
138
166
  end
139
167
  DataMapper.repository(:ldap) do
140
168
  @user1 = User.get!(@user1.id)
data/spec/contact.rb ADDED
@@ -0,0 +1,58 @@
1
+ class Contact
2
+ include DataMapper::Resource
3
+
4
+ def self.auto_upgrade!(args = nil)
5
+ DataMapper.logger.warn("Skipping #{self.name}.auto_upgrade!")
6
+ end
7
+
8
+ def self.default_repository_name
9
+ :ldap
10
+ end
11
+
12
+ def self.repository_name
13
+ :ldap
14
+ end
15
+
16
+ property :id, Serial, :field => 'uid'
17
+ property :cn, String, :required => true
18
+ property :salutation, String, :lazy => [:view]
19
+ property :title, String, :lazy => [:view]
20
+ property :givenname, String
21
+ property :sn, String, :required => true
22
+ property :o, String
23
+ property :postaladdress, String, :lazy => [:view]
24
+ property :postalcode, String, :lazy => [:view]
25
+ property :l, String
26
+ property :st, String, :lazy => [:view]
27
+ property :c, String, :lazy => [:view]
28
+ property :telephonenumber, String
29
+ property :facsimiletelephonenumber, String, :lazy => [:view]
30
+ property :pager, String, :lazy => [:view]
31
+ property :jpegphoto, LdapArray, :lazy => true
32
+ property :mobile, String, :lazy => [:view]
33
+ property :anniversary, String, :lazy => [:view]
34
+ property :mail, LdapArray
35
+ property :labeleduri, LdapArray, :lazy => [:view]
36
+ property :marker, LdapArray, :lazy => [:view]
37
+ property :description, LdapArray, :lazy => [:view]
38
+
39
+ dn_prefix do |u|
40
+ "uid=#{u.id}"
41
+ end
42
+
43
+ ldap_properties do |u|
44
+ properties = { :objectclass => ['inetOrgPerson']}#, "posixAccount", "shadowAccount"]}#'contactPerson'] }
45
+ properties
46
+ end
47
+
48
+ treebase 'ou=people'
49
+
50
+ before :save, :fix_object
51
+
52
+ private
53
+
54
+ def fix_object
55
+ self.cn = "#{self.givenname} #{self.sn}".strip
56
+ end
57
+
58
+ end
@@ -0,0 +1,119 @@
1
+ $LOAD_PATH << Pathname(__FILE__).dirname.parent.expand_path + 'lib'
2
+
3
+ require 'ldap/array'
4
+ require 'dm-migrations'
5
+ require 'dm-sqlite-adapter'
6
+
7
+ class A
8
+
9
+ include DataMapper::Resource
10
+
11
+ property :id, Serial
12
+ property :list, ::Ldap::LdapArray, :accessor => :public
13
+ property :hidden_list, ::Ldap::LdapArray, :accessor => :private
14
+ property :write_list, ::Ldap::LdapArray, :reader => :private, :writer => :public
15
+ property :read_list, ::Ldap::LdapArray, :reader => :public, :writer => :private
16
+ end
17
+
18
+ require 'fileutils'
19
+ FileUtils.mkdir_p("target")
20
+ DataMapper.setup(:default, 'sqlite3:target/test.sqlite3')
21
+ DataMapper.finalize
22
+ DataMapper.auto_migrate!(:default)
23
+
24
+ describe Ldap::LdapArray do
25
+ before { @resource = A.new }
26
+
27
+ it 'should create new with array' do
28
+ @resource.list = ["1", "2"]
29
+ @resource.dirty?.should be_true
30
+ @resource.save
31
+ resource = A.first(:id => @resource.id)
32
+ resource.list.should == ["1", "2"]
33
+ resource.list.class.should == Ldap::Array
34
+ end
35
+
36
+ it 'should have empty array on new resource' do
37
+ @resource.list << "1"
38
+ @resource.dirty?.should be_true
39
+ @resource.save
40
+ resource = A.first(:id => @resource.id)
41
+ resource.list.should == ["1"]
42
+ resource.list.class.should == Ldap::Array
43
+ end
44
+
45
+ it 'should save after adding an element' do
46
+ @resource.list = ["1", "2"]
47
+ @resource.save
48
+ @resource.list << "4"
49
+ @resource.save
50
+ resource = A.first(:id => @resource.id)
51
+ resource.list.should == ["1", "2", "4"]
52
+ resource.list.class.should == Ldap::Array
53
+ end
54
+
55
+ it 'should save after changing an element' do
56
+ @resource.list = ["1", "2"]
57
+ @resource.save
58
+ @resource.list[1] = "4"
59
+ @resource.save
60
+ resource = A.first(:id => @resource.id)
61
+ resource.list.should == ["1", "4"]
62
+ resource.list.class.should == Ldap::Array
63
+ end
64
+
65
+ it 'should save after deleting element from list' do
66
+ @resource.list = ["1", "2"]
67
+ @resource.save
68
+ @resource.list.delete("1")
69
+ @resource.save
70
+ resource = A.first(:id => @resource.id)
71
+ resource.list.should == ["2"]
72
+ resource.list.class.should == Ldap::Array
73
+ end
74
+
75
+ context 'when :accessor property is set to :private' do
76
+ it 'should not create a write accessor' do
77
+ @resource.should_not respond_to(:hidden_list=)
78
+ end
79
+
80
+ it 'should not create a reade accessor' do
81
+ @resource.should_not respond_to(:hidden_list)
82
+ end
83
+ end
84
+
85
+ context 'when :accessor property is set to :public' do
86
+ it 'should create a write accessor' do
87
+ @resource.should respond_to(:list=)
88
+ end
89
+
90
+ it 'should create a reade accessor' do
91
+ @resource.should respond_to(:list)
92
+ end
93
+ end
94
+
95
+ context 'when :writer property is set to :public' do
96
+ it 'should create a write accessor' do
97
+ @resource.should respond_to(:write_list=)
98
+ end
99
+ end
100
+
101
+ context 'when :writer property is set to :private' do
102
+ it 'should not create a write accessor' do
103
+ @resource.should_not respond_to(:read_list=)
104
+ end
105
+ end
106
+
107
+ context 'when :reader property is set to :public' do
108
+ it 'should create a read accessor' do
109
+ @resource.should respond_to(:read_list)
110
+ end
111
+ end
112
+
113
+ context 'when :reader property is set to :private' do
114
+ it 'should not create a read accessor' do
115
+ @resource.should_not respond_to(:write_list)
116
+ end
117
+ end
118
+
119
+ end
@@ -151,7 +151,6 @@ describe DataMapper.repository(:ldap).adapter.class do
151
151
  end
152
152
 
153
153
  it 'should be able to use multilines with LdapArray' do
154
- pending "not working for net-ldap" if DataMapper.repository(:ldap).adapter.ldap.class.to_s == 'Ldap::NetLdapFacade'
155
154
  DataMapper.repository(:ldap) do
156
155
  @contact.mail = ["email1\nmail2\nmail2\nmail4", "email1"]
157
156
  @contact.save
data/spec/spec_helper.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
 
3
3
  require 'slf4r/ruby_logger'
4
- Slf4r::LoggerFacade4RubyLogger.level = :info
4
+ Slf4r::LoggerFacade4RubyLogger.level = :warn
5
5
 
6
6
  require 'dm-sqlite-adapter'
7
7
  require 'dm-migrations'
@@ -26,6 +26,8 @@ DataMapper.setup(:ldap, {
26
26
  :password => "behappy"
27
27
  })
28
28
 
29
+ puts "using facade #{(ENV['FACADE'] || :net_ldap).to_sym}"
30
+
29
31
  module DataMapper
30
32
  module Resource
31
33
  class State
@@ -55,25 +57,30 @@ class User
55
57
  has n, :group_users
56
58
 
57
59
  def groups
58
- groups = GroupUser.all(:user_id => id).collect{ |gu| gu.group }
60
+ groups = GroupUser.all(:user_id => login).collect{ |gu| gu.group }
61
+
59
62
  def groups.user=(user)
60
63
  @user = user
61
64
  end
65
+
62
66
  groups.user = self
67
+
63
68
  def groups.<<(group)
64
69
  unless member? group
65
- GroupUser.create(:user_id => @user.id, :group_id => group.id)
70
+ GroupUser.create(:user_id => @user.login, :group_id => group.id)
66
71
  super
67
72
  end
68
73
  self
69
74
  end
75
+
70
76
  def groups.delete(group)
71
- gu = GroupUser.first(:user_id => @user.id, :group_id => group.id)
77
+ gu = GroupUser.first(:user_id => @user.login, :group_id => group.id)
72
78
  if gu
73
79
  gu.destroy
74
80
  super
75
81
  end
76
82
  end
83
+
77
84
  groups
78
85
  end
79
86
 
@@ -90,7 +97,8 @@ class User
90
97
  end
91
98
 
92
99
  def password=(password)
93
- attribute_set(:hashed_password, Ldap::Digest.ssha(password, "--#{Time.now}--#{login}--")) if password
100
+ salt = "--#{Time.now}--#{login}--"
101
+ attribute_set(:hashed_password, Ldap::Digest.ssha(password, salt)) if password
94
102
  end
95
103
  end
96
104
 
@@ -133,7 +141,7 @@ class GroupUser
133
141
  {:cn=>"#{group_user.group.name}", :objectclass => "posixGroup"}
134
142
  end
135
143
 
136
- property :user_id, Integer, :key => true, :field => "memberUid"
144
+ property :user_id, String, :key => true, :field => "memberUid"
137
145
  property :group_id, Integer, :key => true, :field => "gidNumber"
138
146
 
139
147
  def group
metadata CHANGED
@@ -1,37 +1,38 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-ldap-adapter
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
5
- prerelease: false
4
+ hash: 9
5
+ prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 4
9
- - 2
10
- version: 0.4.2
9
+ - 3
10
+ version: 0.4.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - mkristian
14
+ - xertres
14
15
  autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2011-02-08 00:00:00 +05:30
19
+ date: 2011-06-06 00:00:00 +05:30
19
20
  default_executable:
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency
22
- name: ruby-net-ldap
23
+ name: net-ldap
23
24
  prerelease: false
24
25
  requirement: &id001 !ruby/object:Gem::Requirement
25
26
  none: false
26
27
  requirements:
27
- - - "="
28
+ - - ~>
28
29
  - !ruby/object:Gem::Version
29
- hash: 23
30
+ hash: 19
30
31
  segments:
31
32
  - 0
32
- - 0
33
- - 4
34
- version: 0.0.4
33
+ - 2
34
+ - 2
35
+ version: 0.2.2
35
36
  type: :runtime
36
37
  version_requirements: *id001
37
38
  - !ruby/object:Gem::Dependency
@@ -40,12 +41,14 @@ dependencies:
40
41
  requirement: &id002 !ruby/object:Gem::Requirement
41
42
  none: false
42
43
  requirements:
43
- - - ">="
44
+ - - ~>
44
45
  - !ruby/object:Gem::Version
45
- hash: 3
46
+ hash: 11
46
47
  segments:
47
48
  - 0
48
- version: "0"
49
+ - 4
50
+ - 2
51
+ version: 0.4.2
49
52
  type: :runtime
50
53
  version_requirements: *id002
51
54
  - !ruby/object:Gem::Dependency
@@ -56,74 +59,134 @@ dependencies:
56
59
  requirements:
57
60
  - - ~>
58
61
  - !ruby/object:Gem::Version
59
- hash: 23
62
+ hash: 15
60
63
  segments:
61
64
  - 1
62
65
  - 0
63
- - 0
64
- version: 1.0.0
66
+ version: "1.0"
65
67
  type: :runtime
66
68
  version_requirements: *id003
67
69
  - !ruby/object:Gem::Dependency
68
- name: hoe
70
+ name: dm-transactions
69
71
  prerelease: false
70
72
  requirement: &id004 !ruby/object:Gem::Requirement
71
73
  none: false
72
74
  requirements:
73
- - - ">="
75
+ - - ~>
74
76
  - !ruby/object:Gem::Version
75
- hash: 47
77
+ hash: 15
78
+ segments:
79
+ - 1
80
+ - 0
81
+ version: "1.0"
82
+ type: :runtime
83
+ version_requirements: *id004
84
+ - !ruby/object:Gem::Dependency
85
+ name: dm-sqlite-adapter
86
+ prerelease: false
87
+ requirement: &id005 !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ~>
91
+ - !ruby/object:Gem::Version
92
+ hash: 15
93
+ segments:
94
+ - 1
95
+ - 0
96
+ version: "1.0"
97
+ type: :development
98
+ version_requirements: *id005
99
+ - !ruby/object:Gem::Dependency
100
+ name: dm-migrations
101
+ prerelease: false
102
+ requirement: &id006 !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ~>
106
+ - !ruby/object:Gem::Version
107
+ hash: 15
108
+ segments:
109
+ - 1
110
+ - 0
111
+ version: "1.0"
112
+ type: :development
113
+ version_requirements: *id006
114
+ - !ruby/object:Gem::Dependency
115
+ name: rspec
116
+ prerelease: false
117
+ requirement: &id007 !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ~>
121
+ - !ruby/object:Gem::Version
122
+ hash: 23
76
123
  segments:
77
124
  - 2
78
- - 8
125
+ - 6
79
126
  - 0
80
- version: 2.8.0
127
+ version: 2.6.0
81
128
  type: :development
82
- version_requirements: *id004
129
+ version_requirements: *id007
130
+ - !ruby/object:Gem::Dependency
131
+ name: ruby-ldap
132
+ prerelease: false
133
+ requirement: &id008 !ruby/object:Gem::Requirement
134
+ none: false
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ hash: 45
139
+ segments:
140
+ - 0
141
+ - 9
142
+ - 11
143
+ version: 0.9.11
144
+ type: :development
145
+ version_requirements: *id008
83
146
  description: ldap adapter for datamapper which uses either net-ldap or ruby-ldap
84
147
  email:
85
148
  - m.kristian@web.de
149
+ - ""
86
150
  executables: []
87
151
 
88
152
  extensions: []
89
153
 
90
154
  extra_rdoc_files:
91
155
  - History.txt
92
- - Manifest.txt
93
- - README.txt
156
+ - README.md
94
157
  - ldap-commands.txt
95
158
  files:
96
- - .project
97
- - .yardoc
98
159
  - History.txt
99
160
  - MIT-LICENSE
100
- - Manifest.txt
101
- - README-example.markdown
102
- - README.txt
103
- - Rakefile
104
- - example/posix.rb
105
161
  - ldap-commands.txt
162
+ - README.md
163
+ - README-example.md
164
+ - README-bundler.md
106
165
  - lib/adapters/ldap_adapter.rb
107
166
  - lib/adapters/noop_transaction.rb
167
+ - lib/dm-ldap-adapter.rb
168
+ - lib/ldap_resource.rb
108
169
  - lib/dummy_ldap_resource.rb
109
- - lib/ldap/array.rb
110
- - lib/ldap/conditions_2_filter.rb
170
+ - lib/ldap/version.rb
171
+ - lib/ldap/unboundid_ldap_facade.rb
172
+ - lib/ldap/transactions.rb
173
+ - lib/ldap/ruby_ldap_facade.rb
111
174
  - lib/ldap/digest.rb
112
175
  - lib/ldap/net_ldap_facade.rb
113
- - lib/ldap/ruby_ldap_facade.rb
114
- - lib/ldap/version.rb
115
- - lib/ldap_resource.rb
116
- - spec/assiociations_ldap_adapter_spec.rb
117
- - spec/authentication_ldap_adapter_spec.rb
118
- - spec/ldap_adapter_spec.rb
119
- - spec/multi_repository_spec.rb
176
+ - lib/ldap/conditions_2_filter.rb
177
+ - lib/ldap/array.rb
120
178
  - spec/multi_value_attributes_spec.rb
121
179
  - spec/sorting_spec.rb
122
- - spec/spec.opts
180
+ - spec/multi_repository_spec.rb
181
+ - spec/contact.rb
123
182
  - spec/spec_helper.rb
124
- - test.ldif
183
+ - spec/ldap_adapter_spec.rb
184
+ - spec/authentication_ldap_adapter_spec.rb
185
+ - spec/assiociations_ldap_adapter_spec.rb
186
+ - spec/ldap_array_spec.rb
187
+ - example/posix.rb
125
188
  has_rdoc: true
126
- homepage: http://dm-ldap-adapter.rubyforge.org
189
+ homepage: http://github.com/mkristian/dm-ldap-adapter
127
190
  licenses: []
128
191
 
129
192
  post_install_message:
@@ -152,8 +215,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
215
  version: "0"
153
216
  requirements: []
154
217
 
155
- rubyforge_project: dm-ldap-adapter
156
- rubygems_version: 1.3.7
218
+ rubyforge_project:
219
+ rubygems_version: 1.5.3
157
220
  signing_key:
158
221
  specification_version: 3
159
222
  summary: ""
data/.project DELETED
@@ -1,11 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <projectDescription>
3
- <name>dm-ldap-adapter</name>
4
- <comment></comment>
5
- <projects>
6
- </projects>
7
- <buildSpec>
8
- </buildSpec>
9
- <natures>
10
- </natures>
11
- </projectDescription>
data/.yardoc DELETED
Binary file
data/Manifest.txt DELETED
@@ -1,29 +0,0 @@
1
- .project
2
- .yardoc
3
- History.txt
4
- MIT-LICENSE
5
- Manifest.txt
6
- README-example.markdown
7
- README.txt
8
- Rakefile
9
- example/posix.rb
10
- ldap-commands.txt
11
- lib/adapters/ldap_adapter.rb
12
- lib/adapters/noop_transaction.rb
13
- lib/dummy_ldap_resource.rb
14
- lib/ldap/array.rb
15
- lib/ldap/conditions_2_filter.rb
16
- lib/ldap/digest.rb
17
- lib/ldap/net_ldap_facade.rb
18
- lib/ldap/ruby_ldap_facade.rb
19
- lib/ldap/version.rb
20
- lib/ldap_resource.rb
21
- spec/assiociations_ldap_adapter_spec.rb
22
- spec/authentication_ldap_adapter_spec.rb
23
- spec/ldap_adapter_spec.rb
24
- spec/multi_repository_spec.rb
25
- spec/multi_value_attributes_spec.rb
26
- spec/sorting_spec.rb
27
- spec/spec.opts
28
- spec/spec_helper.rb
29
- test.ldif
data/Rakefile DELETED
@@ -1,38 +0,0 @@
1
- # -*- ruby -*-
2
-
3
- require 'rubygems'
4
- require 'hoe'
5
- require './lib/ldap/version.rb'
6
-
7
- require 'spec'
8
- require 'spec/rake/spectask'
9
- require 'pathname'
10
-
11
- Hoe.spec('dm-ldap-adapter') do |p|
12
- p.version = "0.4.2"
13
- p.description = "ldap adapter for datamapper which uses either net-ldap or ruby-ldap"
14
- p.developer('mkristian', 'm.kristian@web.de')
15
- p.url = "http://dm-ldap-adapter.rubyforge.org"
16
- p.extra_deps = [['ruby-net-ldap', '=0.0.4'],['slf4r', '>=0'], ['dm-core', '~>1.0.0']]
17
- p.remote_rdoc_dir = '' # Release to root
18
- end
19
-
20
- desc 'Install the package as a gem.'
21
- task :install => [:clean, :package] do
22
- gem = Dir['pkg/*.gem'].first
23
- sh "gem install --local #{gem} --no-ri --no-rdoc"
24
- end
25
-
26
- desc 'Run specifications'
27
- Spec::Rake::SpecTask.new(:spec) do |t|
28
- if File.exists?('spec/spec.opts')
29
- t.spec_opts << '--options' << 'spec/spec.opts'
30
- end
31
- t.spec_files = Pathname.glob('./spec/**/*_spec.rb')
32
- end
33
-
34
- #require 'yard'
35
-
36
- #YARD::Rake::YardocTask.new
37
-
38
- # vim: syntax=Ruby
data/spec/spec.opts DELETED
@@ -1,2 +0,0 @@
1
- --colour
2
- --loadby random
data/test.ldif DELETED
@@ -1,46 +0,0 @@
1
- dn: ou=people,dc=example,dc=com
2
- objectClass: organizationalUnit
3
- ou: people
4
-
5
- dn: ou=groups,dc=example,dc=com
6
- objectClass: organizationalUnit
7
- ou: groups
8
-
9
- dn: uid=kristian,ou=people,dc=example,dc=com
10
- objectClass: inetOrgPerson
11
- objectClass: posixAccount
12
- uid: kristian
13
- sn: Meier
14
- givenName: Kristian
15
- cn: Kristian Meier
16
- uidNumber: 1000
17
- gidNumber: 10000
18
- userPassword: {SSHA}/o6oa2GOphls/lzOFCwVkw9ARWEEiw+x
19
- mail: m.kristian@web.de
20
- loginShell: /bin/false
21
- homeDirectory:
22
-
23
- dn: cn=manager,ou=groups,dc=example,dc=com
24
- objectClass: posixGroup
25
- cn: manager
26
- gidNumber: 10000
27
-
28
- dn: cn=admin,ou=groups,dc=example,dc=com
29
- objectClass: posixGroup
30
- cn: admin
31
- gidNumber: 10001
32
- memberUid: kristian
33
-
34
- dn: uid=bill,ou=people,dc=example,dc=com
35
- objectClass: inetOrgPerson
36
- objectClass: posixAccount
37
- uid: bill
38
- sn: bill
39
- givenName: bill
40
- cn: bill
41
- uidNumber: 1001
42
- gidNumber: 10000
43
- userPassword: {SSHA}/o6oa2GOphls/lzOFCwVkw9ARWEEiw+x
44
- mail: bill@web.de
45
- loginShell: /bin/false
46
- homeDirectory: /root