wobaduser 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +14 -0
- data/Rakefile +2 -0
- data/lib/string_addons.rb +5 -0
- data/lib/wobaduser.rb +19 -0
- data/lib/wobaduser/base.rb +174 -0
- data/lib/wobaduser/ldap.rb +84 -0
- data/lib/wobaduser/user.rb +72 -0
- data/lib/wobaduser/version.rb +4 -0
- data/spec/.env +15 -0
- data/spec/ldap_setup_spec.rb +78 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/user_spec.rb +126 -0
- data/wobaduser.gemspec +29 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b28543ca5d946a4e698ad38bd4b29c492a7ac1b8
|
4
|
+
data.tar.gz: adccab4655ae479b566d8fa8ff23adee912b5011
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f97d54312d98d9faa83f77079c4131f4c7aa76f7be52ebf2709d59aa4987b879599a8deff312d7fbbe60f512566e86ed5ea5b5a8fd2a2b3a71cefd0ff777aa0e
|
7
|
+
data.tar.gz: 2ffd81aa1db62405afd8eb596c06c27cb123c84ffc99fdc0b02081f6c05284a74c49dacea90492c69f0c42c91fcbcbd04bb46453be9aa7e99023f90415ed5426
|
data/.gitignore
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.*.swp
|
24
|
+
utils/
|
25
|
+
.localenv
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014-2018 Wolfgang Barth
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
wobaduser
|
2
|
+
=========
|
3
|
+
|
4
|
+
Lightweight Active Directory access.
|
5
|
+
|
6
|
+
If you are looking for a full featured solution to access and modify Active Directory, see [https://github.com/ajrkerr/active_directory](https://github.com/ajrkerr/active_directory)
|
7
|
+
|
8
|
+
Licence
|
9
|
+
-------
|
10
|
+
|
11
|
+
wobauth Copyright (C) 2014-2018 Wolfgang Barth
|
12
|
+
|
13
|
+
MIT license, see [LICENSE](LICENSE)
|
14
|
+
|
data/Rakefile
ADDED
data/lib/wobaduser.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "wobaduser/version"
|
2
|
+
require 'net/ldap'
|
3
|
+
require 'active_support/core_ext/module'
|
4
|
+
require 'active_support/core_ext/hash'
|
5
|
+
require 'string_addons'
|
6
|
+
|
7
|
+
module Wobaduser
|
8
|
+
autoload :LDAP, 'wobaduser/ldap'
|
9
|
+
autoload :Base, 'wobaduser/base'
|
10
|
+
autoload :User, 'wobaduser/user'
|
11
|
+
|
12
|
+
def self.setup
|
13
|
+
yield self
|
14
|
+
end
|
15
|
+
|
16
|
+
# timeout for ldap connections
|
17
|
+
mattr_accessor :timeout
|
18
|
+
@@timeout = 10
|
19
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# additional copyright info:
|
2
|
+
# the metaprogramming for generate_single_value_readers and
|
3
|
+
# generate_multi_value_readers was published 2008 by Ernie Miller on
|
4
|
+
# http://erniemiller.org/2008/04/04/simplified-active-directory-authentication/
|
5
|
+
# with an excellent explanation.
|
6
|
+
#
|
7
|
+
require 'immutable-struct'
|
8
|
+
|
9
|
+
module Wobaduser
|
10
|
+
class Base
|
11
|
+
SearchResult = ImmutableStruct.new( :success?, :errors, :entries )
|
12
|
+
|
13
|
+
###################################################################
|
14
|
+
# ATTR_SV is for single valued attributes only. result is a string.
|
15
|
+
# ATTR_MV is for multi valued attributes. result is an array.
|
16
|
+
# Concrete values should be set in subclasses. See Wobaduser::User
|
17
|
+
# for an example.
|
18
|
+
#
|
19
|
+
ATTR_SV={}
|
20
|
+
ATTR_MV={}
|
21
|
+
#
|
22
|
+
###################################################################
|
23
|
+
|
24
|
+
attr_reader :errors, :entry
|
25
|
+
|
26
|
+
# Create an new Wobaduser object
|
27
|
+
# not to be intended to call directly, but possible. Better to use
|
28
|
+
# Wobaduser::User.new or Wobaduser::Group.new. There are to modes:
|
29
|
+
# 1) use Wobaduser::LDAP + LDAP-Filter
|
30
|
+
# 2) use a retrieved LDAP entry, i.e. used in Wobaduser::Base.search
|
31
|
+
#
|
32
|
+
# [+:entry+] ldap entry
|
33
|
+
# [+:ldap+] instance of Wobaduser::LDAP
|
34
|
+
# [+:filter+] ldap filter (as string, *not* as Net::LDAP::Filter)
|
35
|
+
# [+:ldap_options+] additional ldap options for search
|
36
|
+
#
|
37
|
+
# :entry and (:ldap, :filter) are mutually exclusive
|
38
|
+
#
|
39
|
+
def initialize(options = {})
|
40
|
+
options.symbolize_keys!
|
41
|
+
keys = options.keys
|
42
|
+
if keys.include?(:entry) && (keys & [:ldap, :filter, :ldap_options]).any?
|
43
|
+
raise ArgumentError, ":entry and one of (:ldap, :filter, :ldap_options) are mutually exclusive!"
|
44
|
+
end
|
45
|
+
reset_errors
|
46
|
+
get_ldap_entry(options)
|
47
|
+
unless entry.nil?
|
48
|
+
self.class.class_eval do
|
49
|
+
generate_single_value_readers
|
50
|
+
generate_multi_value_readers
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.search(options = {})
|
56
|
+
search = search_ldap_entries(options)
|
57
|
+
if search.success?
|
58
|
+
entries = search.entries.map {|entry| self.new(entry: entry)}
|
59
|
+
result = SearchResult.new(success: true, errors: [], entries: entries)
|
60
|
+
else
|
61
|
+
result = SearchResult.new(success: false, errors: search.errors, entries: [])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.filter
|
66
|
+
Net::LDAP::Filter.present('objectClass')
|
67
|
+
end
|
68
|
+
|
69
|
+
def valid?
|
70
|
+
@entry.kind_of? Net::LDAP::Entry
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
#
|
76
|
+
# method generator for single value attributes defined in ATTR_SV
|
77
|
+
#
|
78
|
+
def self.generate_single_value_readers
|
79
|
+
return if ATTR_SV.nil?
|
80
|
+
self::ATTR_SV.each_pair do |k, v|
|
81
|
+
val, block = Array(v)
|
82
|
+
define_method(k) do
|
83
|
+
if @entry.attribute_names.include?(val)
|
84
|
+
attribute = @entry.send(val)
|
85
|
+
attribute = attribute.first if attribute.is_a? Array
|
86
|
+
if block.is_a?(Proc)
|
87
|
+
final = block[attribute.to_s]
|
88
|
+
else
|
89
|
+
final = attribute.to_s
|
90
|
+
end
|
91
|
+
final = final.force_encoding('UTF-8') if final.is_a? String
|
92
|
+
return final
|
93
|
+
else
|
94
|
+
return ''
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# method generator for multi value attributes defined in ATTR_MV
|
102
|
+
#
|
103
|
+
def self.generate_multi_value_readers
|
104
|
+
return if ATTR_SV.nil?
|
105
|
+
self::ATTR_MV.each_pair do |k, v|
|
106
|
+
val, block = Array(v)
|
107
|
+
define_method(k) do
|
108
|
+
if @entry.attribute_names.include?(val)
|
109
|
+
if block.is_a?(Proc)
|
110
|
+
finals = @entry.send(val).collect(&block)
|
111
|
+
else
|
112
|
+
finals = @entry.send(val)
|
113
|
+
end
|
114
|
+
finals = finals.map{|v| v.is_a?(String) ? v.to_s.force_encoding('UTF-8') : v } if finals.is_a? Array
|
115
|
+
return finals.compact
|
116
|
+
else
|
117
|
+
return []
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.search_ldap_entries(options)
|
124
|
+
ldap = options.fetch(:ldap)
|
125
|
+
if ldap.nil?
|
126
|
+
raise "ldap connection not yet available"
|
127
|
+
end
|
128
|
+
filter = options.fetch(:filter)
|
129
|
+
ldap_options = options.fetch(:ldap_options, {}).
|
130
|
+
merge(filter: build_filter(filter))
|
131
|
+
entries = ldap.search(ldap_options)
|
132
|
+
if ldap.errors.any?
|
133
|
+
result = SearchResult.new(success: false, errors: ldap.errors, entries: [])
|
134
|
+
else
|
135
|
+
result = SearchResult.new(success: true, errors: [], entries: entries)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.build_filter(filter)
|
140
|
+
unless filter.kind_of? Net::LDAP::Filter
|
141
|
+
filter = Net::LDAP::Filter.construct(filter)
|
142
|
+
end
|
143
|
+
filter & self.filter
|
144
|
+
end
|
145
|
+
|
146
|
+
protected
|
147
|
+
|
148
|
+
def add_error(message)
|
149
|
+
@errors << message
|
150
|
+
end
|
151
|
+
|
152
|
+
def reset_errors
|
153
|
+
@errors = []
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def get_ldap_entry(options)
|
159
|
+
if options.keys.include?(:entry)
|
160
|
+
@entry = options.fetch(:entry)
|
161
|
+
reset_errors
|
162
|
+
else
|
163
|
+
result = Wobaduser::Base.search_ldap_entries(options)
|
164
|
+
if result.success?
|
165
|
+
@entry = result.entries.first
|
166
|
+
else
|
167
|
+
add_error(result.errors.join(", "))
|
168
|
+
@entry = nil
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'net/ldap'
|
3
|
+
|
4
|
+
module Wobaduser
|
5
|
+
class LDAP
|
6
|
+
attr_reader :ldap_options, :errors
|
7
|
+
|
8
|
+
# Wobaduser::LDAP.new({ldap_options: {}, bind: true})
|
9
|
+
#
|
10
|
+
# [+:ldap_options+] for possible ldap options see Net::LDAP::new
|
11
|
+
# [+:bind+] true: bind on initialize, false: bind on later operations
|
12
|
+
#
|
13
|
+
def initialize(options = {})
|
14
|
+
options.symbolize_keys!
|
15
|
+
reset_errors
|
16
|
+
@ldap_options = options.fetch(:ldap_options).symbolize_keys!
|
17
|
+
do_bind = options.fetch(:bind, true)
|
18
|
+
connection(ldap_options: @ldap_options, bind: do_bind)
|
19
|
+
end
|
20
|
+
|
21
|
+
# execute ldap search operation
|
22
|
+
#
|
23
|
+
# for possible ldap options see Net::LDAP#search
|
24
|
+
#
|
25
|
+
def search(options = {})
|
26
|
+
reset_errors
|
27
|
+
options.symbolize_keys!
|
28
|
+
begin
|
29
|
+
result = connection.search(options)
|
30
|
+
add_error(operation_error)
|
31
|
+
rescue => e
|
32
|
+
result = []
|
33
|
+
add_error(e.message)
|
34
|
+
end
|
35
|
+
result
|
36
|
+
end
|
37
|
+
|
38
|
+
# returns last ldap operation error, if any
|
39
|
+
#
|
40
|
+
def operation_error
|
41
|
+
(connection.get_operation_result.code == 0) ? nil : connection.get_operation_result
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
attr_reader :connection
|
46
|
+
|
47
|
+
def add_error(message)
|
48
|
+
@errors << message
|
49
|
+
end
|
50
|
+
|
51
|
+
def reset_errors
|
52
|
+
@errors = []
|
53
|
+
end
|
54
|
+
|
55
|
+
def connected?
|
56
|
+
!!@connected
|
57
|
+
end
|
58
|
+
|
59
|
+
def connection(options ={})
|
60
|
+
return @connection if @connected
|
61
|
+
@connected = false
|
62
|
+
reset_errors
|
63
|
+
options.symbolize_keys!
|
64
|
+
@connection ||= Net::LDAP.new(options.fetch(:ldap_options))
|
65
|
+
if options.fetch(:bind, true)
|
66
|
+
begin
|
67
|
+
Timeout::timeout(Wobaduser.timeout) {
|
68
|
+
if @connection.bind
|
69
|
+
@connected = true
|
70
|
+
else
|
71
|
+
add_error(operation_error)
|
72
|
+
end
|
73
|
+
}
|
74
|
+
rescue Timeout::Error => e
|
75
|
+
add_error("Timeout: could not bind to server within #{Wobaduser.timeout} seconds")
|
76
|
+
rescue Net::LDAP::Error => e
|
77
|
+
add_error(e.message)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@connection
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Wobaduser
|
2
|
+
class User < Base
|
3
|
+
|
4
|
+
########################################################################
|
5
|
+
# ATTR_SV is for single valued attributes only. Generated readers will
|
6
|
+
# convert the value to a string before returning or calling your Proc.
|
7
|
+
|
8
|
+
ATTR_SV = {
|
9
|
+
# method name ldap attribute
|
10
|
+
:username => :userprincipalname,
|
11
|
+
:userprincipalname => :userprincipalname,
|
12
|
+
:givenname => :givenname,
|
13
|
+
:sn => :sn,
|
14
|
+
:cn => :cn,
|
15
|
+
:dn => :dn,
|
16
|
+
:displayname => :displayname,
|
17
|
+
:mail => :mail,
|
18
|
+
:title => :title,
|
19
|
+
:telephonenumber => :telephonenumber,
|
20
|
+
:facsimiletelephonenumber => :facsimiletelephonenumber,
|
21
|
+
:mobile => :mobile,
|
22
|
+
:description => :description,
|
23
|
+
:department => :department,
|
24
|
+
:company => :company,
|
25
|
+
:postalcode => :postalcode,
|
26
|
+
:l => :l,
|
27
|
+
:streetaddress => :streetaddress,
|
28
|
+
:samaccountname => :samaccountname,
|
29
|
+
:primarygroupid => :primarygroupid,
|
30
|
+
:guid => [ :objectguid, Proc.new {|p| Base64.encode64(p).chomp } ],
|
31
|
+
:useraccountcontrol => :useraccountcontrol,
|
32
|
+
:is_valid? => [ :useraccountcontrol, Proc.new {|c| (c.to_i & 2) == 0 } ],
|
33
|
+
}
|
34
|
+
|
35
|
+
# ATTR_MV is for multi-valued attributes. Generated readers will always
|
36
|
+
# return an array.
|
37
|
+
|
38
|
+
ATTR_MV = {
|
39
|
+
# method name ldap attribute
|
40
|
+
:members => :member,
|
41
|
+
:objectclass => :objectclass,
|
42
|
+
:groups => [ :memberof,
|
43
|
+
# Get the simplified name of first-level groups.
|
44
|
+
# TODO: Handle escaped special characters
|
45
|
+
Proc.new {|g| g.sub(/.*?CN=(.*?),.*/, '\1')} ],
|
46
|
+
# :mailaliases => [ :proxyAddresses, Proc.new{|p| p.lowercase.gsub(/\Asmtp:/,'')}]
|
47
|
+
:mailaliases => [ :proxyaddresses, Proc.new {|p|
|
48
|
+
p = p.downcase
|
49
|
+
next unless p=~ /\Asmtp:/
|
50
|
+
p.gsub(/\Asmtp:/, '')
|
51
|
+
}],
|
52
|
+
}
|
53
|
+
#
|
54
|
+
########################################################################
|
55
|
+
|
56
|
+
def filter(valid = false)
|
57
|
+
filter = Net::LDAP::Filter.eq('objectClass', 'user')
|
58
|
+
if valid
|
59
|
+
filter & ~(Net::LDAP::Filter.ex('UserAccountControl:1.2.840.113556.1.4.803', 2))
|
60
|
+
else
|
61
|
+
filter
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def all_groups
|
66
|
+
filter = Net::LDAP::Filter.present("cn") & Net::LDAP::Filter.eq("objectClass", "group") &
|
67
|
+
Net::LDAP::Filter.ex("member:1.2.840.113556.1.4.1941", @entry.dn)
|
68
|
+
@ldap.search(filter: filter, attributes: ['cn']).map(&:cn).flatten.map(&:as_utf8)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
data/spec/.env
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
LDAP_HOST=
|
2
|
+
LDAP_PORT=
|
3
|
+
LDAP_BASE=
|
4
|
+
LDAP_USER=
|
5
|
+
LDAP_PASSWD=
|
6
|
+
# SN must match UPN2 & UPN3
|
7
|
+
# GIVENNAME must match UPN2 & UPN3
|
8
|
+
# EMAIL must match UPN2
|
9
|
+
USERPRINCIPALNAME=
|
10
|
+
USERPRINCIPALNAME2=
|
11
|
+
USERPRINCIPALNAME3=
|
12
|
+
LDAP_SEARCH_SN=
|
13
|
+
LDAP_SEARCH_GIVENNAME=
|
14
|
+
LDAP_SEARCH_EMAIL=
|
15
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'LdapSetup' do
|
5
|
+
context "with dummy options" do
|
6
|
+
let(:ldap_options) {{ "host" => '127.0.0.1', "base" => 'dc=example,dc=com', :port => 3268}}
|
7
|
+
let(:ldap) { Wobaduser::LDAP.new(ldap_options: ldap_options, bind: false) }
|
8
|
+
let(:filter) { Net::LDAP::Filter.eq("userprincipalname", "doesnotexist") }
|
9
|
+
|
10
|
+
it "set ldap options as symbols" do
|
11
|
+
expect(ldap.ldap_options).to be_a_kind_of Hash
|
12
|
+
opts = ldap.ldap_options
|
13
|
+
expect(opts).to include(:host, :base, :port)
|
14
|
+
expect(opts).not_to include("host", "base", "port")
|
15
|
+
end
|
16
|
+
|
17
|
+
it { expect(ldap).to respond_to(:search) }
|
18
|
+
it { expect(ldap).to respond_to(:errors) }
|
19
|
+
it { expect(ldap).not_to respond_to(:connection) }
|
20
|
+
it { expect(ldap).not_to respond_to(:connected?) }
|
21
|
+
|
22
|
+
it "ldap search doesn't raise an error" do
|
23
|
+
expect{
|
24
|
+
ldap.search(filter: filter)
|
25
|
+
}.not_to raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
it "ldap search delivers some errors" do
|
29
|
+
ldap.search(filter: filter)
|
30
|
+
expect(ldap.errors.any?).to be_truthy
|
31
|
+
end
|
32
|
+
|
33
|
+
it "connect to a existing host without ldap server should get connection refused" do
|
34
|
+
ldap = nil
|
35
|
+
Wobaduser.timeout = 2
|
36
|
+
expect {
|
37
|
+
ldap = Wobaduser::LDAP.new(ldap_options: ldap_options, bind: true)
|
38
|
+
}.not_to raise_error
|
39
|
+
expect(ldap.errors.any?).to be_truthy
|
40
|
+
expect(ldap.errors.join(" ")).to match /Connection refused/
|
41
|
+
end
|
42
|
+
|
43
|
+
it "connect to a nonexistent host should timeout" do
|
44
|
+
Wobaduser.timeout = 2
|
45
|
+
ldap_options['host'] = '1.2.3.4'
|
46
|
+
ldap = Wobaduser::LDAP.new(ldap_options: ldap_options, bind: true)
|
47
|
+
expect(ldap.errors.join(" ")).to match /Timeout: could not bind to server/
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "with real environment" do
|
52
|
+
let(:ldap_options) { {
|
53
|
+
host: ENV['LDAP_HOST'],
|
54
|
+
base: ENV['LDAP_BASE'],
|
55
|
+
port: ENV['LDAP_PORT'],
|
56
|
+
auth: {
|
57
|
+
method: :simple,
|
58
|
+
username: ENV['LDAP_USER'],
|
59
|
+
password: ENV['LDAP_PASSWD'],
|
60
|
+
}
|
61
|
+
} }
|
62
|
+
let(:ldap) { Wobaduser::LDAP.new(ldap_options: ldap_options, bind: true) }
|
63
|
+
|
64
|
+
it "Wobaduser::LDAP should delegate search" do
|
65
|
+
expect(ldap).to respond_to(:search)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "search should return Net::LDAP::Entries" do
|
69
|
+
filter = Net::LDAP::Filter.eq("userprincipalname", ENV['USERPRINCIPALNAME'])
|
70
|
+
entry = ldap.search(filter: filter).first
|
71
|
+
expect(ldap.errors.any?).to be_falsey
|
72
|
+
expect(entry).to be_a_kind_of Net::LDAP::Entry
|
73
|
+
expect(entry).to respond_to(:userprincipalname)
|
74
|
+
expect(entry.userprincipalname).to include(ENV['USERPRINCIPALNAME'])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'wobaduser'
|
5
|
+
require 'dotenv'
|
6
|
+
|
7
|
+
Dotenv.load( File.expand_path(__FILE__ + '/../.localenv'),
|
8
|
+
File.expand_path(__FILE__ + '/../.env'))
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.run_all_when_everything_filtered = true
|
12
|
+
# config.filter_run :focus
|
13
|
+
config.mock_framework = :rspec
|
14
|
+
|
15
|
+
config.around(:each) do |example|
|
16
|
+
Timeout::timeout(Wobaduser.timeout + 1) {
|
17
|
+
example.run
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Run specs in random order to surface order dependencies. If you find an
|
22
|
+
# order dependency and want to debug it, you can fix the order by providing
|
23
|
+
# the seed, which is printed after each run.
|
24
|
+
# --seed 1234
|
25
|
+
config.order = 'random'
|
26
|
+
end
|
data/spec/user_spec.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'User' do
|
5
|
+
context "with different options on #new" do
|
6
|
+
let(:ldap_options) {{"host" => '127.0.0.1', "base" => 'dc=example,dc=com', :port => 3268}}
|
7
|
+
let(:ldap) { Wobaduser::LDAP.new(ldap_options: ldap_options) }
|
8
|
+
let(:entry) { instance_double("Net::LDAP::Entry") }
|
9
|
+
let(:filter) { Net::LDAP::Filter.eq("userprincipalname", "doesnotexist") }
|
10
|
+
|
11
|
+
it ":entry does not raise an ArgumentError" do
|
12
|
+
expect {
|
13
|
+
Wobaduser::User.new(entry: entry)
|
14
|
+
}.not_to raise_error
|
15
|
+
end
|
16
|
+
|
17
|
+
it ":ldap + :filter does not raise an ArgumentError" do
|
18
|
+
expect {
|
19
|
+
Wobaduser::User.new(ldap: ldap, filter: filter)
|
20
|
+
}.not_to raise_error
|
21
|
+
end
|
22
|
+
|
23
|
+
[:ldap, :filter, :ldap_options].each do |option|
|
24
|
+
it ":entry and #{option} raises an ArgumentError" do
|
25
|
+
expect {
|
26
|
+
Wobaduser::User.new(entry: entry, option => nil)
|
27
|
+
}.to raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with dummy options" do
|
33
|
+
let(:ldap_options) {{"host" => '127.0.0.1', "base" => 'dc=example,dc=com', :port => 3268}}
|
34
|
+
let(:ldap) { Wobaduser::LDAP.new(ldap_options: ldap_options, bind: false) }
|
35
|
+
let(:user) { Wobaduser::User.new(ldap: ldap, filter: filter) }
|
36
|
+
let(:filter) { Net::LDAP::Filter.eq("userprincipalname", "doesnotexist") }
|
37
|
+
|
38
|
+
it { expect(ldap.errors.any?).to be_falsey }
|
39
|
+
|
40
|
+
it { expect { user }.not_to raise_error }
|
41
|
+
it { expect(user.errors.any?).to be_truthy }
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with real environment" do
|
45
|
+
let(:ldap_options) {{
|
46
|
+
host: ENV['LDAP_HOST'],
|
47
|
+
base: ENV['LDAP_BASE'],
|
48
|
+
port: ENV['LDAP_PORT'],
|
49
|
+
auth: {
|
50
|
+
method: :simple,
|
51
|
+
username: ENV['LDAP_USER'],
|
52
|
+
password: ENV['LDAP_PASSWD'],
|
53
|
+
}
|
54
|
+
}}
|
55
|
+
let(:ldap) {Wobaduser::LDAP.new(ldap_options: ldap_options, bind: true)}
|
56
|
+
|
57
|
+
context "and valid USERPRINCIPALNAME" do
|
58
|
+
let(:filter) {Net::LDAP::Filter.eq("userprincipalname", ENV['USERPRINCIPALNAME'])}
|
59
|
+
|
60
|
+
it "valid user should respond to various attribute methods" do
|
61
|
+
user = Wobaduser::User.new(ldap: ldap, filter: filter)
|
62
|
+
expect(user.valid?).to be_truthy
|
63
|
+
expect(user).to respond_to(:userprincipalname)
|
64
|
+
expect(user.userprincipalname).to include(ENV['USERPRINCIPALNAME'])
|
65
|
+
Wobaduser::User::ATTR_SV.each do |key,value|
|
66
|
+
expect(user.send(key)).to be_a_kind_of String unless key == :is_valid?
|
67
|
+
end
|
68
|
+
Wobaduser::User::ATTR_MV.each do |key,value|
|
69
|
+
expect(user.send(key)).to be_a_kind_of Array
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "and invalid USERPRINCIPALNAME" do
|
75
|
+
let(:filter) {Net::LDAP::Filter.eq("userprincipalname", "doesnotexist")}
|
76
|
+
|
77
|
+
it "valid user should respond to various attribute methods" do
|
78
|
+
user = Wobaduser::User.new(ldap: ldap, filter: filter)
|
79
|
+
expect(user.valid?).to be_falsey
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "::search" do
|
84
|
+
let(:entries) { Wobaduser::User.search(ldap: ldap, filter: filter) }
|
85
|
+
let(:users) { entries.entries }
|
86
|
+
|
87
|
+
context "search for sn" do
|
88
|
+
let(:filter) {Net::LDAP::Filter.eq("sn", "#{ENV['LDAP_SEARCH_SN']}*")}
|
89
|
+
|
90
|
+
it { expect(entries.success?).to be_truthy }
|
91
|
+
it { expect(users).to be_a_kind_of Array }
|
92
|
+
it { expect(users.map {|u| u.userprincipalname}).to include(
|
93
|
+
ENV['USERPRINCIPALNAME3'], ENV['USERPRINCIPALNAME2']) }
|
94
|
+
end
|
95
|
+
|
96
|
+
context "search for givenname" do
|
97
|
+
let(:filter) {Net::LDAP::Filter.eq("givenname", ENV['LDAP_SEARCH_GIVENNAME'])}
|
98
|
+
|
99
|
+
it { expect(entries.success?).to be_truthy }
|
100
|
+
it { expect(users).to be_a_kind_of Array }
|
101
|
+
it { expect(users.map {|u| u.userprincipalname}).to include(
|
102
|
+
ENV['USERPRINCIPALNAME3'], ENV['USERPRINCIPALNAME2']) }
|
103
|
+
end
|
104
|
+
|
105
|
+
context "search for mail" do
|
106
|
+
let(:filter) {Net::LDAP::Filter.eq("mail", ENV['LDAP_SEARCH_EMAIL'])}
|
107
|
+
|
108
|
+
it { expect(entries.success?).to be_truthy }
|
109
|
+
it { expect(users).to be_a_kind_of Array }
|
110
|
+
it { expect(users.map {|u| u.userprincipalname}).to include(
|
111
|
+
ENV['USERPRINCIPALNAME2']) }
|
112
|
+
end
|
113
|
+
|
114
|
+
context "with invalid filter" do
|
115
|
+
let(:filter) {"bla"}
|
116
|
+
let(:entries) { Wobaduser::User.search(ldap: ldap, filter: filter) }
|
117
|
+
|
118
|
+
it "raises a FilterSyntaxInvalidError" do
|
119
|
+
expect {
|
120
|
+
entries.success?
|
121
|
+
}.to raise_error(Net::LDAP::FilterSyntaxInvalidError)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/wobaduser.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'wobaduser/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "wobaduser"
|
8
|
+
spec.version = Wobaduser::VERSION
|
9
|
+
spec.authors = ["Wolfgang Barth"]
|
10
|
+
spec.email = ["wob@swobspace.net"]
|
11
|
+
spec.summary = %q{Lightweight Active Directory LDAP read access}
|
12
|
+
spec.description = %q{Lightweight Active Directory LDAP read access}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "net-ldap"
|
22
|
+
spec.add_dependency "activesupport"
|
23
|
+
spec.add_dependency "immutable-struct"
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "> 1.6"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
spec.add_development_dependency "rspec"
|
28
|
+
spec.add_development_dependency "dotenv"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wobaduser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Wolfgang Barth
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-ldap
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: immutable-struct
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.6'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.6'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: dotenv
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Lightweight Active Directory LDAP read access
|
112
|
+
email:
|
113
|
+
- wob@swobspace.net
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rspec"
|
120
|
+
- Gemfile
|
121
|
+
- LICENSE
|
122
|
+
- README.md
|
123
|
+
- Rakefile
|
124
|
+
- lib/string_addons.rb
|
125
|
+
- lib/wobaduser.rb
|
126
|
+
- lib/wobaduser/base.rb
|
127
|
+
- lib/wobaduser/ldap.rb
|
128
|
+
- lib/wobaduser/user.rb
|
129
|
+
- lib/wobaduser/version.rb
|
130
|
+
- spec/.env
|
131
|
+
- spec/ldap_setup_spec.rb
|
132
|
+
- spec/spec_helper.rb
|
133
|
+
- spec/user_spec.rb
|
134
|
+
- utils/test.rb
|
135
|
+
- utils/test2.rb
|
136
|
+
- wobaduser.gemspec
|
137
|
+
homepage: ''
|
138
|
+
licenses:
|
139
|
+
- MIT
|
140
|
+
metadata: {}
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.6.14.1
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: Lightweight Active Directory LDAP read access
|
161
|
+
test_files:
|
162
|
+
- spec/.env
|
163
|
+
- spec/ldap_setup_spec.rb
|
164
|
+
- spec/spec_helper.rb
|
165
|
+
- spec/user_spec.rb
|