dm-ldap-adapter 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,10 @@
1
+ version 0.2.0
2
+ =============
3
+
4
+ * switched to Slf4r logger
5
+
6
+ * the whole thing became a gem
7
+
8
+ * cleaned up example
9
+
10
+ * moved the SHA and SSHA calculation into its own helper class (incompatible change to older version)
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Kristian Meier
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,24 @@
1
+ History.txt
2
+ MIT-LICENSE
3
+ Manifest.txt
4
+ README-example.markdown
5
+ README.txt
6
+ Rakefile
7
+ example/identity_map.rb
8
+ example/posix.rb
9
+ ldap-commands.txt
10
+ lib/adapters/ldap_adapter.rb
11
+ lib/adapters/memory_adapter.rb
12
+ lib/adapters/simple_adapter.rb
13
+ lib/dummy_ldap_resource.rb
14
+ lib/ldap/digest.rb
15
+ lib/ldap/ldap_facade.rb
16
+ lib/ldap/ldap_facade_mock.rb
17
+ lib/ldap/version.rb
18
+ lib/ldap_resource.rb
19
+ spec/assiociations_ldap_adapter_spec.rb
20
+ spec/authentication_ldap_adapter_spec.rb
21
+ spec/ldap_adapter_spec.rb
22
+ spec/spec.opts
23
+ spec/spec_helper.rb
24
+ test.ldif
@@ -0,0 +1,18 @@
1
+ posix accounts/groups example
2
+ =============================
3
+
4
+ first you need to adjust the configuration for ldap adapter in `example/posix.rb` and then start irb
5
+
6
+ $ LDAP_PWD='secret' irb
7
+
8
+ require 'example/posix.rb'
9
+ u = User.create(:login=>'test', :name => "name", :password => "pwd")
10
+ User.all
11
+ g = Group.create(:name => "test")
12
+ Group.all
13
+ u.groups << g
14
+ u.groups
15
+ g.users
16
+ u.authenticate("wrong-pwd")
17
+ u.authenticate("pwd")
18
+
data/README.txt ADDED
@@ -0,0 +1,188 @@
1
+ = dm-ldap-adapter
2
+
3
+ *Homepage*: [http://dm-ldap-adapter.rubyforge.org]
4
+
5
+ *Git*: [http://github.com/mkristian/dm-ldap-adapter]
6
+
7
+ *Author*: Kristian Meier
8
+
9
+ *Copyright*: 2008-2009
10
+
11
+ == DESCRIPTION:
12
+
13
+ === usecase
14
+
15
+ 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
+
17
+ === low level ldap library
18
+
19
+ the ldap library which does the actual ldap protocol stuff is [http://rubyforge.org/projects/net-ldap] and it is hidden behind a facade, so one could replace it with a different library or make it pluggable.
20
+
21
+ === examples
22
+
23
+ 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)
24
+
25
+ the 'example/identity_maps.rb' shows the usage of identity maps, see also below.
26
+
27
+ == FEATURES/PROBLEMS:
28
+
29
+ * the net-ldap has some issues with not closing the connections when an exception/error got raised
30
+
31
+ * error from the ldap server are only logged and do not raise any exceptions (to be changed in next release)
32
+
33
+ == SYNOPSIS:
34
+
35
+ === distinguished name (DN) of a model
36
+
37
+ 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.
38
+
39
+ class User
40
+ include DataMapper::Resource
41
+ property :id, Serial, :field => "uidnumber"
42
+ dn_prefix { |user| "uid=#{user.login}"}
43
+ treebase "ou=people"
44
+ end
45
+
46
+ with a base `dc=example,dc=com` we get a DN like the user 'admin'
47
+
48
+ uid=admin,ou=people,dc=example,dc=com
49
+
50
+ === ldap entities are bigger than the model
51
+
52
+ for example the ldap posixGroup has more attributes than the model class, it needs the `objectclass` attribute set to `posixGroup`.
53
+
54
+ class Group
55
+ include DataMapper::Resource
56
+ property :id, Serial, :field => "gidnumber"
57
+ property :name, String, :field => "cn"
58
+ dn_prefix { |group| "cn=#{group.name}" }
59
+ treebase "ou=groups"
60
+ ldap_properties {{ :objectclass => "posixGroup"}}
61
+ end
62
+
63
+ 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.
64
+
65
+ === authentication
66
+
67
+ 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.
68
+
69
+ === queries
70
+
71
+ 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:
72
+
73
+ * :eql
74
+ * :not
75
+ * :like
76
+ * :in
77
+ * Range
78
+
79
+ not working are `:lt, :lte, :gt, :gte`
80
+
81
+ *note*: sql handles `NULL` different from values, i.e.
82
+
83
+ select * from users where name = 'sd';
84
+
85
+ and
86
+
87
+ select * from users where name != 'sd';
88
+
89
+ gives the same result when *all* names are `NULL` !!!
90
+
91
+ === multiple repositories
92
+
93
+ 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:
94
+
95
+ class User
96
+ . . .
97
+ def self.default_repository_name
98
+ :ldap
99
+ end
100
+ end
101
+
102
+ class Config
103
+ . . .
104
+ def self.default_repository_name
105
+ :db
106
+ end
107
+ end
108
+
109
+ if you want to benefit from the advantages of the identidy maps you need to wrap your actions for *merb* see http://www.datamapper.org/doku.php?id=docs:identity_map or for *rails* put an `around_filter` inside application.rb
110
+
111
+ around_filter :repositories
112
+
113
+ def repositories
114
+ DataMapper.repository(:ldap) do
115
+ DataMapper.repository(:db) do
116
+ yield
117
+ end
118
+ end
119
+ end
120
+
121
+ and to let the ldap resources use the ldap respository it is best to bind it to that repository like this
122
+
123
+ class User
124
+ . . .
125
+ def self.repository_name
126
+ :ldap
127
+ end
128
+ end
129
+
130
+ === transactions
131
+
132
+ the adapter offers a noop transaction, i.e. you can wrap everything into a transaction but the ldap part has no functionality.
133
+
134
+ *note*: the ldap protocol does not know transactions
135
+
136
+ === many-to-many associations
137
+
138
+ 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.
139
+
140
+ class GroupUser
141
+ include DataMapper::Resource
142
+ property :memberuid, String, :key => true
143
+ property :gidnumber, Integer, :key => true
144
+ dn_prefix { |group_user| "cn=#{group_user.group.name}" }
145
+ treebase "ou=groups"
146
+ ldap_properties do |group_user|
147
+ {:cn=>"#{group_user.group.name}", :objectclass => "posixGroup"}
148
+ end
149
+
150
+ multivalue_field :memberuid
151
+
152
+ end
153
+
154
+ == REQUIREMENTS:
155
+
156
+ * slf4r the logging facade
157
+ * net-ldap pure ruby ldap library
158
+ * logging (optional) if logging via logging is desired
159
+ * log4r (optional) if logging via log4r is desired
160
+
161
+ == INSTALL:
162
+
163
+ * sudo gem install dm-ldap-adapter
164
+
165
+ == LICENSE:
166
+
167
+ (The MIT License)
168
+
169
+ Copyright (c) 2009 Kristian Meier
170
+
171
+ Permission is hereby granted, free of charge, to any person obtaining
172
+ a copy of this software and associated documentation files (the
173
+ 'Software'), to deal in the Software without restriction, including
174
+ without limitation the rights to use, copy, modify, merge, publish,
175
+ distribute, sublicense, and/or sell copies of the Software, and to
176
+ permit persons to whom the Software is furnished to do so, subject to
177
+ the following conditions:
178
+
179
+ The above copyright notice and this permission notice shall be
180
+ included in all copies or substantial portions of the Software.
181
+
182
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
183
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
184
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
185
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
186
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
187
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
188
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,35 @@
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.new('dm-ldap-adapter', Ldap::VERSION) do |p|
12
+ p.developer('mkristian', 'm.kristian@web.de')
13
+ p.extra_deps = ['ruby-net-ldap','slf4r']
14
+ p.remote_rdoc_dir = '' # Release to root
15
+ end
16
+
17
+ desc 'Install the package as a gem.'
18
+ task :install => [:clean, :package] do
19
+ gem = Dir['pkg/*.gem'].first
20
+ sh "gem install --local #{gem} --no-ri --no-rdoc"
21
+ end
22
+
23
+ desc 'Run specifications'
24
+ Spec::Rake::SpecTask.new(:spec) do |t|
25
+ if File.exists?('spec/spec.opts')
26
+ t.spec_opts << '--options' << 'spec/spec.opts'
27
+ end
28
+ t.spec_files = Pathname.glob('./spec/**/*_spec.rb')
29
+ end
30
+
31
+ require 'yard'
32
+
33
+ YARD::Rake::YardocTask.new
34
+
35
+ # vim: syntax=Ruby
@@ -0,0 +1,76 @@
1
+ require 'example/posix.rb'
2
+
3
+ USER_REPO = :default
4
+
5
+ class User
6
+
7
+ def self.ddefault_repository_name
8
+ USER_REPO
9
+ end
10
+
11
+ def self.repository_name
12
+ USER_REPO
13
+ end
14
+
15
+ def authenticate(pwd)
16
+ require 'base64'
17
+ Base64.encode64(Digest::SHA1.digest(pwd)).gsub(/\n/, '') == attribute_get(:hashed_password)[5,1000]
18
+ end
19
+ end
20
+
21
+ class GroupUser
22
+
23
+ def self.ddefault_repository_name
24
+ USER_REPO
25
+ end
26
+
27
+ def self.repository_name
28
+ USER_REPO
29
+ end
30
+
31
+ end
32
+
33
+ class Group
34
+
35
+ def self.ddefault_repository_name
36
+ USER_REPO
37
+ end
38
+
39
+ def self.repository_name
40
+ USER_REPO
41
+ end
42
+
43
+ end
44
+
45
+ require 'adapters/memory_adapter'
46
+ DATA_REPO=:store
47
+ DataMapper.setup(DATA_REPO, {:adapter => 'memory'})
48
+
49
+ class Item
50
+ include DataMapper::Resource
51
+ property :id, Serial
52
+ end
53
+
54
+
55
+ DataMapper.repository(USER_REPO) do |repository|
56
+ repository.adapter.open_ldap_connection do
57
+ DataMapper.repository(DATA_REPO) do
58
+
59
+ root = User.create(:id => 0, :login => :root, :name => 'root', :password => 'none')
60
+ admin = Group.create(:name => :admin)
61
+ root.groups << admin
62
+
63
+ p DataMapper.repository(USER_REPO).identity_map(User)
64
+
65
+ p DataMapper.repository(USER_REPO).identity_map(Group)
66
+
67
+ p root.authenticate('none')
68
+
69
+ p root.groups
70
+
71
+ (1..10).each {Item.create}
72
+
73
+ p DataMapper.repository(DATA_REPO).identity_map(Item)
74
+ end
75
+ end
76
+ end
data/example/posix.rb ADDED
@@ -0,0 +1,166 @@
1
+ require 'pathname'
2
+ require 'rubygems'
3
+ require 'slf4r/logging_logger'
4
+ gem 'data_objects' , "0.9.11"
5
+ require 'dm-core'
6
+
7
+ $LOAD_PATH << Pathname(__FILE__).dirname.parent.expand_path + 'lib'
8
+
9
+ Logging.init :debug, :info, :warn, :error
10
+
11
+ appender = Logging::Appender.stdout
12
+ appender.layout = Logging::Layouts::Pattern.new(:pattern => "%d [%-l] (%c) %m\n")
13
+ logger = Logging::Logger.new(:root)
14
+ logger.add_appenders(appender)
15
+ logger.level = :debug
16
+ logger.info "initialized logger . . ."
17
+
18
+ dummy = true #uncomment this to use dummy, i.e. a database instead of ldap
19
+ dummy = false # uncomment this to use ldap
20
+ unless dummy
21
+ require 'ldap_resource'
22
+ require 'adapters/ldap_adapter'
23
+
24
+ DataMapper.setup(:default, {
25
+ :adapter => 'ldap',
26
+ :host => 'localhost',
27
+ :port => '389',
28
+ :base => ENV['LDAP_BASE'] || "dc=example,dc=com",
29
+ :bind_name => "cn=admin," + (ENV['LDAP_BASE'] || "dc=example,dc=com"),
30
+ :password => ENV['LDAP_PWD'] || "behappy"
31
+ })
32
+ else
33
+ require 'dummy_ldap_resource'
34
+ DataMapper.setup(:default, 'sqlite3::memory:')
35
+ adapter = DataMapper.repository.adapter
36
+ def adapter.ldap_connection
37
+ con = Object.new
38
+ def con.open
39
+ yield
40
+ end
41
+ con
42
+ end
43
+ end
44
+
45
+ class User
46
+ include DataMapper::Resource
47
+
48
+ property :id, Serial, :field => "uidnumber"
49
+ property :login, String, :field => "uid"
50
+ property :hashed_password, String, :field => "userpassword", :access => :private
51
+ property :name, String, :field => "cn"
52
+
53
+ has n, :group_users, :child_key => [:memberuid]
54
+
55
+ def groups
56
+ groups = GroupUser.all(:memberuid => login).collect{ |gu| gu.group }
57
+ def groups.user=(user)
58
+ @user = user
59
+ end
60
+ groups.user = self
61
+ def groups.<<(group)
62
+ unless member? group
63
+ GroupUser.create(:memberuid => @user.login, :gidnumber => group.id)
64
+ super
65
+ end
66
+ self
67
+ end
68
+ def groups.delete(group)
69
+ gu = GroupUser.first(:memberuid => @user.login, :gidnumber => group.id)
70
+ if gu
71
+ gu.destroy
72
+ super
73
+ end
74
+ end
75
+ groups
76
+ end
77
+
78
+ dn_prefix { |user| "uid=#{user.login}"}
79
+
80
+ treebase "ou=people"
81
+
82
+ ldap_properties do |user|
83
+ properties = { :objectclass => ["inetOrgPerson", "posixAccount", "shadowAccount"], :loginshell => "/bin/bash", :gidnumber => "10000" }
84
+ properties[:sn] = "#{user.name.sub(/.*\ /, "")}"
85
+ properties[:givenname] = "#{user.name.sub(/\ .*/, "")}"
86
+ properties[:homedirectory] = "/home/#{user.login}"
87
+ properties
88
+ end
89
+
90
+ def password=(password)
91
+ attribute_set(:hashed_password, Ldap::Digest.sha(password)) if password
92
+ end
93
+ end
94
+
95
+ class Group
96
+ include DataMapper::Resource
97
+ include Slf4r::Logger
98
+ property :id, Integer, :serial => true, :field => "gidnumber"
99
+ property :name, String, :field => "cn"
100
+
101
+ dn_prefix { |group| "cn=#{group.name}" }
102
+
103
+ treebase "ou=groups"
104
+
105
+ ldap_properties {{ :objectclass => "posixGroup"}}
106
+
107
+ def users
108
+ users = GroupUser.all(:gidnumber => id).collect{ |gu| gu.user }
109
+ def users.group=(group)
110
+ @group = group
111
+ end
112
+ users.group = self
113
+ def users.<<(user)
114
+ unless member? user
115
+ GroupUser.create(:memberuid => user.login, :gidnumber => @group.id)
116
+ super
117
+ end
118
+ self
119
+ end
120
+ def users.delete(user)
121
+ gu = GroupUser.first(:memberuid => user.login, :gidnumber => @group.id)
122
+ if gu
123
+ gu.destroy
124
+ super
125
+ end
126
+ end
127
+ users
128
+ end
129
+ end
130
+
131
+ class GroupUser
132
+ include DataMapper::Resource
133
+ include Slf4r::Logger
134
+
135
+ dn_prefix { |group_user| "cn=#{group_user.group.name}" }
136
+
137
+ treebase "ou=groups"
138
+
139
+ multivalue_field :memberuid
140
+
141
+ ldap_properties do |group_user|
142
+ {:cn=>"#{group_user.group.name}", :objectclass => "posixGroup"}
143
+ end
144
+ property :memberuid, String, :key => true#, :field => "memberuid"
145
+ property :gidnumber, Integer, :key => true#, :field => "gidnumber"
146
+
147
+ def group
148
+ Group.get!(gidnumber)
149
+ end
150
+
151
+ def group=(group)
152
+ gidnumber = group.id
153
+ end
154
+
155
+ def user
156
+ User.first(:login => memberuid)
157
+ end
158
+
159
+ def user=(user)
160
+ memberuid = user.login
161
+ end
162
+ end
163
+
164
+ if dummy
165
+ DataMapper.auto_migrate!
166
+ end