omniauth-ldap 2.3.2 → 2.3.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64fced98d7ab577e6c9abc446ace5678b829670e9d241f0a759c61efc47ecf5e
4
- data.tar.gz: 7fda93687c96509833b9d72f277995f53eb2368ebe44740180ceadd3afac6ebe
3
+ metadata.gz: 8bcc01a9129d0db019b5530a9bf5f7b5241f9eacba974d5c5585b4620a8d140b
4
+ data.tar.gz: 5eb2878ad0b0d471d60dfb4596baddb077cde9c26499ad1d2c3f661a96f1b242
5
5
  SHA512:
6
- metadata.gz: 8750eeed19ed13d89d041b14123b68cc459e7f5595d04d6d0fe67227c06c312f7952264f6fdd19a8f57957ce98c9ea3620d125fd01c445446c8848400a668e12
7
- data.tar.gz: ed9dc416b2eba5c6e9d9cc5e889f50bd70347ff4a6ce9261cb8703e77d010c7834a5a566ef8ed7f93f5fbaf4baba0afa9965191decebdf84ed8ffd8b79590a8d
6
+ metadata.gz: f3d489556fa774b9865a3138dcce9f9eef4d2afedbeff2d0bdb5ce239a3b18dd430bcdf2438b77135434e511750a50236fb0e6902ad00ce16e60d1021058fb41
7
+ data.tar.gz: 4d544ca2868b9c0eb8d283dbc996b305bd1ca3d8070c9b5d342dc3de6d8d98206f197926da1bfd9f9b57e03728f8d96932a824b540fce3f4c66efd7e8ae08630
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -32,6 +32,22 @@ Please file a bug if you notice a violation of semantic versioning.
32
32
 
33
33
  ### Security
34
34
 
35
+ ## [2.3.3] - 2025-11-10
36
+
37
+ - TAG: [v2.3.3][2.3.3t]
38
+ - COVERAGE: 97.61% -- 286/293 lines in 4 files
39
+ - BRANCH COVERAGE: 79.69% -- 102/128 branches in 4 files
40
+ - 94.44% documented
41
+
42
+ ### Added
43
+
44
+ - Documentation cleanup & updates
45
+ - YARD documentation covering 94% of the code
46
+
47
+ ### Changed
48
+
49
+ - kettle-dev v1.1.54
50
+
35
51
  ## [2.3.2] - 2025-11-06
36
52
 
37
53
  - TAG: [v2.3.2][2.3.2t]
@@ -50,14 +66,14 @@ Please file a bug if you notice a violation of semantic versioning.
50
66
  - https://datatracker.ietf.org/doc/html/draft-behera-ldap-password-policy-11
51
67
  - Support for JSON bodies
52
68
  - Support custom LDAP attributes mapping
53
- - Raise a distinct error when LDAP server is unreachable
54
- - Previously raised an invalid credentials authentication failure error, which is technically incorrect
55
69
  - Documentation of TLS verification options
56
70
 
57
71
  ### Changed
58
72
 
59
73
  - Make support for OmniAuth v1.2+ explicit
60
74
  - Versions < 1.2 do not support SCRIPT_NAME properly, and may cause other issues
75
+ - Raise a distinct error when LDAP server is unreachable
76
+ - Previously raised an invalid credentials authentication failure error, which is technically incorrect
61
77
 
62
78
  ## [2.3.1] - 2025-11-05
63
79
 
@@ -224,7 +240,9 @@ Please file a bug if you notice a violation of semantic versioning.
224
240
  [1.0.0]: https://github.com/omniauth/omniauth-ldap/compare/5656da80d4193e0d0584f44bac493a87695e580f...v1.0.0
225
241
  [1.0.0t]: https://github.com/omniauth/omniauth-ldap/releases/tag/v1.0.0
226
242
 
227
- [Unreleased]: https://github.com/omniauth/omniauth-ldap/compare/v2.3.2...HEAD
243
+ [Unreleased]: https://github.com/omniauth/omniauth-ldap/compare/v2.3.3...HEAD
244
+ [2.3.3]: https://github.com/omniauth/omniauth-ldap/compare/v2.3.2...v2.3.3
245
+ [2.3.3t]: https://github.com/omniauth/omniauth-ldap/releases/tag/v2.3.3
228
246
  [2.3.2]: https://github.com/omniauth/omniauth-ldap/compare/v2.3.1...v2.3.2
229
247
  [2.3.2t]: https://github.com/omniauth/omniauth-ldap/releases/tag/v2.3.2
230
248
  [2.3.1]: https://github.com/omniauth/omniauth-ldap/compare/v2.0.0...v2.3.1
data/CONTRIBUTING.md CHANGED
@@ -24,13 +24,13 @@ Follow these instructions:
24
24
 
25
25
  ## Executables vs Rake tasks
26
26
 
27
- Executables shipped by dependencies, such as omniauth-ldap, and stone_checksums, are available
27
+ Executables shipped by dependencies, such as kettle-dev, and stone_checksums, are available
28
28
  after running `bin/setup`. These include:
29
29
 
30
30
  - gem_checksums
31
31
  - kettle-changelog
32
32
  - kettle-commit-msg
33
- - omniauth-ldap-setup
33
+ - kettle-dev-setup
34
34
  - kettle-dvcs
