el_dap 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ test.rb
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
data/.rvmrc ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ environment_id="ruby-1.9.2-head@el_dap"
3
+
4
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
5
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] ; then
6
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
7
+ else
8
+ # If the environment file has not yet been created, use the RVM CLI to select.
9
+ rvm --create "$environment_id"
10
+ fi
11
+
@@ -0,0 +1,8 @@
1
+ 0.0.1
2
+ =====
3
+
4
+ Initial release.
5
+
6
+ Features:
7
+ * Perform directory service search
8
+ * Perform authentication against directory service
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in el_dap.gemspec
4
+ gemspec
5
+
6
+ gem 'net-ldap'
7
+
8
+ group :development, :test do
9
+ gem 'autotest'
10
+ gem 'autotest-notification'
11
+ gem 'bundler'
12
+ gem 'launchy'
13
+ gem 'rcov'
14
+ gem 'rspec'
15
+ end
16
+
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Ed James
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.
@@ -0,0 +1,70 @@
1
+ = El Dap
2
+
3
+ A simple search and authentication tool for Active Directory using LDAP.
4
+
5
+ == Why make this?
6
+
7
+ The need for this gem arose out of a business requirement for all user authentication to be done against Active Directory.
8
+ Our users were tired of having to remember multiple usernames and passwords, and asked if they could simply use their network
9
+ credentials to log into our applications. This gem passes off their username and password to an Active Directory / LDAP
10
+ server for authentication.
11
+
12
+ Additionally, users are also able to search the Active Directory.
13
+
14
+ == Installation
15
+
16
+ Just add this to your gem file:
17
+
18
+ gem 'el_dap'
19
+
20
+ == Features
21
+
22
+ * Easily perform authentication against an Active Directory / LDAP server
23
+ * Search an Active Directory based on wildcard search criteria
24
+
25
+ == Usage
26
+
27
+ Assuming we have an Active Directory called *ad.domain.com* which can also be referred to simply as *ad*,
28
+ the following examples are valid:
29
+
30
+ === Authenticate a user
31
+
32
+ ElDap.configure do |c|
33
+ c.server_ips = ['127.0.0.1']
34
+ c.treebase = 'dc=ad,dc=domain,dc=com'
35
+ end
36
+
37
+ ElDap.validate("ad\\ed.james", 'password') # will return true if username and password are valid
38
+ ElDap.validate("ed.james@ad.domain.com", 'password') # will return true if username and password are valid
39
+
40
+ === Search the directory
41
+
42
+ ElDap.configure do |c|
43
+ c.username = 'service.account@ad.domain.com'
44
+ c.password = 'password'
45
+ c.server_ips = ['127.0.0.1']
46
+ c.treebase = 'dc=ad,dc=domain,dc=com'
47
+ end
48
+
49
+ ElDap.search('some guy') # will return an array of OpenStruct objects for each matching search result.
50
+
51
+ == Contributing to El Dap
52
+
53
+ My understanding of LDAP limited. This gem has only been tested on a Linux server which queries Active Directory. The gem is in
54
+ production, but I'm not sure how well it will perform in another kind of LDAP-enabled architecture. Any comments or assistence
55
+ would be greatly appreciated.
56
+
57
+ If you want to contribute:
58
+
59
+ * Check out the latest master to make sure the feature hasn’t been implemented or the bug hasn’t been fixed yet
60
+ * Check out the issue tracker to make sure someone already hasn’t requested it and/or contributed it
61
+ * Fork the project
62
+ * Start a feature/bugfix branch
63
+ * Commit and push until you are happy with your contribution
64
+ * Make sure to add tests for it. This is important so I don’t break it in a future version unintentionally.
65
+ * Please try not to mess with the Rakefile, version, or history.
66
+
67
+ == Copyright
68
+
69
+ Copyright (c) 2011 Ed James. See LICENSE for details.
70
+
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "el_dap/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "el_dap"
7
+ s.version = ElDap::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ed James"]
10
+ s.email = ["ed.james.email@gmail.com"]
11
+ s.homepage = "https://github.com/edjames/el_dap"
12
+ s.summary = %q{A simple search and authentication tool for Active Directory using LDAP.}
13
+ s.description = %q{A simple search and authentication tool for Active Directory using LDAP.}
14
+
15
+ s.rubyforge_project = "el_dap"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ end
@@ -0,0 +1,9 @@
1
+ require 'net/ldap'
2
+ require 'timeout'
3
+ require 'el_dap/api_methods'
4
+ require 'el_dap/constants'
5
+ require 'el_dap/base'
6
+
7
+ module ElDap
8
+ extend(ApiMethods)
9
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+ environment_id="ruby-1.9.2-head@el_dap"
3
+
4
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
5
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]] ; then
6
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
7
+ else
8
+ rvm --create "$environment_id"
9
+ fi
10
+
@@ -0,0 +1,18 @@
1
+ module ElDap
2
+ module ApiMethods
3
+
4
+ def configure
5
+ @@worker = Base.new
6
+ yield(@@worker)
7
+ end
8
+
9
+ def validate(username, password)
10
+ @@worker.validate(username, password)
11
+ end
12
+
13
+ def search(search_string)
14
+ @@worker.search(search_string)
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,79 @@
1
+ module ElDap
2
+ class Base
3
+ include Constants
4
+
5
+ attr_accessor :server_ips, :username, :password, :timeout, :treebase
6
+
7
+ def initialize
8
+ @server_ips = []
9
+ @timeout = 15
10
+ yield(self) if block_given?
11
+ end
12
+
13
+ def validate(uname, pword)
14
+ return false if uname.nil? || uname.empty? || pword.nil? || pword.empty?
15
+ workers(uname, pword).each do |worker|
16
+ begin
17
+ Timeout::timeout(self.timeout) do
18
+ return worker.bind
19
+ end
20
+ rescue Timeout::Error
21
+ next
22
+ end
23
+ end
24
+ false
25
+ end
26
+
27
+ def search(search_string)
28
+ return nil if search_string.nil? || search_string.empty?
29
+ search_result = nil
30
+ workers(self.username, self.password).each do |worker|
31
+ begin
32
+ Timeout::timeout(self.timeout) do
33
+ search_result ||= search_active_directory(worker, search_string)
34
+ end
35
+ rescue Timeout::Error
36
+ next
37
+ end
38
+ end
39
+ create_result_collection search_result
40
+ end
41
+
42
+ private
43
+ def workers(uname = self.username, pword = self.password)
44
+ self.server_ips.map do |ip|
45
+ create_worker uname, pword, ip
46
+ end
47
+ end
48
+
49
+ def create_worker(uname, pword, server_ip)
50
+ obj = ::Net::LDAP.new
51
+ obj.host = server_ip
52
+ obj.auth uname, pword
53
+ obj
54
+ end
55
+
56
+ def search_active_directory(worker, search_string)
57
+ filters = LDAP_FILTERS & ::Net::LDAP::Filter.eq(LDAP_SEARCH_FIELD, "*#{search_string}*")
58
+ result = worker.search(:base => self.treebase,
59
+ :filter => filters,
60
+ :attributes => LDAP_ATTRS,
61
+ :return_result => true)
62
+ # search will return false if unable to bind
63
+ # e.g. service account credentials have expired
64
+ result || []
65
+ end
66
+
67
+ def create_result_collection(collection = [])
68
+ collection ||= []
69
+ collection.map { |entry| create_struct entry }
70
+ end
71
+
72
+ def create_struct(hash = {})
73
+ key_values = {}
74
+ hash.each{|k, v| key_values[k] = v[0]}
75
+ OpenStruct.new(key_values)
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,7 @@
1
+ module ElDap
2
+ module Constants
3
+ LDAP_ATTRS = ['cn', 'samaccountname', 'displayname', 'name', 'telephonenumber', 'userprincipalname', 'mail']
4
+ LDAP_FILTERS = ::Net::LDAP::Filter.eq("objectcategory", "person")
5
+ LDAP_SEARCH_FIELD = "cn"
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module ElDap
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ module ElDap
4
+ describe ApiMethods do
5
+ before(:each) do
6
+ @base = mock(ElDap::Base)
7
+ end
8
+
9
+ it "should setup a new instance of the Base class" do
10
+ @base.should_receive(:username=).with('username')
11
+ @base.should_receive(:password=).with('password')
12
+ @base.should_receive(:server_ips=).with(['1.2.3.4'])
13
+ @base.should_receive(:treebase=).with('dc=ad,dc=example,dc=com')
14
+ ElDap::Base.should_receive(:new).and_return(@base)
15
+ ElDap.configure do |l|
16
+ l.username = 'username'
17
+ l.password = 'password'
18
+ l.server_ips = ['1.2.3.4']
19
+ l.treebase = 'dc=ad,dc=example,dc=com'
20
+ end
21
+ end
22
+
23
+ it "should perform the validate method on the worker" do
24
+ ElDap::ApiMethods.class_variable_set(:@@worker, @base)
25
+ @base.should_receive(:validate).with('username', 'password')
26
+ ElDap.validate('username', 'password')
27
+ end
28
+
29
+ it "should perform the search method on the worker" do
30
+ ElDap::ApiMethods.class_variable_set(:@@worker, @base)
31
+ @base.should_receive(:search).with('search string')
32
+ ElDap.search('search string')
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ module ElDap
4
+ describe Base do
5
+
6
+ before(:each) do
7
+ @instance = Base.new
8
+ @ldap = mock(Net::LDAP)
9
+ end
10
+
11
+ describe "ElDap check for blank parameters" do
12
+ it "should not attempt validation if username or password are blank" do
13
+ @instance.validate('username', '').should be_false
14
+ @instance.validate('username', nil).should be_false
15
+ @instance.validate('', 'password').should be_false
16
+ @instance.validate(nil, 'password').should be_false
17
+ @instance.validate('', '').should be_false
18
+ @instance.validate(nil, nil).should be_false
19
+ end
20
+
21
+ it "should not attempt to search if no search criteria are provided" do
22
+ @worker.should_not_receive(:search)
23
+ @instance.search('').should be_false
24
+ @instance.search(nil).should be_nil
25
+ end
26
+ end
27
+
28
+ describe "protected methods" do
29
+ it "should create an internal worker for each ip address" do
30
+ @instance.stub!(:server_ips).and_return(['1.1.1.1', '2.2.2.2'])
31
+ @instance.should_receive(:create_worker).with('username', 'password', '1.1.1.1').and_return('worker1')
32
+ @instance.should_receive(:create_worker).with('username', 'password', '2.2.2.2').and_return('worker2')
33
+ @instance.send(:workers, 'username', 'password').should == ['worker1', 'worker2']
34
+ end
35
+
36
+ it "should create a Net::LDAP instance with the correct parameters" do
37
+ Net::LDAP.should_receive(:new).and_return(@ldap)
38
+ @ldap.should_receive(:host=).with('1.1.1.1')
39
+ @ldap.should_receive(:auth).with(/username/, /password/)
40
+ @instance.send(:create_worker, 'username', 'password', '1.1.1.1').should == @ldap
41
+ end
42
+
43
+ it "should transform a search result into a collection of structs" do
44
+ @instance.stub!(:create_struct).and_return('struct')
45
+ @instance.send(:create_result_collection, ['result']).should == ['struct']
46
+ end
47
+
48
+ it "should create a struct from a search result hash" do
49
+ search_result = {:cn => ['cn'], :mail => ['mail']}
50
+ struct = OpenStruct.new(:cn => 'cn',:mail => 'mail')
51
+ @instance.send(:create_struct, search_result).should == struct
52
+ end
53
+
54
+ describe "search_active_directory method" do
55
+ it "should search active directory using the correct filters and parameters" do
56
+ filter1 = Net::LDAP::Filter.eq("objectcategory", "person")
57
+ filter2 = Net::LDAP::Filter.eq("cn", "*search-string*")
58
+ @ldap.should_receive(:search).with(
59
+ :base => @instance.treebase,
60
+ :filter => filter1 & filter2,
61
+ :attributes => Base.const_get('LDAP_ATTRS'),
62
+ :return_result => true
63
+ ).and_return(['result'])
64
+ @instance.send(:search_active_directory, @ldap, 'search-string').should == ['result']
65
+ end
66
+
67
+ it "should return a valid result when search cannot bind to the domain" do
68
+ @ldap.should_receive(:search).and_return(false)
69
+ @instance.send(:search_active_directory, @ldap, 'search-string').should == []
70
+ end
71
+ end
72
+
73
+ describe "validate method" do
74
+ it "should attempt validation when username and password are provided" do
75
+ @instance.should_receive(:workers).with(/username/, /password/).and_return([@ldap])
76
+ @ldap.should_receive(:bind).and_return(true)
77
+ @instance.validate('username', 'password').should be_true
78
+ end
79
+
80
+ it "should stop the validation cycle when the first result if received" do
81
+ ldap2 = mock(Net::LDAP)
82
+ ldap2.should_not_receive(:bind)
83
+ @ldap.should_receive(:bind).and_return(true)
84
+ @instance.stub!(:workers).and_return([@ldap, ldap2])
85
+ @instance.validate('username', 'password').should be_true
86
+ end
87
+
88
+ it "should return false when a timeout occurs" do
89
+ @instance.stub!(:workers).and_return([@ldap])
90
+ @ldap.should_receive(:bind).and_raise(Timeout::Error)
91
+ @instance.validate('username', 'password').should be_false
92
+ end
93
+ end
94
+
95
+ describe 'search method' do
96
+ it "should complete gracefully if invalid credentials are used for search (search returns false)" do
97
+ @instance.should_receive(:workers).and_return([@ldap])
98
+ @ldap.should_receive(:search).and_return(false)
99
+ @instance.search('string').should == []
100
+ end
101
+
102
+ it "should sanitize the Net::LDAP search result into an array of generic structures" do
103
+ @instance.should_receive(:workers).and_return([])
104
+ @instance.should_receive(:create_result_collection).and_return([])
105
+ @instance.search('search-string').should == []
106
+ end
107
+ end
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ module ElDap
4
+ describe Constants do
5
+ class TestThing
6
+ include ElDap::Constants
7
+ end
8
+
9
+ it "should define LDAP_ATTRS" do
10
+ TestThing.const_get('LDAP_ATTRS').
11
+ should == ['cn', 'samaccountname', 'displayname', 'name', 'telephonenumber', 'userprincipalname', 'mail']
12
+ end
13
+
14
+ it "should define LDAP_FILTERS" do
15
+ TestThing.const_get('LDAP_FILTERS').should == Net::LDAP::Filter.eq("objectcategory", "person")
16
+ end
17
+
18
+ it "should define LDAP_SEARCH_FIELD" do
19
+ TestThing.const_get('LDAP_SEARCH_FIELD').should == 'cn'
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rubygems'
4
+ require 'rspec'
5
+ require 'el_dap'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc,
8
+ # in ./support/ and its subdirectories.
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ RSpec.configure do |config|
12
+
13
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: el_dap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ed James
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-04-19 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: A simple search and authentication tool for Active Directory using LDAP.
15
+ email:
16
+ - ed.james.email@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - .rspec
23
+ - .rvmrc
24
+ - CHANGELOG
25
+ - Gemfile
26
+ - LICENSE
27
+ - README.rdoc
28
+ - Rakefile
29
+ - el_dap.gemspec
30
+ - lib/el_dap.rb
31
+ - lib/el_dap/.rvmrc
32
+ - lib/el_dap/api_methods.rb
33
+ - lib/el_dap/base.rb
34
+ - lib/el_dap/constants.rb
35
+ - lib/el_dap/version.rb
36
+ - spec/el_dap/api_methods_spec.rb
37
+ - spec/el_dap/base_spec.rb
38
+ - spec/el_dap/constants_spec.rb
39
+ - spec/spec_helper.rb
40
+ homepage: https://github.com/edjames/el_dap
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project: el_dap
60
+ rubygems_version: 1.7.2
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: A simple search and authentication tool for Active Directory using LDAP.
64
+ test_files: []