ldap_query 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Used to initialize and container the LdapQuery modules
4
+ module LdapQuery
5
+ attr_accessor :ldap, :config
6
+
7
+ EMPTY_ARRAY = [].freeze
8
+ EMPTY_HASH = {}.freeze
9
+
10
+ @config = {}
11
+
12
+ # Reconfigure the LdapQuery credential configuration
13
+ #
14
+ # @param config_hash [Hash]
15
+ # @return [Class <LdapQuery::Config>]
16
+ def self.configure(config_hash = {})
17
+ raise(ConfigError, 'a valid configuration hash must be passed.') unless config_hash.is_a?(Hash)
18
+
19
+ # if new a new config_hash hash been passed, create a new Config instance
20
+ @config = LdapQuery::Config.new(config_hash) unless config_hash.empty?
21
+ @config
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net-ldap'
4
+
5
+ module LdapQuery
6
+ # Used to build LDAP filters and query the host based on the configuration passed
7
+ class Query
8
+ REQUIRED_QUERY_ATTRS = %i[attr val].freeze
9
+ FILTER_METHODS = %i[cn displayname memberof object_class mail samaccountname person].freeze
10
+
11
+ # Establish LDAP connection, apply filters, and return results
12
+ #
13
+ # @param credentials [Hash]
14
+ # @opts attr [String] The attribute field in ldap that you want to query again
15
+ # @opts val [String] the value you want to query ldap with
16
+ # @opts limit [Integer] the number of entries to limit the query to
17
+ # @opts wildcard [Boolean] used to determine if the filter should be wildcard match
18
+ def self.perform(credentials, attr: nil, val: nil, limit: 20, wildcard: false)
19
+ raise(AttributeError, 'a valid attribute name and value are required in order to make an ldap query.') if attr.nil? || val.nil?
20
+
21
+ config = LdapQuery::Config.new(credentials)
22
+ filter = attach_filter({ attr: attr, val: val }, wildcard: wildcard)
23
+ ldap = ldap_connection(config.hash)
24
+ entries = ldap.search(filter: filter, size: ensure_limit_set(limit))
25
+ entries.nil? ? EMPTY_ARRAY : sort_by_displayname(entries)
26
+ end
27
+
28
+ # Used to associate and LDAP filter to the connection based on the attr and value supplied
29
+ #
30
+ # @param query [Hash<{attr: attr, val: :val}]
31
+ # @attr wildcard [Boolean] used to determine if the filter should be wildcard match
32
+ def self.attach_filter(query, wildcard: false)
33
+ if FILTER_METHODS.include?(query[:attr].to_sym)
34
+ # Add the filter for the specific
35
+ LdapQuery::Filter.public_send(query[:attr], query[:val], wildcard: wildcard)
36
+ else
37
+ LdapQuery::Filter.other(query[:attr], query[:val], wildcard: wildcard)
38
+ end
39
+ end
40
+
41
+ # Establish an ldap connection with the supplied credentials
42
+ #
43
+ # @param credemntials [Hash]
44
+ # @return [Net::LDAP]
45
+ def self.ldap_connection(credentials)
46
+ LdapQuery::Connection.new(credentials).link
47
+ end
48
+
49
+ ########################################
50
+ # Sorters
51
+ ########################################
52
+ # Sort results by their displayanmes
53
+ # @param [Hash,Struct, Interface<Net::Ldap>]
54
+ # @return [Hash]
55
+ def self.sort_by_displayname(entries = [])
56
+ return EMPTY_ARRAY if entries.blank?
57
+
58
+ # the begin/rescue is in place because some service accounts are missing the displayname and causes issues when sorting
59
+ # => if they are missing this attribute they should be sorted last ie: the 'zzzzzzzzzzzz' value
60
+ entries.sort_by do |entry|
61
+ entry.respond_to?(:displayname) ? entry&.displayname.first.downcase : 'zzzzzzzzzzz'
62
+ end
63
+ end
64
+
65
+ def self.ensure_limit_set(limit = 20)
66
+ return limit if limit.is_a?(Integer) && limit.positive?
67
+
68
+ 20
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LdapQuery
4
+ # If used with a rails application, this allows the the script to pull ldap credentials from Rails.application.credentials
5
+ class RailsCredentials
6
+ attr_accessor :credentials
7
+
8
+ # Used to grab the applications encrypted credentials with the ldap key
9
+ #
10
+ # @return [Hash]
11
+ def self.credentials
12
+ return EMPTY_HASH unless rails?
13
+
14
+ @_credentials ||= Rails.application.credentials[:ldap]
15
+ rescue
16
+ # In case an older rails application is used were `Rails.application.credentials` isn't defined
17
+ raise(CredentialsError, 'Rails.application.credentials could not be found')
18
+ end
19
+
20
+ # Used to verify `Rails.application` exists within the codebase
21
+ #
22
+ # @return [Boolean]
23
+ def self.rails?
24
+ (defined?(Rails) && Rails.respond_to?(:application))
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LdapQuery
4
+ # Inject the specified helpers into the rails application helpers and controllers for ease of use
5
+ class Railtie < Rails::Railtie
6
+ initializer 'ldap.configure_rails_initialization' do |app|
7
+ app.config.to_prepare do
8
+ # Allow the LDAP gem to be acessible by default from helpers, controllers, and models
9
+
10
+ # Add the LdapQuery helpers to the rails applications controllers
11
+ if defined?(ApplicationController)
12
+ ApplicationController.include(LdapQuery::LdapHelper)
13
+ elsif defined?(ActionController::Base)
14
+ ActionController::Base.include(LdapQuery::LdapHelper)
15
+ end
16
+ # Add the LdapQuery helpers to the rails applications helpers
17
+ ApplicationHelper.include(LdapQuery::LdapHelper) if defined?(ApplicationHelper)
18
+
19
+ # Allow the LdapQuery helpers to be used in ActiveRecord/Models
20
+ if defined?(ApplicationRecord)
21
+ ApplicationRecord.include(LdapQuery::LdapHelper)
22
+ elsif defined?(ActiveRecord::Base)
23
+ ActiveRecord::Base.include(LdapQuery::LdapHelper)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LdapQuery
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe LdapQuery::Authenticate do
6
+ describe '' do
7
+ it { expect { described_class.new({}) }.to raise_error(LdapQuery::ConnectionError) }
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe LdapQuery::Config do
6
+
7
+ describe 'without valid hash' do
8
+ it { expect { described_class.new }.to raise_error(ArgumentError) }
9
+ it { expect { described_class.new({}) }.to raise_error(ArgumentError) }
10
+ it { expect { described_class.new(123) }.to raise_error(ArgumentError) }
11
+ end
12
+
13
+ describe 'should convert specific keys/values to symbols' do
14
+ subject(:config) { config = described_class.new({ david: 'friends', 'base' => '123', username: 'foo', password: 'bar', method: 'simple', host: 'company.tld' }) }
15
+
16
+ it { expect(config.method).to eq(:simple) }
17
+ end
18
+
19
+ describe 'required keys missing' do
20
+ it 'should raise with ' do
21
+ expect { described_class.new({ port: 123, david: 'friends' }) }.to raise_error(LdapQuery::ConfigError, 'required config values ([:base, :username, :password, :host, :port]) can not be nil')
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe LdapQuery::Connection do
6
+ describe 'without credentials' do
7
+ it { expect { described_class.new({}) }.to raise_error(LdapQuery::CredentialsError) }
8
+ it { expect { described_class.new({ username: 'foobar', password: 'password' })}.to raise_error(LdapQuery::CredentialsError) }
9
+ end
10
+
11
+ describe 'valid connection' do
12
+ let(:ldap_conn) { described_class.new({ port: 369, base: 'dc=company,dc=tld', host: 'company.tld', auth: { username: 'foobar', password: 'password' } }) }
13
+ it { expect(ldap_conn.link.class.name).to eq('Net::LDAP')}
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe LdapQuery::Filter do
6
+ describe 'should return filter string' do
7
+ it { expect(described_class.cn('fooBar').to_s).to eq('(cn=fooBar)') }
8
+ it { expect(described_class.person('fooBar').to_s).to eq('(&(cn=fooBar)(objectClass=person))') }
9
+ it { expect{ described_class.person }.to raise_error(ArgumentError) }
10
+
11
+ it { expect(described_class.other('someAttr', 'fooBar').to_s).to eq('(someAttr=fooBar)')}
12
+ end
13
+
14
+
15
+ describe 'clean_str' do
16
+ it { expect(described_class.cn(' bob jones').to_s).to eq('(cn=bob jones)') }
17
+ it { expect(described_class.cn(' bob jones', wildcard: true).to_s).to eq('(cn=*bob*jones*)') }
18
+
19
+ # using custome attr && value
20
+ it { expect(described_class.other(:attr, 'misc').to_s).to eq('(attr=misc)') }
21
+ it { expect(described_class.other(:attr, 'misc', wildcard: true).to_s).to eq('(attr=*misc*)') }
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe LdapQuery::Query do
6
+ describe 'attaching a filter' do
7
+ it { expect(described_class.attach_filter({ attr: 'cn', val: 'bob' }).to_s).to eq('(cn=bob)') }
8
+ it { expect(described_class.attach_filter({ attr: 'stevey', val: 'bob' }).to_s).to eq('(stevey=bob)') }
9
+ end
10
+
11
+ describe 'ensure_limit_set' do
12
+ it { expect(described_class.ensure_limit_set).to eq(20) }
13
+ it { expect(described_class.ensure_limit_set(-10)).to eq(20) }
14
+ it { expect(described_class.ensure_limit_set(0)).to eq(20) }
15
+ it { expect(described_class.ensure_limit_set(2)).to eq(2) }
16
+ it { expect(described_class.ensure_limit_set('a')).to eq(20) }
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe LdapQuery::RailsCredentials do
6
+ describe 'without rails' do
7
+ it { expect(described_class.credentials).to eq({}) }
8
+ it { expect(described_class.rails?).to be_falsey }
9
+ end
10
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[File.expand_path(File.join(File.dirname(__FILE__), 'support', '**', '*.rb'))].each { |f| require f }
4
+
5
+ require 'ldap_query'
6
+
7
+ # This file was generated by the `rspec --init` command. Conventionally, all
8
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
9
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
10
+ # this file to always be loaded, without a need to explicitly require it in any
11
+ # files.
12
+ #
13
+ # Given that it is always loaded, you are encouraged to keep this file as
14
+ # light-weight as possible. Requiring heavyweight dependencies from this file
15
+ # will add to the boot time of your test suite on EVERY test run, even for an
16
+ # individual file that may not need all of that loaded. Instead, consider making
17
+ # a separate helper file that requires the additional dependencies and performs
18
+ # the additional setup, and require it from the spec files that actually need
19
+ # it.
20
+ #
21
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
22
+ RSpec.configure do |config|
23
+ # rspec-expectations config goes here. You can use an alternate
24
+ # assertion/expectation library such as wrong or the stdlib/minitest
25
+ # assertions if you prefer.
26
+ config.expect_with :rspec do |expectations|
27
+ # This option will default to `true` in RSpec 4. It makes the `description`
28
+ # and `failure_message` of custom matchers include text for helper methods
29
+ # defined using `chain`, e.g.:
30
+ # be_bigger_than(2).and_smaller_than(4).description
31
+ # # => "be bigger than 2 and smaller than 4"
32
+ # ...rather than:
33
+ # # => "be bigger than 2"
34
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
35
+ end
36
+
37
+ # rspec-mocks config goes here. You can use an alternate test double
38
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
39
+ config.mock_with :rspec do |mocks|
40
+ # Prevents you from mocking or stubbing a method that does not exist on
41
+ # a real object. This is generally recommended, and will default to
42
+ # `true` in RSpec 4.
43
+ mocks.verify_partial_doubles = true
44
+ end
45
+
46
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
47
+ # have no way to turn it off -- the option exists only for backwards
48
+ # compatibility in RSpec 3). It causes shared context metadata to be
49
+ # inherited by the metadata hash of host groups and examples, rather than
50
+ # triggering implicit auto-inclusion in groups with matching metadata.
51
+ config.shared_context_metadata_behavior = :apply_to_host_groups
52
+
53
+ # The settings below are suggested to provide a good initial experience
54
+ # with RSpec, but feel free to customize to your heart's content.
55
+ =begin
56
+ # This allows you to limit a spec run to individual examples or groups
57
+ # you care about by tagging them with `:focus` metadata. When nothing
58
+ # is tagged with `:focus`, all examples get run. RSpec also provides
59
+ # aliases for `it`, `describe`, and `context` that include `:focus`
60
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
61
+ config.filter_run_when_matching :focus
62
+
63
+ # Allows RSpec to persist some state between runs in order to support
64
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
65
+ # you configure your source control system to ignore this file.
66
+ config.example_status_persistence_file_path = "spec/examples.txt"
67
+
68
+ # Limits the available syntax to the non-monkey patched syntax that is
69
+ # recommended. For more details, see:
70
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
71
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
72
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
73
+ config.disable_monkey_patching!
74
+
75
+ # This setting enables warnings. It's recommended, but in some cases may
76
+ # be too noisy due to issues in dependencies.
77
+ config.warnings = true
78
+
79
+ # Many RSpec users commonly either run the entire suite or an individual
80
+ # file, and it's useful to allow more verbose output when running an
81
+ # individual spec file.
82
+ if config.files_to_run.one?
83
+ # Use the documentation formatter for detailed output,
84
+ # unless a formatter has already been configured
85
+ # (e.g. via a command-line flag).
86
+ config.default_formatter = "doc"
87
+ end
88
+
89
+ # Print the 10 slowest examples and example groups at the
90
+ # end of the spec run, to help surface which specs are running
91
+ # particularly slow.
92
+ config.profile_examples = 10
93
+
94
+ # Run specs in random order to surface order dependencies. If you find an
95
+ # order dependency and want to debug it, you can fix the order by providing
96
+ # the seed, which is printed after each run.
97
+ # --seed 1234
98
+ config.order = :random
99
+
100
+ # Seed global randomization in this process using the `--seed` CLI option.
101
+ # Setting this allows you to use `--seed` to deterministically reproduce
102
+ # test failures related to randomization by passing the same `--seed` value
103
+ # as the one that triggered the failure.
104
+ Kernel.srand config.seed
105
+ =end
106
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'simplecov'
4
+
5
+ SimpleCov.start if defined?(SimpleCov)
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ldap_query
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Brandon Hicks
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-03-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-ldap
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.16'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.17'
48
+ - - "<"
49
+ - !ruby/object:Gem::Version
50
+ version: '3.0'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '1.17'
58
+ - - "<"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '13'
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '3.9'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '3.9'
89
+ description: Easily generate LDAP connections and queries without having to learn
90
+ how to build an LDAP connection with ruby.
91
+ email:
92
+ - tarellel@gmail.com
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files: []
96
+ files:
97
+ - ".fasterer"
98
+ - ".gitignore"
99
+ - ".rubocop.yml"
100
+ - ".simplecov"
101
+ - ".yardopts"
102
+ - CHANGELOG.md
103
+ - Gemfile
104
+ - Gemfile.lock
105
+ - Guardfile
106
+ - LICENSE
107
+ - README.md
108
+ - Rakefile
109
+ - bin/console
110
+ - bin/setup
111
+ - ldap_query.gemspec
112
+ - lib/ldap_query.rb
113
+ - lib/ldap_query/authenticate.rb
114
+ - lib/ldap_query/config.rb
115
+ - lib/ldap_query/connection.rb
116
+ - lib/ldap_query/error.rb
117
+ - lib/ldap_query/filter.rb
118
+ - lib/ldap_query/ldap_helper.rb
119
+ - lib/ldap_query/ldap_query.rb
120
+ - lib/ldap_query/query.rb
121
+ - lib/ldap_query/rails_credentials.rb
122
+ - lib/ldap_query/railtie.rb
123
+ - lib/ldap_query/version.rb
124
+ - spec/ldap_lookup/authenticate_spec.rb
125
+ - spec/ldap_lookup/config_spec.rb
126
+ - spec/ldap_lookup/connection_spec.rb
127
+ - spec/ldap_lookup/filter_spec.rb
128
+ - spec/ldap_lookup/query_spec.rb
129
+ - spec/ldap_lookup/rails_credentials_spec.rb
130
+ - spec/spec_helper.rb
131
+ - spec/support/simplecov.rb
132
+ homepage: https://github.com/tarellel/ldap_query
133
+ licenses:
134
+ - MIT
135
+ metadata:
136
+ homepage_uri: https://github.com/tarellel/ldap_query
137
+ source_code_uri: https://github.com/tarellel/ldap_query
138
+ changelog_uri: https://github.com/tarellel/ldap_query/blob/master/CHANGELOG.md
139
+ post_install_message:
140
+ rdoc_options: []
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: 2.4.0
148
+ required_rubygems_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ requirements: []
154
+ rubygems_version: 3.2.11
155
+ signing_key:
156
+ specification_version: 4
157
+ summary: To easily connect and query ldap
158
+ test_files: []