35
35
  - kettle-pre-release
36
36
  - kettle-readme-backers
@@ -68,7 +68,9 @@ GitHub API and CI helpers
68
68
  Releasing and signing
69
69
  - SKIP_GEM_SIGNING: If set, skip gem signing during build/release
70
70
  - GEM_CERT_USER: Username for selecting your public cert in `certs/<USER>.pem` (defaults to $USER)
71
- - SOURCE_DATE_EPOCH: Reproducible build timestamp. `kettle-release` will set this automatically for the session.
71
+ - SOURCE_DATE_EPOCH: Reproducible build timestamp.
72
+ - `kettle-release` will set this automatically for the session.
73
+ - Not needed on bundler >= 2.7.0, as reproducible builds have become the default.
72
74
 
73
75
  Git hooks and commit message helpers (exe/kettle-commit-msg)
74
76
  - GIT_HOOK_BRANCH_VALIDATE: Branch name validation mode (e.g., `jira`) or `false` to disable
@@ -166,6 +168,7 @@ NOTE: To build without signing the gem set `SKIP_GEM_SIGNING` to any value in th
166
168
  1. Update version.rb to contain the correct version-to-be-released.
167
169
  2. Run `bundle exec kettle-changelog`.
168
170
  3. Run `bundle exec kettle-release`.
171
+ 4. Stay awake and monitor the release process for any errors, and answer any prompts.
169
172
 
170
173
  #### Manual process
171
174
 
data/FUNDING.md CHANGED
@@ -6,7 +6,7 @@ Many paths lead to being a sponsor or a backer of this project. Are you on such
6
6
 
7
7
  [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal]
8
8
 
