admapper 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ LICENSE
2
+
3
+ The MIT License
4
+
5
+ Copyright (c) 2010 Brian P. Hogan
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,183 @@
1
+ ==ADMapper
2
+ Library to connect to ActiveDirectory and map your users to ActiveDirectory users.
3
+
4
+ You supply your own classes and mix the User or Group module into your classes.
5
+
6
+ == Why
7
+ Because http://github.com/filefrog/activedirectory didn't work well for me and I wanted
8
+ flexibility to use my own classes, whether those are in Sinatra or
9
+ using full-blown ActiveRecord classes in a Rails app.
10
+
11
+ == Installation
12
+
13
+ It's a gem, so you can
14
+
15
+ gem install admapper
16
+
17
+ Or you can clone the repo and build it yourself with
18
+
19
+ gem build admapper.gemspec
20
+
21
+ == Usage
22
+ Basic usage is simple. Include the gem
23
+
24
+ require 'rubygems'
25
+ require 'admapper'
26
+
27
+ Then add the mixin to your class of choice
28
+
29
+ class User
30
+ attr_accessor: username
31
+ include ADMapper::Resource
32
+ end
33
+
34
+ Finally, configure the connection:
35
+
36
+ ADMapper::Configuration.connection_info = {:username => "homer",
37
+ :password=>"foo",
38
+ :host => "your_ad_host",
39
+ :port=>"636",
40
+ :ssl => true,
41
+ :domain => "example.com"}
42
+
43
+ The connection_info method takes a hash, so you can use a YML file if you'd like:
44
+
45
+ CONFIG_OPTS = YAML::load(File.open(File.expand_path(File.dirname(__FILE__) + "/admapper.yml"))).symbolize_keys
46
+ ADMapper::Configuration.connection_info
47
+
48
+ == Working with Users
49
+
50
+ === Finding someone by username
51
+
52
+ u = User.find_in_ad_by_username("homer")
53
+
54
+ This creates a new instance of your User class. If you're using ActiveRecord, you can save this.
55
+
56
+ === Mapping users
57
+
58
+ We use the "username" column to find stuff in ActiveDirectory by default. But you may not have a username column - you might call it "login". You may also want to easily grab other info from ActiveDirectory and map it to your own objects. In your model, add this method:
59
+
60
+ Simply map your model (keys of the hash) to ActiveDirectory (values of the hash)
61
+
62
+ def ad_map
63
+ {
64
+ :username => :samaccountname,
65
+ :full_name => :displayname
66
+ }
67
+ end
68
+
69
+ When you call the find_in_ad_by_username method, this method gets called.
70
+ You can also access the actual Net::LDAP::Entry object by calling
71
+
72
+ user = User.find_in_ad_by_username("homer")
73
+ user.ad_user
74
+ user.ad_user.samaccountname
75
+ user.ad_user.givenname
76
+
77
+ === Working with an existing user
78
+ So maybe you have a user already.
79
+
80
+ u = User.find(1)
81
+
82
+ And you want his AD information? Fetch it. It'll default to looking up the user in ActiveDirectory by the <tt>username</tt> attribute.
83
+
84
+ u.find_in_ad
85
+
86
+ This will fill in the attributes you specified in your ad_map method.
87
+
88
+ You can use a different field. If you store the username as "login", do this:
89
+
90
+ u.find_in_ad(:key => "login")
91
+
92
+
93
+ === Authenticating a user
94
+
95
+ Don't do this. Taking someone's password and passing it on to Active Directory is just stupid. Use CAS, Shibboleth, or something else that prevents your app from ever seeing a user's password. If you insist on doing this, use SSL, filter the password out of your logs, and pray. This will let you do what you want
96
+
97
+ User.authenticate_with_active_directory("homer", "1234")
98
+
99
+ It'll return true or false. It won't return a user. I assume you'll be wrapping this call in something else that will fetch the user object from your local DB.
100
+
101
+ == Groups
102
+
103
+ Working on group support. Got a few things working:
104
+
105
+ === Getting groups
106
+
107
+ Create your own class
108
+
109
+ class Group
110
+ attr_accessor :name
111
+ include ADMapper::Group
112
+ end
113
+
114
+ Then look for a group
115
+
116
+ g = Group.find_in_ad_by_name("Marketing")
117
+
118
+ You can also look for groups
119
+
120
+ groups = Group.find_all_in_ad_by_name("HR.*")
121
+
122
+ === Users and their groups
123
+
124
+ Need to find all groups for a user?
125
+
126
+ class Group
127
+ attr_accessor :name
128
+ include ADMapper::Group
129
+ end
130
+
131
+ class User
132
+ attr_accessor :name, :username
133
+
134
+ include ADMapper::User
135
+ set_group_class Group
136
+ end
137
+
138
+ Then get a user
139
+
140
+ u = User.find(1)
141
+ u.find_in_ad
142
+ groups = u.groups
143
+
144
+ == Testing
145
+
146
+ I'm not giving you my credentials, so you'll need to supply your own. Create the file
147
+
148
+ test/admapper.yml
149
+
150
+ and put this inside:
151
+
152
+ username: "username"
153
+ password: "password"
154
+ domain: "example.com"
155
+ host: "domain.controller.example.com"
156
+ ssl: true
157
+ port: 636
158
+
159
+ Then open test_helper and change the GROUP constant to a group that your username is a member of.
160
+
161
+ Given good credentials, the tests should pass.
162
+
163
+ == Patches
164
+ * Fork it
165
+ * Write a test
166
+ * Make a fix
167
+ * send a patch or a pull request.
168
+ * Don't touch the changelog.
169
+ * If it doesn't have a test, I'm not looking at it.
170
+
171
+ If this doesn't do what you want it to do, fork this and write your own library. I'm sharing this as a starting point and this is the bare minimum I need to get my AD tasks accomplished.
172
+
173
+ == Changelog
174
+ 0.0.3 (2010-11-08)
175
+ * Added group lookup by name
176
+ * Added ability to get groups for a user
177
+ 0.0.2 (2010-03-31)
178
+ * refactoring to a central configuration option
179
+ * removed ad_connect! class method from resource module. Use a single global connection
180
+ 0.0.1 (2010-03-30)
181
+ * Initial mixin and basic support for finding stuff via AD
182
+ * Mapping objects
183
+ * Tests
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
6
+ require 'ADMapper'
7
+
8
+ desc "Run the tests"
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'test'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ Rake::TestTask.new(:t) do |t|
16
+ t.libs << 'test'
17
+ t.pattern = ENV["TEST"]
18
+ t.verbose = true
19
+ end
20
+
21
+ desc 'Start an IRB session with all necessary files required.'
22
+ task :shell do |t|
23
+ chdir File.dirname(__FILE__)
24
+ exec 'irb -I lib/ -I lib/ADMapper -r rubygems -r net-ldap -r tempfile -r init'
25
+ end
26
+
27
+ desc 'Generate documentation for ADMapper'
28
+ Rake::RDocTask.new(:rdoc) do |rdoc|
29
+ rdoc.rdoc_dir = 'doc'
30
+ rdoc.title = 'ADMapper'
31
+ rdoc.options << '--line-numbers' << '--inline-source'
32
+ rdoc.rdoc_files.include('README*')
33
+ rdoc.rdoc_files.include('lib/**/*.rb')
34
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "lib", "admapper")
data/lib/admapper.rb ADDED
@@ -0,0 +1,8 @@
1
+ # ADMapper is a simple wrapper for ActiveRecord user lookup and authentication.
2
+ require 'yaml'
3
+ require 'net/ldap'
4
+ require 'admapper/extensions'
5
+ require 'admapper/configuration'
6
+ require 'admapper/connection'
7
+ require 'admapper/user'
8
+ require 'admapper/group'
@@ -0,0 +1,13 @@
1
+ module ADMapper
2
+ # Holds configuration options.
3
+ class Configuration
4
+
5
+ @connection_info = nil
6
+ class << self; attr_accessor :connection_info; end
7
+
8
+ end
9
+
10
+ # Configuration exception
11
+ class ConfigurationMissingError < StandardError
12
+ end
13
+ end
@@ -0,0 +1,69 @@
1
+ module ADMapper
2
+ class Connection
3
+ @@ad_connection = nil
4
+ @@ad_treebase = nil
5
+ @@ad_user_filter = nil
6
+
7
+ def self.treebase
8
+ @@ad_treebase
9
+ end
10
+
11
+ # class method to return the current connection
12
+ def self.current_connection
13
+ self.ad_connect_if_not_connected
14
+ @@ad_connection
15
+ end
16
+
17
+ def self.current_connection=(conn)
18
+ @@ad_connection = conn
19
+ end
20
+
21
+ # config_options = {:username => "homer",
22
+ # :password => "1234",
23
+ # :domain => "springfieldnuclear.com",
24
+ # :host => "ad.springfieldnuclear.com",
25
+ # :ssl => true,
26
+ # :port => 636}
27
+ #
28
+ # if ad_connect!(config_options)
29
+ # ....
30
+ # else
31
+ # ...
32
+ # end
33
+ def self.ad_connect!(config = nil)
34
+ config ||= ADMapper::Configuration.connection_info
35
+
36
+ raise ADMapper::ConfigurationMissingError if config.nil?
37
+ username = config[:username]
38
+ password = config[:password]
39
+ domain = config[:domain]
40
+ host = config[:host]
41
+ port = config[:port]
42
+ dc1 = domain.split(".").first
43
+ dc2 = domain.split(".").last
44
+ ssl = config[:ssl] ? true : false
45
+ self.current_connection = initialize_ldap_con(username + "@#{domain}", password, host, port, ssl)
46
+ @@ad_treebase = "DC=#{dc1},DC=#{dc2}"
47
+ @@ad_user_filter = Net::LDAP::Filter.eq( "sAMAccountName", username )
48
+ self.current_connection.bind
49
+ end
50
+
51
+ # initializes the connection to the ldap server. Does not bind.
52
+ def self.initialize_ldap_con(user_name, password, host, port, ssl)
53
+ ldap = Net::LDAP.new
54
+ ldap.host = host
55
+ ldap.port = port #required for SSL connections, 389 is the default plain text port
56
+ if ssl
57
+ ldap.encryption :simple_tls #also required to tell Net:LDAP that we want SSL
58
+ end
59
+ ldap.auth "#{user_name}","#{password}"
60
+ ldap #explicitly return the ldap connection object
61
+ end
62
+
63
+ # connects if not connected already
64
+ def self.ad_connect_if_not_connected
65
+ self.ad_connect! if @@ad_connection.nil?
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ def symbolize_keys
3
+ inject({}) do |options, (key, value)|
4
+ options[(key.to_sym rescue key) || key] = value
5
+ options
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,90 @@
1
+ module ADMapper
2
+
3
+ module Group
4
+
5
+ def self.included(model)
6
+ model.class_eval do
7
+ extend ADMapper::Group::ClassMethods
8
+ include ADMapper::Group::InstanceMethods
9
+ attr_accessor :ad_group
10
+ end
11
+
12
+ end
13
+
14
+ module InstanceMethods
15
+
16
+ # maps the ad_user's attributes to your class' attributes
17
+ #. Implement the ad_map method in your own class to control how fields map.
18
+ def map_group_from_ad(ad_group)
19
+ self.ad_map.each do |group_object_field, ad_object_field|
20
+ self.send("#{group_object_field}=", ad_group.send(ad_object_field).to_s)
21
+ end
22
+ self.ad_group = ad_group
23
+ end
24
+
25
+ # Default mapping of user object to active directory.
26
+ # You will most likely want to implement this in your own class
27
+ # instead of using this very basic default.
28
+ # Simply map your model (keys of the hash) to ActiveDirectory (values of the hash)
29
+ #
30
+ # def ad_map
31
+ # {
32
+ # :username => :samaccountname,
33
+ # :full_name => :displayname
34
+ # }
35
+ # end
36
+ def ad_map
37
+ {:name => :cn}
38
+ end
39
+ end
40
+
41
+ module ClassMethods
42
+
43
+ def find_in_ad_by_name(groupname)
44
+ attrs = [ "cn", "ou" , "objectClass"]
45
+ filter = Net::LDAP::Filter.eq( "objectClass", "group" )
46
+ name_filter = Net::LDAP::Filter.eq( "cn", groupname )
47
+ ad_connection = ADMapper::Connection.current_connection
48
+ ad_group = nil
49
+ ad_connection.search( :base => ADMapper::Connection.treebase,
50
+ :attributes => attrs,
51
+ :filter => filter & name_filter ) do |entry|
52
+ if entry
53
+ ad_group = self.new
54
+ ad_group.map_group_from_ad(entry)
55
+ break
56
+ end
57
+ end
58
+ ad_group
59
+
60
+ end
61
+ def find_all_in_ad_by_name(groupname)
62
+
63
+ attrs = [ "cn", "ou" , "objectClass"]
64
+ filter = Net::LDAP::Filter.eq( "objectClass", "group" )
65
+ name_filter = Net::LDAP::Filter.eq( "cn", groupname )
66
+ ad_connection = ADMapper::Connection.current_connection
67
+ ad_groups = []
68
+
69
+ ad_connection.search( :base => ADMapper::Connection.treebase,
70
+ :attributes => attrs,
71
+ :filter => filter & name_filter ) do |entry|
72
+ ad_group = entry
73
+ if ad_group
74
+ group = self.new
75
+ group.map_group_from_ad(ad_group)
76
+ ad_groups << group
77
+ end
78
+
79
+
80
+ end
81
+
82
+ ad_groups
83
+
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ end
@@ -0,0 +1,165 @@
1
+ module ADMapper
2
+
3
+ module User
4
+
5
+ def self.included(model)
6
+ model.class_eval do
7
+ extend ADMapper::User::ClassMethods
8
+ include ADMapper::User::InstanceMethods
9
+ attr_accessor :ad_user
10
+ end
11
+
12
+ end
13
+
14
+ module InstanceMethods
15
+
16
+ # Returns the groups for a user.
17
+ def groups
18
+ groups = []
19
+
20
+ search_filter = Net::LDAP::Filter.eq("sAMAccountName", self.ad_user.samaccountname.first)
21
+ ad_connection = ADMapper::Connection.current_connection
22
+ ad_connection.search(:base => ADMapper::Connection.treebase,
23
+ :filter => search_filter) do |entry|
24
+
25
+ entry.each do |attribute, values|
26
+ if attribute.to_s.match(/memberof/)
27
+ values.each do |value|
28
+ a = value.split(',')
29
+ md = a[0].match(/CN=(.+)/)
30
+
31
+ groups << md[1]
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ groups.collect do |g|
38
+ self.class.group_class.find_in_ad_by_name(g)
39
+ end.compact
40
+
41
+
42
+ end
43
+
44
+
45
+
46
+ def member_of?(group)
47
+ group_member = false
48
+
49
+ search_filter = Net::LDAP::Filter.eq("sAMAccountName", self.ad_user.samaccountname.first)
50
+ ad_connection = ADMapper::Connection.current_connection
51
+ ad_connection.search(:base => ADMapper::Connection.treebase,
52
+ :filter => search_filter) do |entry|
53
+
54
+ entry.each do |attribute, values|
55
+ if attribute.to_s.match(/memberof/)
56
+ values.each do |value|
57
+ a = value.split(',')
58
+ md = a[0].match(/CN=(.+)/)
59
+
60
+ # user is a member of the right group
61
+ if md[1] == group
62
+ group_member = true
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ group_member
69
+
70
+ end
71
+
72
+
73
+ # finds a user in active directory using the internal key.
74
+ # Defaults to username
75
+ def find_in_ad(options = {:key => :username})
76
+ username = send(options[:key])
77
+ ad_user = self.class.ad_query_by_username(username)
78
+ return nil if ad_user.nil?
79
+ self.map_user_from_ad(ad_user)
80
+ true
81
+ end
82
+
83
+ # maps the ad_user's attributes to your class' attributes
84
+ #. Implement the ad_map method in your own class to control how fields map.
85
+ def map_user_from_ad(ad_user)
86
+ self.ad_map.each do |user_object_field, ad_object_field|
87
+ self.send("#{user_object_field}=", ad_user.send(ad_object_field).to_s)
88
+ end
89
+ self.ad_user = ad_user
90
+ end
91
+
92
+ # Default mapping of user object to active directory.
93
+ # You will most likely want to implement this in your own class
94
+ # instead of using this very basic default.
95
+ # Simply map your model (keys of the hash) to ActiveDirectory (values of the hash)
96
+ #
97
+ # def ad_map
98
+ # {
99
+ # :username => :samaccountname,
100
+ # :full_name => :displayname
101
+ # }
102
+ # end
103
+ def ad_map
104
+ {:username => :samaccountname}
105
+ end
106
+
107
+ end
108
+
109
+ module ClassMethods
110
+ attr_accessor :group_class
111
+
112
+ def set_group_class(group_class)
113
+ self.group_class = group_class
114
+ end
115
+
116
+ # Authenticating users:
117
+ # Don't do this. Taking someone's password and passing it
118
+ # on to Active Directory is just stupid. Use CAS, Shibboleth, or
119
+ # something else that prevents your app from ever seeing a user's password.
120
+ # If you insist on doing this, use SSL, filter the password out of your logs,
121
+ # and pray. This will let you do what you want
122
+ #
123
+ # User.authenticate_with_active_directory("homer", "1234")
124
+ #
125
+ # It'll return true or false. It won't return a user. I assume you'll be wrapping this call in something else that will fetch the user object from your local DB.
126
+ def authenticate_with_active_directory(username, password)
127
+ auth_ldap = ADMapper::Connection.current_connection.dup.bind_as(
128
+ :filter => Net::LDAP::Filter.eq( "sAMAccountName", username ),
129
+ :base => ADMapper::Connection.treebase,
130
+ :password => password
131
+ )
132
+
133
+ end
134
+
135
+ # Find a user in AD by the given username
136
+ # Calls #map_user_from_ad on the returned results
137
+ # so you can manage it yourself.
138
+ def find_in_ad_by_username(username)
139
+ ad_user = ad_query_by_username(username)
140
+ return nil if ad_user.nil?
141
+
142
+ user = self.new
143
+ user.map_user_from_ad(ad_user)
144
+ user
145
+ end
146
+
147
+ # find a user in AD by the given userame.
148
+ # Connects if not connected
149
+ # Returns an AD object
150
+ def ad_query_by_username(username)
151
+
152
+ user = nil
153
+ search_filter = Net::LDAP::Filter.eq( "sAMAccountName", username )
154
+ ad_connection = ADMapper::Connection.current_connection
155
+ ad_connection.search(:base => ADMapper::Connection.treebase,
156
+ :filter => search_filter,
157
+ :attributes => ['dn','sAMAccountName','displayname','SN','givenName']) do |ad_user|
158
+ user = ad_user
159
+ end
160
+ user
161
+ end
162
+
163
+ end
164
+ end
165
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: admapper
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 3
10
+ version: 0.0.3
11
+ platform: ruby
12
+ authors:
13
+ - Brian Hogan
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-08 00:00:00 -06:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: net-ldap
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - "="
28
+ - !ruby/object:Gem::Version
29
+ hash: 25
30
+ segments:
31
+ - 0
32
+ - 1
33
+ - 1
34
+ version: 0.1.1
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: mocha
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - "="
44
+ - !ruby/object:Gem::Version
45
+ hash: 43
46
+ segments:
47
+ - 0
48
+ - 9
49
+ - 8
50
+ version: 0.9.8
51
+ type: :development
52
+ version_requirements: *id002
53
+ description:
54
+ email: brianhogan@napcs.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files:
60
+ - README.rdoc
61
+ files:
62
+ - README.rdoc
63
+ - LICENSE
64
+ - Rakefile
65
+ - init.rb
66
+ - lib/admapper.rb
67
+ - lib/admapper/extensions.rb
68
+ - lib/admapper/connection.rb
69
+ - lib/admapper/group.rb
70
+ - lib/admapper/configuration.rb
71
+ - lib/admapper/user.rb
72
+ has_rdoc: true
73
+ homepage: http://www.napcs.com/projects/
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options:
78
+ - --line-numbers
79
+ - --inline-source
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ requirements:
101
+ - net-ldap
102
+ rubyforge_project: admapper
103
+ rubygems_version: 1.3.7
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: Friendly mixin for working with Microsoft's ActiveDirectory
107
+ test_files: []
108
+