wobaduser 1.0.0
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.
- 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
|