9
- [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
9
+ [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
10
10
 
11
11
  [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
12
12
  [⛳liberapay]: https://liberapay.com/pboling/donate
@@ -27,11 +27,11 @@ Many paths lead to being a sponsor or a backer of this project. Are you on such
27
27
 
28
28
  <!-- RELEASE-NOTES-FOOTER-END -->
29
29
 
30
- # 🤑 Request for Help
30
+ # 🤑 A request for help
31
31
 
32
32
  Maintainers have teeth and need to pay their dentists.
33
- After getting laid off in an RIF in March and filled with many dozens of rejections,
34
- I'm now spending ~60+ hours a week building open source tools.
33
+ After getting laid off in an RIF in March, and encountering difficulty finding a new one,
34
+ I began spending most of my time building open source tools.
35
35
  I'm hoping to be able to pay for my kids' health insurance this month,
36
36
  so if you value the work I am doing, I need your support.
37
37
  Please consider sponsoring me or the project.
@@ -40,16 +40,13 @@ To join the community or get help 👇️ Join the Discord.
40
40
 
41
41
  [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
42
42
 
43
- To say "thanks for maintaining such a great tool" ☝️ Join the Discord or 👇️ send money.
43
+ To say "thanks!" ☝️ Join the Discord or 👇️ send money.
44
44
 
45
- [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay-img] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal-img]
45
+ [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
46
46
 
47
47
  # Another Way to Support Open Source Software
48
48
 
49
- > How wonderful it is that nobody need wait a single moment before starting to improve the world.<br/>
50
- >—Anne Frank
51
-
52
- I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions — totaling 79 hours of FLOSS coding over just the past seven days, a pretty regular week for me. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
49
+ I’m driven by a passion to foster a thriving open-source community – a space where people can tackle complex problems, no matter how small. Revitalizing libraries that have fallen into disrepair, and building new libraries focused on solving real-world challenges, are my passions. I was recently affected by layoffs, and the tech jobs market is unwelcoming. I’m reaching out here because your support would significantly aid my efforts to provide for my family, and my farm (11 🐔 chickens, 2 🐶 dogs, 3 🐰 rabbits, 8 🐈‍ cats).
53
50
 
54
51
  If you work at a company that uses my work, please encourage them to support me as a corporate sponsor. My work on gems you use might show up in `bundle fund`.
55
52
 
data/README.md CHANGED
@@ -81,7 +81,7 @@ use OmniAuth::Strategies::LDAP,
81
81
 
82
82
  All of the listed options are required, with the exception of `:title`, `:name_proc`, `:bind_dn`, and `:password`.
83
83
 
84
- ## TLS certificate verification
84
+ ### TLS certificate verification
85
85
 
86
86
  This gem enables TLS certificate verification by default when you use `encryption: "ssl"` (LDAPS / simple TLS) or `encryption: "tls"` (STARTTLS). We always pass `tls_options` to Net::LDAP based on `OpenSSL::SSL::SSLContext::DEFAULT_PARAMS`, which includes `verify_mode: OpenSSL::SSL::VERIFY_PEER` and sane defaults.
87
87
 
@@ -153,7 +153,7 @@ Compatible with MRI Ruby 2.0+, and concordant releases of JRuby, and TruffleRuby
153
153
 
154
154
  Available as part of the Tidelift Subscription.
155
155
 
156
- <details>
156
+ <details markdown="1">
157
157
  <summary>Need enterprise-level guarantees?</summary>
158
158
 
159
159
  The maintainers of this and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use.
@@ -188,7 +188,7 @@ gem install omniauth-ldap
188
188
 
189
189
  ### 🔒 Secure Installation
190
190
 
191
- <details>
191
+ <details markdown="1">
192
192
  <summary>For Medium or High Security Installations</summary>
193
193
 
194
194
  This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
@@ -668,7 +668,7 @@ Security checklist:
668
668
 
669
669
  ## 🦷 FLOSS Funding
670
670
 
671
- While these tools are free software and will always be, the project would benefit immensely from some funding.
671
+ While omniauth tools are free software and will always be, the project would benefit immensely from some funding.
672
672
  Raising a monthly budget of... "dollars" would make the project more sustainable.
673
673
 
674
674
  We welcome both individual and corporate sponsors! We also offer a
@@ -678,7 +678,7 @@ Currently, [GitHub Sponsors][🖇sponsor], and [Liberapay][⛳liberapay] are our
678
678
  **If you're working in a company that's making significant use of omniauth tools we'd
679
679
  appreciate it if you suggest to your company to become a omniauth sponsor.**
680
680
 
681
- You can support me in development of OmniAuth tools via
681
+ You can support the development of omniauth tools via
682
682
  [GitHub Sponsors][🖇sponsor],
683
683
  [Liberapay][⛳liberapay],
684
684
  [PayPal][🖇paypal],
@@ -698,7 +698,7 @@ I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed
698
698
 
699
699
  **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
700
700
 
701
- [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS or refugee efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS or refugee efforts using Patreon][🖇patreon-img]][🖇patreon]
701
+ [![Sponsor Me on Github][🖇sponsor-img]][🖇sponsor] [![Liberapay Goal Progress][⛳liberapay-img]][⛳liberapay] [![Donate on PayPal][🖇paypal-img]][🖇paypal] [![Buy me a coffee][🖇buyme-small-img]][🖇buyme] [![Donate on Polar][🖇polar-img]][🖇polar] [![Donate to my FLOSS efforts at ko-fi.com][🖇kofi-img]][🖇kofi] [![Donate to my FLOSS efforts using Patreon][🖇patreon-img]][🖇patreon]
702
702
 
703
703
  ## 🔐 Security
704
704
 
@@ -770,12 +770,11 @@ For example:
770
770
  spec.add_dependency("omniauth-ldap", "~> 1.0")
771
771
  ```
772
772
 
773
- <details>
773
+ <details markdown="1">
774
774
  <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
775
775
 
776
776
  SemVer should, IMO, but doesn't explicitly, say that dropping support for specific Platforms
777
- is a *breaking change* to an API.
778
- It is obvious to many, but not all, and since the spec is silent, the bike shedding is endless.
777
+ is a *breaking change* to an API, and for that reason the bike shedding is endless.
779
778
 
780
779
  To get a better understanding of how SemVer is intended to work over a project's lifetime,
781
780
  read this article from the creator of SemVer:
@@ -991,7 +990,7 @@ Thanks for RTFM. ☺️
991
990
  [📌gitmoji]: https://gitmoji.dev
992
991
  [📌gitmoji-img]: https://img.shields.io/badge/gitmoji_commits-%20%F0%9F%98%9C%20%F0%9F%98%8D-34495e.svg?style=flat-square
993
992
  [🧮kloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
994
- [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.297-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
993
+ [🧮kloc-img]: https://img.shields.io/badge/KLOC-0.293-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
995
994
  [🔐security]: SECURITY.md
996
995
  [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
997
996
  [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -1,14 +1,64 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "omniauth"
2
4
  require "omniauth/version"
3
5
 
6
+ # OmniAuth strategies namespace.
7
+ #
8
+ # This file implements an LDAP authentication strategy for OmniAuth.
9
+ # It provides both an interactive request phase (login form) and a
10
+ # callback phase which binds to an LDAP directory to authenticate the
11
+ # user or performs a lookup for header-based SSO.
12
+ #
13
+ # The strategy exposes a number of options (see `option` calls below)
14
+ # that control LDAP connection, mapping of LDAP attributes to the
15
+ # OmniAuth `info` hash, header-based SSO behavior, and SSL/timeouts.
16
+ #
17
+ # @example Minimal Rack mounting
18
+ # use OmniAuth::Builder do
19
+ # provider :ldap, {
20
+ # host: 'ldap.example.com',
21
+ # base: 'dc=example,dc=com'
22
+ # }
23
+ # end
24
+ #
4
25
  module OmniAuth
5
26
  module Strategies
27
+ # LDAP OmniAuth strategy
28
+ #
29
+ # This class implements the OmniAuth::Strategy interface and performs
30
+ # LDAP authentication using an `Adaptor` object. It supports three
31
+ # primary flows:
32
+ #
33
+ # - Interactive login form (request_phase) where users POST username/password
34
+ # - Callback binding where the strategy attempts to bind as the user
35
+ # - Header-based SSO (trusted upstream) where a header identifies the user
36
+ #
37
+ # The mapping from LDAP attributes to resulting `info` fields is
38
+ # configurable via the `:mapping` option. See `map_user` for the
39
+ # mapping algorithm.
40
+ #
41
+ # @see OmniAuth::Strategy
6
42
  class LDAP
43
+ # Whether the loaded OmniAuth version is >= 2.0.0; used to set default request methods.
44
+ # @return [Boolean]
7
45
  OMNIAUTH_GTE_V2 = Gem::Version.new(OmniAuth::VERSION) >= Gem::Version.new("2.0.0")
46
+
8
47
  include OmniAuth::Strategy
9
48
 
49
+ # Raised when credentials are invalid or the user cannot be authenticated.
50
+ # @example
51
+ # raise InvalidCredentialsError, 'Invalid credentials'
10
52
  InvalidCredentialsError = Class.new(StandardError)
11
53
 
54
+ # Default mapping for converting LDAP attributes to OmniAuth `info` keys.
55
+ # Keys are the resulting `info` hash keys (strings). Values may be:
56
+ # - String: single LDAP attribute name
57
+ # - Array: list of attribute names in priority order
58
+ # - Hash: pattern mapping where pattern keys contain %<n> placeholders
59
+ # that are substituted from a list of possible attribute names
60
+ #
61
+ # @return [Hash<String, String|Array|Hash>]
12
62
  option :mapping, {
13
63
  "name" => "cn",
14
64
  "first_name" => "givenName",
@@ -24,7 +74,11 @@ module OmniAuth
24
74
  "image" => "jpegPhoto",
25
75
  "description" => "description",
26
76
  }
27
- option :title, "LDAP Authentication" # default title for authentication form
77
+
78
+ # Default title shown on the login form.
79
+ # @return [String]
80
+ option :title, "LDAP Authentication"
81
+
28
82
  # For OmniAuth >= 2.0 the default allowed request method is POST only.
29
83
  # Ensure the strategy follows that default so GET /auth/:provider returns 404 as expected in tests.
30
84
  if OMNIAUTH_GTE_V2
@@ -32,6 +86,8 @@ module OmniAuth
32
86
  else
33
87
  option(:request_methods, [:get, :post])
34
88
  end
89
+
90
+ # Default LDAP connection options / behavior
35
91
  option :port, 389
36
92
  option :method, :plain
37
93
  option :disable_verify_certificates, false
@@ -39,6 +95,7 @@ module OmniAuth
39
95
  option :ssl_version, nil # use OpenSSL default if nil
40
96
  option :uid, "sAMAccountName"
41
97
  option :name_proc, lambda { |n| n }
98
+
42
99
  # Trusted header SSO support (disabled by default)
43
100
  # :header_auth - when true and the header is present, the strategy trusts the upstream gateway
44
101
  # and searches the directory for the user without requiring a user password.
@@ -46,10 +103,20 @@ module OmniAuth
46
103
  # standard Rack "HTTP_" variant automatically.
47
104
  option :header_auth, false
48
105
  option :header_name, "REMOTE_USER"
106
+
49
107
  # Optional timeouts (forwarded to Net::LDAP when supported)
50
108
  option :connect_timeout, nil
51
109
  option :read_timeout, nil
52
110
 
111
+ # Request phase: Render the login form or redirect to callback for header-auth or direct POSTed credentials
112
+ #
113
+ # This will behave differently depending on OmniAuth version and request method:
114
+ # - For OmniAuth >= 2.0 a GET to /auth/:provider should return 404 (so we return a 404 for GET requests).
115
+ # - If header-based SSO is enabled and a trusted header is present we immediately redirect to the callback.
116
+ # - If credentials are POSTed directly to /auth/:provider we redirect to the callback so the test helpers
117
+ # that populate `env['omniauth.auth']` can operate on the callback request.
118
+ #
119
+ # @return [Array] A Rack response triple from the login form or redirect.
53
120
  def request_phase
54
121
  # OmniAuth >= 2.0 expects the request phase to be POST-only for /auth/:provider.
55
122
  # Some test environments (and OmniAuth itself) enforce this by returning 404 on GET.
@@ -78,6 +145,19 @@ module OmniAuth
78
145
  f.to_response
79
146
  end
80
147
 
148
+ # Callback phase: Authenticate user or perform header-based lookup
149
+ #
150
+ # This method executes on the callback URL and implements the main
151
+ # authentication logic. There are two primary paths:
152
+ #
153
+ # - Header-based lookup: when `options[:header_auth]` is enabled and a header value is present,
154
+ # we perform a read-only directory lookup for the user and, if found, map attributes and finish.
155
+ # - Password bind: when username/password are provided we attempt a bind as the user using the adaptor.
156
+ #
157
+ # Errors raised by the LDAP adaptor are captured and turned into OmniAuth failures.
158
+ #
159
+ # @raise [InvalidCredentialsError] when credentials are invalid
160
+ # @return [Object] result of calling `super` from the OmniAuth::Strategy chain
81
161
  def callback_phase
82
162
  @adaptor = OmniAuth::LDAP::Adaptor.new(@options)
83
163
 
@@ -118,6 +198,15 @@ module OmniAuth
118
198
  end
119
199
  end
120
200
 
201
+ # Build an LDAP filter for searching/binding the user.
202
+ #
203
+ # If the adaptor has a custom `filter` option set it will be used (with
204
+ # interpolation of `%{username}`). Otherwise a simple equality filter for
205
+ # the configured uid attribute is used.
206
+ #
207
+ # @param adaptor [OmniAuth::LDAP::Adaptor] the adaptor used to build connection/filters
208
+ # @param username_override [String, nil] optional username to build the filter for (defaults to request username)
209
+ # @return [Net::LDAP::Filter] the constructed filter object
121
210
  def filter(adaptor, username_override = nil)
122
211
  flt = adaptor.filter
123
212
  if flt && !flt.to_s.empty?
@@ -128,17 +217,37 @@ module OmniAuth
128
217
  end
129
218
  end
130
219
 
131
- uid {
132
- @user_info["uid"]
133
- }
134
- info {
135
- @user_info
136
- }
137
- extra {
138
- {raw_info: @ldap_user_info}
139
- }
220
+ # The uid exposed to OmniAuth consumers.
221
+ #
222
+ # This block-based DSL is part of OmniAuth::Strategy; document the value
223
+ # returned by the block.
224
+ #
225
+ # @return [String] the user's uid as determined from the mapped info
226
+ uid { @user_info["uid"] }
227
+
228
+ # The `info` hash returned to OmniAuth consumers. Usually contains name, email, etc.
229
+ # @return [Hash<String, Object>]
230
+ info { @user_info }
231
+
232
+ # Extra information exposed under `extra[:raw_info]` containing the raw LDAP entry.
233
+ # @return [Hash{Symbol => Object}]
234
+ extra { {raw_info: @ldap_user_info} }
140
235
 
141
236
  class << self
237
+ # Map LDAP attributes from the directory entry into a simple Hash used
238
+ # for the OmniAuth `info` hash according to the provided `mapper`.
239
+ #
240
+ # The mapper supports three types of values:
241
+ # - String: a single attribute name. The method will call the attribute
242
+ # reader (downcased symbol) on the `object` and take the first value.
243
+ # - Array: iterate values and pick the first attribute that exists on the object.
244
+ # - Hash: a mapping of a pattern string to an array of attribute-name lists
245
+ # where each `%<n>` placeholder in the pattern will be substituted by the
246
+ # first available attribute from the corresponding list.
247
+ #
248
+ # @param mapper [Hash] mapping configuration (see option :mapping)
249
+ # @param object [#respond_to?, #[]] directory entry (commonly a Net::LDAP::Entry or similar)
250
+ # @return [Hash<String, Object>] the mapped user info hash
142
251
  def map_user(mapper, object)
143
252
  user = {}
144
253
  mapper.each do |key, value|
@@ -177,20 +286,38 @@ module OmniAuth
177
286
 
178
287
  protected
179
288
 
289
+ # Validate that the incoming request method is allowed.
290
+ #
291
+ # For OmniAuth >= 2.0 the default is POST only. This method checks the
292
+ # Rack env REQUEST_METHOD directly so tests and environments that stub
293
+ # request.HTTP_METHOD are handled deterministically.
294
+ #
295
+ # @return [Boolean] true when the request method is POST
180
296
  def valid_request_method?
181
297
  request.env["REQUEST_METHOD"] == "POST"
182
298
  end
183
299
 
300
+ # Determine if the request is missing required credentials.
301
+ #
302
+ # @return [Boolean] true when username or password are nil/empty
184
303
  def missing_credentials?
185
304
  request_data["username"].nil? || request_data["username"].empty? || request_data["password"].nil? || request_data["password"].empty?
186
305
  end
187
306
 
307
+ # Extract request parameters in a way compatible with Rails/Rack.
308
+ #
309
+ # @return [Hash] parameters hash containing at least "username" and "password" when provided
188
310
  def request_data
189
311
  @env["action_dispatch.request.request_parameters"] || request.params
190
312
  end
191
313
 
192
314
  # Extract a normalized username from a trusted header when enabled.
193
315
  # Returns nil when not configured or not present.
316
+ #
317
+ # The method will attempt the raw env key (e.g. "REMOTE_USER") and the Rack
318
+ # HTTP_ variant (e.g. "HTTP_REMOTE_USER" or "HTTP_X_REMOTE_USER").
319
+ #
320
+ # @return [String, nil] normalized username or nil if not present
194
321
  def header_username
195
322
  return unless options[:header_auth]
196
323
 
@@ -204,6 +331,10 @@ module OmniAuth
204
331
 
205
332
  # Perform a directory lookup for the given username using the strategy configuration
206
333
  # (bind_dn/password or anonymous). Does not attempt to bind as the user.
334
+ #
335
+ # @param adaptor [OmniAuth::LDAP::Adaptor] initialized adaptor
336
+ # @param username [String] username to look up
337
+ # @return [Object, nil] first directory entry found or nil
207
338
  def directory_lookup(adaptor, username)
208
339
  entry = nil
209
340
  search_filter = filter(adaptor, username)
@@ -216,6 +347,11 @@ module OmniAuth
216
347
 
217
348
  # If the adaptor captured a Password Policy response control, expose a minimal, stable hash
218
349
  # in the Rack env for applications to inspect.
350
+ #
351
+ # The structure is available at `request.env['omniauth.ldap.password_policy']`.
352
+ #
353
+ # @param adaptor [OmniAuth::LDAP::Adaptor]
354
+ # @return [void]
219
355
  def attach_password_policy_env(adaptor)
220
356
  return unless adaptor.respond_to?(:password_policy) && adaptor.password_policy
221
357
  ctrl = adaptor.respond_to?(:last_password_policy_response) ? adaptor.last_password_policy_response : nil
@@ -226,6 +362,10 @@ module OmniAuth
226
362
  end
227
363
 
228
364
  # Best-effort extraction across net-ldap versions; if fields are not available, returns a raw payload.
365
+ #
366
+ # @param control [Object, nil] the password policy response control if available
367
+ # @param operation [Object, nil] the last operation result if available
368
+ # @return [Hash] normalized password policy info with keys :raw, :error, :time_before_expiration, :grace_authns_remaining, :oid, :operation
229
369
  def extract_password_policy(control, operation)
230
370
  data = {raw: control}
231
371
  if control
@@ -10,12 +10,36 @@ require "sasl"
10
10
 
11
11
  module OmniAuth
12
12
  module LDAP
13
+ # Adaptor encapsulates the behavior required to connect to an LDAP server
14
+ # and perform searches and binds. It maps user-provided configuration into
15
+ # a Net::LDAP connection and provides compatibility helpers for different
16
+ # net-ldap and SASL versions. The adaptor is intentionally defensive and
17
+ # provides a small, stable API used by the OmniAuth strategy.
18
+ #
19
+ # @example Initialize with minimal config
20
+ # adaptor = OmniAuth::LDAP::Adaptor.new(base: 'dc=example,dc=com', host: 'ldap.example.com')
21
+ #
22
+ # @note Public API: {validate}, {initialize}, {bind_as}, and attr readers such as {connection}, {uid}
13
23
  class Adaptor
24
+ # Generic adaptor error super-class
25
+ # @see Error classes that inherit from this class
14
26
  class LdapError < StandardError; end
27
+
28
+ # Raised when configuration is invalid
29
+ # @example
30
+ # raise ConfigurationError, 'missing base'
15
31
  class ConfigurationError < StandardError; end
32
+
33
+ # Raised when authentication fails
16
34
  class AuthenticationError < StandardError; end
35
+
36
+ # Raised on connection-related failures
17
37
  class ConnectionError < StandardError; end
18
38
 
39
+ # Valid configuration keys accepted by the adaptor. These correspond to
40
+ # the options supported by the gem and are used during initialization.
41
+ #
42
+ # @return [Array<Symbol>]
19
43
  VALID_ADAPTER_CONFIGURATION_KEYS = [
20
44
  :hosts,
21
45
  :host,
@@ -42,7 +66,9 @@ module OmniAuth
42
66
  :ssl_version,
43
67
  ]
44
68
 
45
- # A list of needed keys. Possible alternatives are specified using sub-lists.
69
+ # Required configuration keys. This may include alternatives as sub-lists
70
+ # (e.g., [:hosts, :host] means either key is acceptable).
71
+ # @return [Array]
46
72
  MUST_HAVE_KEYS = [
47
73
  :base,
48
74
  [:encryption, :method], # :method is deprecated
@@ -51,6 +77,8 @@ module OmniAuth
51
77
  [:uid, :filter],
52
78
  ]
53
79
 
80
+ # Supported encryption method mapping for configuration readability.
81
+ # @return [Hash<Symbol,Symbol,nil>]
54
82
  ENCRYPTION_METHOD = {
55
83
  simple_tls: :simple_tls,
56
84
  start_tls: :start_tls,
@@ -62,9 +90,47 @@ module OmniAuth
62
90
  tls: :start_tls,
63
91
  }
64
92
 
93
+ # @!attribute [rw] bind_dn
94
+ # The distinguished name used for binding when provided in configuration.
95
+ # @return [String, nil]
96
+ # @!attribute [rw] password
97
+ # The bind password (may be nil for anonymous binds)
98
+ # @return [String, nil]
65
99
  attr_accessor :bind_dn, :password
100
+
101
+ # Read-only attributes exposing connection and configuration state.
102
+ # @!attribute [r] connection
103
+ # The underlying Net::LDAP connection object.
104
+ # @return [Net::LDAP]
105
+ # @!attribute [r] uid
106
+ # The user id attribute used for lookups (e.g., 'sAMAccountName')
107
+ # @return [String]
108
+ # @!attribute [r] base
109
+ # The base DN for searches.
110
+ # @return [String]
111
+ # @!attribute [r] auth
112
+ # The final auth structure used by net-ldap.
113
+ # @return [Hash]
114
+ # @!attribute [r] filter
115
+ # Custom filter pattern when provided in configuration.
116
+ # @return [String, nil]
117
+ # @!attribute [r] password_policy
118
+ # Whether to request LDAP Password Policy controls.
119
+ # @return [Boolean]
120
+ # @!attribute [r] last_operation_result
121
+ # Last operation result object returned by the ldap library (if any)
122
+ # @return [Object, nil]
123
+ # @!attribute [r] last_password_policy_response
124
+ # Last extracted password policy response control (if any)
125
+ # @return [Object, nil]
66
126
  attr_reader :connection, :uid, :base, :auth, :filter, :password_policy, :last_operation_result, :last_password_policy_response
67
127
 
128
+ # Validate that a minimal configuration is present. Raises ArgumentError when required
129
+ # keys are missing. This is a convenience to provide early feedback to callers.
130
+ #
131
+ # @param configuration [Hash] configuration hash passed to the adaptor
132
+ # @raise [ArgumentError] when required keys are missing
133
+ # @return [void]
68
134
  def self.validate(configuration = {})
69
135
  message = []
70
136
  MUST_HAVE_KEYS.each do |names|
@@ -77,6 +143,15 @@ module OmniAuth
77
143
  raise ArgumentError.new(message.join(",") + " MUST be provided") unless message.empty?
78
144
  end
79
145
 
146
+ # Create a new adaptor instance backed by a Net::LDAP connection.
147
+ #
148
+ # The constructor does not immediately open a network connection but
149
+ # prepares the Net::LDAP instance according to the provided configuration.
150
+ # It also applies timeout settings where supported by the installed net-ldap version.
151
+ #
152
+ # @param configuration [Hash] user-provided configuration options
153
+ # @raise [ArgumentError, ConfigurationError] on invalid configuration
154
+ # @return [OmniAuth::LDAP::Adaptor]
80
155
  def initialize(configuration = {})
81
156
  Adaptor.validate(configuration)
82
157
  @configuration = configuration.dup
@@ -128,6 +203,16 @@ module OmniAuth
128
203
  #:base => "dc=yourcompany, dc=com",
129
204
  # :filter => "(mail=#{user})",
130
205
  # :password => psw
206
+ #
207
+ # Attempt to locate a user entry and bind as that entry using the supplied
208
+ # password. Returns the entry on success, or false/nil on failure.
209
+ #
210
+ # @param args [Hash] search and bind options forwarded to net-ldap's search
211
+ # @option args [Net::LDAP::Filter,String] :filter LDAP filter to use
212
+ # @option args [Integer] :size maximum number of results to fetch
213
+ # @option args [String,Proc] :password a password string or callable returning a password
214
+ # @return [Net::LDAP::Entry, false, nil] the found entry on successful bind, otherwise false/nil
215
+ # @raise [ConnectionError] if the underlying LDAP search fails
131
216
  def bind_as(args = {})
132
217
  result = false
133
218
  @last_operation_result = nil
@@ -179,6 +264,9 @@ module OmniAuth
179
264
 
180
265
  private
181
266
 
267
+ # Build encryption options for Net::LDAP given the configured method
268
+ #
269
+ # @return [Hash, nil] encryption options or nil for plain (no encryption)
182
270
  def encryption_options
183
271
  translated_method = translate_method
184
272
  return unless translated_method
@@ -189,6 +277,10 @@ module OmniAuth
189
277
  }
190
278
  end
191
279
 
280
+ # Normalize the user-provided encryption/method option and map to known values.
281
+ #
282
+ # @raise [ConfigurationError] when an unknown method is provided
283
+ # @return [Symbol, nil]
192
284
  def translate_method
193
285
  method = @encryption || @method
194
286
  method ||= "plain"
@@ -203,6 +295,10 @@ module OmniAuth
203
295
  ENCRYPTION_METHOD[normalized_method]
204
296
  end
205
297
 
298
+ # Build TLS options including backward-compatibility for deprecated keys.
299
+ #
300
+ # @param translated_method [Symbol] the normalized encryption method
301
+ # @return [Hash] a hash suitable for passing as :tls_options
206
302
  def tls_options(translated_method)
207
303
  return {} if translated_method.nil? # (plain)
208
304
 
@@ -223,6 +319,10 @@ module OmniAuth
223
319
  options
224
320
  end
225
321
 
322
+ # Build a list of SASL auth structures for each requested mechanism.
323
+ #
324
+ # @param options [Hash] options such as :username and :password
325
+ # @return [Array<Hash>] list of auth structures
226
326
  def sasl_auths(options = {})
227
327
  auths = []
228
328
  sasl_mechanisms = options[:sasl_mechanisms] || @sasl_mechanisms
@@ -241,6 +341,9 @@ module OmniAuth
241
341
  auths
242
342
  end
243
343
 
344
+ # Prepare SASL DIGEST-MD5 bind details
345
+ # @param options [Hash]
346
+ # @return [Array] initial_credential and a challenge response proc
244
347
  def sasl_bind_setup_digest_md5(options)
245
348
  bind_dn = options[:username]
246
349
  initial_credential = ""
@@ -253,6 +356,9 @@ module OmniAuth
253
356
  [initial_credential, challenge_response]
254
357
  end
255
358
 
359
+ # Prepare SASL GSS-SPNEGO bind details
360
+ # @param options [Hash]
361
+ # @return [Array] initial Type1 message and a nego proc
256
362
  def sasl_bind_setup_gss_spnego(options)
257
363
  bind_dn = options[:username]
258
364
  psw = options[:password]
@@ -268,8 +374,9 @@ module OmniAuth
268
374
  [Net::NTLM::Message::Type1.new.serialize, nego]
269
375
  end
270
376
 
271
- private
272
-
377
+ # Default TLS/OpenSSL options used when not explicitly configured.
378
+ #
379
+ # @return [Hash]
273
380
  def default_options
274
381
  if @disable_verify_certificates
275
382
  # It is important to explicitly set verify_mode for two reasons:
@@ -286,6 +393,9 @@ module OmniAuth
286
393
  #
287
394
  # This gem may not always be in the context of Rails so we
288
395
  # do this rather than `.blank?`.
396
+ #
397
+ # @param hash [Hash]
398
+ # @return [Hash] sanitized hash with blank values removed
289
399
  def sanitize_hash_values(hash)
290
400
  hash.delete_if do |_, value|
291
401
  value.nil? ||
@@ -293,6 +403,10 @@ module OmniAuth
293
403
  end
294
404
  end
295
405
 
406
+ # Convert string keys to symbol keys for options hashes.
407
+ #
408
+ # @param hash [Hash]
409
+ # @return [Hash<Symbol, Object>]
296
410
  def symbolize_hash_keys(hash)
297
411
  hash.each_with_object({}) do |(key, value), result|
298
412
  result[key.to_sym] = value
@@ -300,6 +414,12 @@ module OmniAuth
300
414
  end
301
415
 
302
416
  # Capture the operation result and extract any Password Policy response control if present.
417
+ #
418
+ # This method is defensive: if the server or the installed net-ldap gem doesn't
419
+ # support controls, the method will swallow errors and leave policy fields nil.
420
+ #
421
+ # @param conn [Net::LDAP]
422
+ # @return [void]
303
423
  def capture_password_policy(conn)
304
424
  return unless @password_policy
305
425
  return unless conn.respond_to?(:get_operation_result)
@@ -1,8 +1,17 @@
1
1
  module OmniAuth
2
2
  module LDAP
3
+ # Version namespace for the omniauth-ldap gem
4
+ #
5
+ # This module contains the version constant used by rubygems and in code
6
+ # consumers. It intentionally exposes VERSION both inside the Version
7
+ # namespace and as OmniAuth::LDAP::VERSION for compatibility.
3
8
  module Version
4
- VERSION = "2.3.2"
9
+ # Public semantic version for the gem
10
+ # @return [String]
11
+ VERSION = "2.3.3"
5
12
  end
13
+ # Convenience constant for consumers that expect OmniAuth::LDAP::VERSION
14
+ # @return [String]
6
15
  VERSION = Version::VERSION # Make VERSION available in traditional way
7
16
  end
8
17
  end
data/lib/omniauth-ldap.rb CHANGED
@@ -1,3 +1,11 @@
1
+ # Integrate the VersionGem helper into the OmniAuth::LDAP::Version module
2
+ # to expose common version-related helper methods. This file is the public
3
+ # entry point required by consumers of the gem.
4
+ #
5
+ # @example
6
+ # require 'omniauth-ldap'
7
+ # OmniAuth::LDAP::VERSION # => "2.3.2"
8
+
1
9
  require "version_gem"
2
10
 
3
11
  require "omniauth-ldap/version"
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omniauth-ldap
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.2
4
+ version: 2.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Boling
@@ -337,34 +337,6 @@ dependencies:
337
337
  - - ">="
338
338
  - !ruby/object:Gem::Version
339
339
  version: 1.0.3
340
- - !ruby/object:Gem::Dependency
341
- name: vcr
342
- requirement: !ruby/object:Gem::Requirement
343
- requirements:
344
- - - ">="
345
- - !ruby/object:Gem::Version
346
- version: '4'
347
- type: :development
348
- prerelease: false
349
- version_requirements: !ruby/object:Gem::Requirement
350
- requirements:
351
- - - ">="
352
- - !ruby/object:Gem::Version
353
- version: '4'
354
- - !ruby/object:Gem::Dependency
355
- name: webmock
356
- requirement: !ruby/object:Gem::Requirement
357
- requirements:
358
- - - ">="
359
- - !ruby/object:Gem::Version
360
- version: '3'
361
- type: :development
362
- prerelease: false
363
- version_requirements: !ruby/object:Gem::Requirement
364
- requirements:
365
- - - ">="
366
- - !ruby/object:Gem::Version
367
- version: '3'
368
340
  description: "\U0001F4C1 LDAP strategy for OmniAuth."
369
341
  email:
370
342
  - floss@galtzo.com
@@ -408,10 +380,10 @@ licenses:
408
380
  - MIT
409
381
  metadata:
410
382
  homepage_uri: https://omniauth-ldap.galtzo.com/
411
- source_code_uri: https://github.com/omniauth/omniauth-ldap/tree/v2.3.2
412
- changelog_uri: https://github.com/omniauth/omniauth-ldap/blob/v2.3.2/CHANGELOG.md
383
+ source_code_uri: https://github.com/omniauth/omniauth-ldap/tree/v2.3.3
384
+ changelog_uri: https://github.com/omniauth/omniauth-ldap/blob/v2.3.3/CHANGELOG.md
413
385
  bug_tracker_uri: https://github.com/omniauth/omniauth-ldap/issues
414
- documentation_uri: https://www.rubydoc.info/gems/omniauth-ldap/2.3.2
386
+ documentation_uri: https://www.rubydoc.info/gems/omniauth-ldap/2.3.3
415
387
  funding_uri: https://github.com/sponsors/pboling
416
388
  wiki_uri: https://github.com/omniauth/omniauth-ldap/wiki
417
389
  news_uri: https://www.railsbling.com/tags/omniauth-ldap
metadata.gz.sig CHANGED
Binary file