omniauth-ldap 2.0.0 → 2.3.2

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.
data/README.md CHANGED
@@ -1,71 +1,1018 @@
1
- # OmniAuth LDAP
1
+ | 📍 NOTE |
2
+ |-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
3
+ | RubyGems (the [GitHub org][rubygems-org], not the website) [suffered][draper-security] a [hostile takeover][ellen-takeover] in September 2025. |
4
+ | Ultimately [4 maintainers][simi-removed] were [hard removed][martin-removed] and a reason has been given for only 1 of those, while 2 others resigned in protest. |
5
+ | It is a [complicated story][draper-takeover] which is difficult to [parse quickly][draper-lies]. |
6
+ | I'm adding notes like this to gems because I [don't condone theft][draper-theft] of repositories or gems from their rightful owners. |
7
+ | If a similar theft happened with my repos/gems, I'd hope some would stand up for me. |
8
+ | Disenfranchised former-maintainers have started [gem.coop][gem-coop]. |
9
+ | Once available I will publish there exclusively; unless RubyCentral makes amends with the community. |
10
+ | The ["Technology for Humans: Joel Draper"][reinteractive-podcast] podcast episode by [reinteractive][reinteractive] is the most cogent summary I'm aware of. |
11
+ | See [here][gem-naming], [here][gem-coop] and [here][martin-ann] for more info on what comes next. |
12
+ | What I'm doing: A (WIP) proposal for [bundler/gem scopes][gem-scopes], and a (WIP) proposal for a federated [gem server][gem-server]. |
2
13
 
3
- == LDAP
14
+ [rubygems-org]: https://github.com/rubygems/
15
+ [draper-security]: https://joel.drapper.me/p/ruby-central-security-measures/
16
+ [draper-takeover]: https://joel.drapper.me/p/ruby-central-takeover/
17
+ [ellen-takeover]: https://pup-e.com/blog/goodbye-rubygems/
18
+ [simi-removed]: https://www.reddit.com/r/ruby/s/gOk42POCaV
19
+ [martin-removed]: https://bsky.app/profile/martinemde.com/post/3m3occezxxs2q
20
+ [draper-lies]: https://joel.drapper.me/p/ruby-central-fact-check/
21
+ [draper-theft]: https://joel.drapper.me/p/ruby-central/
22
+ [reinteractive]: https://reinteractive.com/ruby-on-rails
23
+ [gem-coop]: https://gem.coop
24
+ [gem-naming]: https://github.com/gem-coop/gem.coop/issues/12
25
+ [martin-ann]: https://martinemde.com/2025/10/05/announcing-gem-coop.html
26
+ [gem-scopes]: https://github.com/galtzo-floss/bundle-namespace
27
+ [gem-server]: https://github.com/galtzo-floss/gem-server
28
+ [reinteractive-podcast]: https://youtu.be/_H4qbtC5qzU?si=BvuBU90R2wAqD2E6
29
+
30
+ [![Galtzo FLOSS Logo by Aboling0, CC BY-SA 4.0][🖼️galtzo-i]][🖼️galtzo-discord] [![ruby-lang Logo, Yukihiro Matsumoto, Ruby Visual Identity Team, CC BY-SA 2.5][🖼️ruby-lang-i]][🖼️ruby-lang] [![omniauth Logo (presumed to be) by tomeara, (presumed to be) MIT License][🖼️omniauth-i]][🖼️omniauth]
31
+
32
+ [🖼️galtzo-i]: https://logos.galtzo.com/assets/images/galtzo-floss/avatar-192px.svg
33
+ [🖼️galtzo-discord]: https://discord.gg/3qme4XHNKN
34
+ [🖼️ruby-lang-i]: https://logos.galtzo.com/assets/images/ruby-lang/avatar-192px.svg
35
+ [🖼️ruby-lang]: https://www.ruby-lang.org/
36
+ [🖼️omniauth-i]: https://logos.galtzo.com/assets/images/omniauth/avatar-192px.png
37
+ [🖼️omniauth]: https://github.com/omniauth/omniauth-ldap
38
+
39
+ # 📁 OmniAuth LDAP
40
+
41
+ [![Version][👽versioni]][👽version] [![GitHub tag (latest SemVer)][⛳️tag-img]][⛳️tag] [![License: MIT][📄license-img]][📄license-ref] [![Downloads Rank][👽dl-ranki]][👽dl-rank] [![Open Source Helpers][👽oss-helpi]][👽oss-help] [![CodeCov Test Coverage][🏀codecovi]][🏀codecov] [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls] [![CI Heads][🚎3-hd-wfi]][🚎3-hd-wf] [![CI Runtime Dependencies @ HEAD][🚎12-crh-wfi]][🚎12-crh-wf] [![CI Current][🚎11-c-wfi]][🚎11-c-wf] [![CI Truffle Ruby][🚎9-t-wfi]][🚎9-t-wf] [![CI JRuby][🚎10-j-wfi]][🚎10-j-wf] [![Deps Locked][🚎13-🔒️-wfi]][🚎13-🔒️-wf] [![Deps Unlocked][🚎14-🔓️-wfi]][🚎14-🔓️-wf] [![CI Supported][🚎6-s-wfi]][🚎6-s-wf] [![CI Legacy][🚎4-lg-wfi]][🚎4-lg-wf] [![CI Unsupported][🚎7-us-wfi]][🚎7-us-wf] [![CI Ancient][🚎1-an-wfi]][🚎1-an-wf] [![CI Test Coverage][🚎2-cov-wfi]][🚎2-cov-wf] [![CI Style][🚎5-st-wfi]][🚎5-st-wf] [![CodeQL][🖐codeQL-img]][🖐codeQL] [![Apache SkyWalking Eyes License Compatibility Check][🚎15-🪪-wfi]][🚎15-🪪-wf]
42
+
43
+ `if ci_badges.map(&:color).detect { it != "green"}` ☝️ [let me know][🖼️galtzo-discord], as I may have missed the [discord notification][🖼️galtzo-discord].
44
+
45
+ ---
46
+
47
+ `if ci_badges.map(&:color).all? { it == "green"}` 👇️ send money so I can do more of this. FLOSS maintenance is now my full-time job.
48
+
49
+ [![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 at ko-fi.com][🖇kofi-img]][🖇kofi]
50
+
51
+ ## 🌻 Synopsis
4
52
 
5
53
  Use the LDAP strategy as a middleware in your application:
6
54
 
7
- use OmniAuth::Strategies::LDAP,
8
- :title => "My LDAP",
9
- :host => '10.101.10.1',
10
- :port => 389,
11
- :method => :plain,
12
- :base => 'dc=intridea, dc=com',
13
- :uid => 'sAMAccountName',
14
- :name_proc => Proc.new {|name| name.gsub(/@.*$/,'')},
15
- :bind_dn => 'default_bind_dn',
16
- # Or, alternatively:
17
- #:filter => '(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))'
18
- :name_proc => Proc.new {|name| name.gsub(/@.*$/,'')}
19
- :bind_dn => 'default_bind_dn'
20
- :password => 'password'
21
-
22
- All of the listed options are required, with the exception of :title, :name_proc, :bind_dn, and :password.
23
- Allowed values of :method are: :plain, :ssl, :tls.
24
-
25
- :bind_dn and :password is the default credentials to perform user lookup.
26
- most LDAP servers require that you supply a complete DN as a binding-credential, along with an authenticator
27
- such as a password. But for many applications, you often don’t have a full DN to identify the user.
28
- You usually get a simple identifier like a username or an email address, along with a password.
29
- Since many LDAP servers don't allow anonymous access, search function will require a bound connection,
30
- :bind_dn and :password will be required for searching on the username or email to retrieve the DN attribute
31
- for the user. If the LDAP server allows anonymous access, you don't need to provide these two parameters.
32
-
33
- :uid is the LDAP attribute name for the user name in the login form.
34
- typically AD would be 'sAMAccountName' or 'UserPrincipalName', while OpenLDAP is 'uid'.
35
-
36
- :filter is the LDAP filter used to search the user entry. It can be used in place of :uid for more flexibility.
37
- `%{username}` will be replaced by the user name processed by :name_proc.
38
-
39
- :name_proc allows you to match the user name entered with the format of the :uid attributes.
40
- For example, value of 'sAMAccountName' in AD contains only the windows user name. If your user prefers using
41
- email to login, a name_proc as above will trim the email string down to just the windows login name.
42
- In summary, use :name_proc to fill the gap between the submitted username and LDAP uid attribute value.
43
-
44
- :try_sasl and :sasl_mechanisms are optional. :try_sasl [true | false], :sasl_mechanisms ['DIGEST-MD5' | 'GSS-SPNEGO']
45
- Use them to initialize a SASL connection to server. If you are not familiar with these authentication methods,
46
- please just avoid them.
47
-
48
- Direct users to '/auth/ldap' to have them authenticated via your company's LDAP server.
49
-
50
-
51
- ## License
52
-
53
- Copyright (C) 2011 by Ping Yu and Intridea, Inc.
54
-
55
- Permission is hereby granted, free of charge, to any person obtaining a copy
56
- of this software and associated documentation files (the "Software"), to deal
57
- in the Software without restriction, including without limitation the rights
58
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
59
- copies of the Software, and to permit persons to whom the Software is
60
- furnished to do so, subject to the following conditions:
61
-
62
- The above copyright notice and this permission notice shall be included in
63
- all copies or substantial portions of the Software.
64
-
65
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
66
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
67
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
68
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
69
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
70
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
71
- THE SOFTWARE.
55
+ ```ruby
56
+ use OmniAuth::Strategies::LDAP,
57
+ title: "My LDAP",
58
+ host: "10.101.10.1",
59
+ port: 389,
60
+ encryption: :plain,
61
+ base: "dc=intridea,dc=com",
62
+ uid: "sAMAccountName",
63
+ name_proc: proc { |name| name.gsub(/@.*$/, "") },
64
+ bind_dn: "default_bind_dn",
65
+ password: "password",
66
+ # Optional timeouts (seconds)
67
+ connect_timeout: 3,
68
+ read_timeout: 7,
69
+ tls_options: {
70
+ ssl_version: "TLSv1_2",
71
+ ciphers: ["AES-128-CBC", "AES-128-CBC-HMAC-SHA1", "AES-128-CBC-HMAC-SHA256"],
72
+ },
73
+ mapping: {
74
+ "name" => "cn;lang-en",
75
+ "email" => ["preferredEmail", "mail"],
76
+ "nickname" => ["uid", "userid", "sAMAccountName"],
77
+ }
78
+ # Or, alternatively:
79
+ # use OmniAuth::Strategies::LDAP, filter: '(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))'
80
+ ```
81
+
82
+ All of the listed options are required, with the exception of `:title`, `:name_proc`, `:bind_dn`, and `:password`.
83
+
84
+ ## TLS certificate verification
85
+
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
+
88
+ - Secure by default: you do not need to set anything extra to verify the LDAP server certificate.
89
+ - To customize trust or ciphers, supply your own `tls_options`, which are merged over the safe defaults.
90
+ - If you truly need to skip verification (not recommended), set `disable_verify_certificates: true`.
91
+
92
+ Examples:
93
+
94
+ ```ruby
95
+ # Verify server certs (default behavior)
96
+ use OmniAuth::Strategies::LDAP,
97
+ host: ENV["LDAP_HOST"],
98
+ port: 636,
99
+ encryption: "ssl", # or "tls"
100
+ base: "dc=example,dc=com",
101
+ uid: "uid"
102
+
103
+ # Use a private CA bundle and restrict protocol/ciphers
104
+ use OmniAuth::Strategies::LDAP,
105
+ host: ENV["LDAP_HOST"],
106
+ port: 636,
107
+ encryption: "ssl",
108
+ base: "dc=example,dc=com",
109
+ uid: "uid",
110
+ tls_options: {
111
+ ca_file: "/etc/ssl/private/my_org_ca.pem",
112
+ ssl_version: "TLSv1_2",
113
+ ciphers: ["TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256"],
114
+ }
115
+
116
+ # Opt out of verification (NOT recommended use only in trusted test/dev scenarios)
117
+ use OmniAuth::Strategies::LDAP,
118
+ host: ENV["LDAP_HOST"],
119
+ port: 636,
120
+ encryption: "ssl",
121
+ base: "dc=example,dc=com",
122
+ uid: "uid",
123
+ disable_verify_certificates: true
124
+ ```
125
+
126
+ Note: Net::LDAP historically defaulted to no certificate validation when `tls_options` were not provided. This library mitigates that by always providing secure `tls_options` unless you explicitly disable verification.
127
+
128
+ ## 💡 Info you can shake a stick at
129
+
130
+ | Tokens to Remember | [![Gem name][⛳️name-img]][⛳️gem-name] [![Gem namespace][⛳️namespace-img]][⛳️gem-namespace] |
131
+ |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
132
+ | Works with JRuby | ![JRuby 9.1 Compat][💎jruby-9.1i] ![JRuby 9.2 Compat][💎jruby-9.2i] ![JRuby 9.3 Compat][💎jruby-9.3i] <br/> [![JRuby 9.4 Compat][💎jruby-9.4i]][🚎10-j-wf] [![JRuby 10.0 Compat][💎jruby-c-i]][🚎11-c-wf] [![JRuby HEAD Compat][💎jruby-headi]][🚎3-hd-wf] |
133
+ | Works with Truffle Ruby | ![Truffle Ruby 22.3 Compat][💎truby-22.3i] ![Truffle Ruby 23.0 Compat][💎truby-23.0i] <br/> [![Truffle Ruby 23.1 Compat][💎truby-23.1i]][🚎9-t-wf] [![Truffle Ruby 24.1 Compat][💎truby-c-i]][🚎11-c-wf] |
134
+ | Works with MRI Ruby 3 | [![Ruby 3.0 Compat][💎ruby-3.0i]][🚎4-lg-wf] [![Ruby 3.1 Compat][💎ruby-3.1i]][🚎6-s-wf] [![Ruby 3.2 Compat][💎ruby-3.2i]][🚎6-s-wf] [![Ruby 3.3 Compat][💎ruby-3.3i]][🚎6-s-wf] [![Ruby 3.4 Compat][💎ruby-c-i]][🚎11-c-wf] [![Ruby HEAD Compat][💎ruby-headi]][🚎3-hd-wf] |
135
+ | Works with MRI Ruby 2 | ![Ruby 2.0 Compat][💎ruby-2.0i] ![Ruby 2.1 Compat][💎ruby-2.1i] ![Ruby 2.2 Compat][💎ruby-2.2i] <br/> [![Ruby 2.3 Compat][💎ruby-2.3i]][🚎1-an-wf] [![Ruby 2.4 Compat][💎ruby-2.4i]][🚎1-an-wf] [![Ruby 2.5 Compat][💎ruby-2.5i]][🚎1-an-wf] [![Ruby 2.6 Compat][💎ruby-2.6i]][🚎7-us-wf] [![Ruby 2.7 Compat][💎ruby-2.7i]][🚎7-us-wf] |
136
+ | Support & Community | [![Join Me on Daily.dev's RubyFriends][✉️ruby-friends-img]][✉️ruby-friends] [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite] [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork] [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor] |
137
+ | Source | [![Source on Github.com][📜src-gh-img]][📜src-gh] [![The best SHA: dQw4w9WgXcQ!][🧮kloc-img]][🧮kloc] |
138
+ | Documentation | [![Current release on RubyDoc.info][📜docs-cr-rd-img]][🚎yard-current] [![YARD on Galtzo.com][📜docs-head-rd-img]][🚎yard-head] [![Maintainer Blog][🚂maint-blog-img]][🚂maint-blog] [![GitHub Wiki][📜gh-wiki-img]][📜gh-wiki] |
139
+ | Compliance | [![License: MIT][📄license-img]][📄license-ref] [![Compatible with Apache Software Projects: Verified by SkyWalking Eyes][📄license-compat-img]][📄license-compat] [![📄ilo-declaration-img]][📄ilo-declaration] [![Security Policy][🔐security-img]][🔐security] [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct] [![SemVer 2.0.0][📌semver-img]][📌semver] |
140
+ | Style | [![Enforced Code Style Linter][💎rlts-img]][💎rlts] [![Keep-A-Changelog 1.0.0][📗keep-changelog-img]][📗keep-changelog] [![Gitmoji Commits][📌gitmoji-img]][📌gitmoji] [![Compatibility appraised by: appraisal2][💎appraisal2-img]][💎appraisal2] |
141
+ | Maintainer 🎖️ | [![Follow Me on LinkedIn][💖🖇linkedin-img]][💖🖇linkedin] [![Follow Me on Ruby.Social][💖🐘ruby-mast-img]][💖🐘ruby-mast] [![Follow Me on Bluesky][💖🦋bluesky-img]][💖🦋bluesky] [![Contact Maintainer][🚂maint-contact-img]][🚂maint-contact] [![My technical writing][💖💁🏼‍♂️devto-img]][💖💁🏼‍♂️devto] |
142
+ | `...` 💖 | [![Find Me on WellFound:][💖✌️wellfound-img]][💖✌️wellfound] [![Find Me on CrunchBase][💖💲crunchbase-img]][💖💲crunchbase] [![My LinkTree][💖🌳linktree-img]][💖🌳linktree] [![More About Me][💖💁🏼‍♂️aboutme-img]][💖💁🏼‍♂️aboutme] [🧊][💖🧊berg] [🐙][💖🐙hub] [🛖][💖🛖hut] [🧪][💖🧪lab] |
143
+
144
+ ### Compatibility
145
+
146
+ Compatible with MRI Ruby 2.0+, and concordant releases of JRuby, and TruffleRuby.
147
+
148
+ | 🚚 _Amazing_ test matrix was brought to you by | 🔎 appraisal2 🔎 and the color 💚 green 💚 |
149
+ |------------------------------------------------|--------------------------------------------------------|
150
+ | 👟 Check it out! | ✨ [github.com/appraisal-rb/appraisal2][💎appraisal2] ✨ |
151
+
152
+ ### Enterprise Support [![Tidelift](https://tidelift.com/badges/package/rubygems/omniauth-ldap)](https://tidelift.com/subscription/pkg/rubygems-omniauth-ldap?utm_source=rubygems-omniauth-ldap&utm_medium=referral&utm_campaign=readme)
153
+
154
+ Available as part of the Tidelift Subscription.
155
+
156
+ <details>
157
+ <summary>Need enterprise-level guarantees?</summary>
158
+
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.
160
+
161
+ [![Get help from me on Tidelift][🏙️entsup-tidelift-img]][🏙️entsup-tidelift]
162
+
163
+ - 💡Subscribe for support guarantees covering _all_ your FLOSS dependencies
164
+ - 💡Tidelift is part of [Sonar][🏙️entsup-tidelift-sonar]
165
+ - 💡Tidelift pays maintainers to maintain the software you depend on!<br/>📊`@`Pointy Haired Boss: An [enterprise support][🏙️entsup-tidelift] subscription is "[never gonna let you down][🧮kloc]", and *supports* open source maintainers
166
+
167
+ Alternatively:
168
+
169
+ - [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
170
+ - [![Get help from me on Upwork][👨🏼‍🏫expsup-upwork-img]][👨🏼‍🏫expsup-upwork]
171
+ - [![Get help from me on Codementor][👨🏼‍🏫expsup-codementor-img]][👨🏼‍🏫expsup-codementor]
172
+
173
+ </details>
174
+
175
+ ## ✨ Installation
176
+
177
+ Install the gem and add to the application's Gemfile by executing:
178
+
179
+ ```console
180
+ bundle add omniauth-ldap
181
+ ```
182
+
183
+ If bundler is not being used to manage dependencies, install the gem by executing:
184
+
185
+ ```console
186
+ gem install omniauth-ldap
187
+ ```
188
+
189
+ ### 🔒 Secure Installation
190
+
191
+ <details>
192
+ <summary>For Medium or High Security Installations</summary>
193
+
194
+ This gem is cryptographically signed, and has verifiable [SHA-256 and SHA-512][💎SHA_checksums] checksums by
195
+ [stone_checksums][💎stone_checksums]. Be sure the gem you install hasn’t been tampered with
196
+ by following the instructions below.
197
+
198
+ Add my public key (if you haven’t already, expires 2045-04-29) as a trusted certificate:
199
+
200
+ ```console
201
+ gem cert --add <(curl -Ls https://raw.github.com/galtzo-floss/certs/main/pboling.pem)
202
+ ```
203
+
204
+ You only need to do that once. Then proceed to install with:
205
+
206
+ ```console
207
+ gem install omniauth-ldap -P HighSecurity
208
+ ```
209
+
210
+ The `HighSecurity` trust profile will verify signed gems, and not allow the installation of unsigned dependencies.
211
+
212
+ If you want to up your security game full-time:
213
+
214
+ ```console
215
+ bundle config set --global trust-policy MediumSecurity
216
+ ```
217
+
218
+ `MediumSecurity` instead of `HighSecurity` is necessary if not all the gems you use are signed.
219
+
220
+ NOTE: Be prepared to track down certs for signed gems and add them the same way you added mine.
221
+
222
+ </details>
223
+
224
+ ## ⚙️ Configuration
225
+
226
+ The following options are available for configuring the OmniAuth LDAP strategy:
227
+
228
+ ### Required Options
229
+
230
+ - `:host` - The hostname or IP address of the LDAP server.
231
+ - `:port` - The port number of the LDAP server (default: 389).
232
+ - `:method` - The connection method. Allowed values: `:plain`, `:ssl`, `:tls` (default: `:plain`).
233
+ - `:base` - The base DN for the LDAP search.
234
+ - `:uid` or `:filter` - Either `:uid` (the LDAP attribute for username, default: "sAMAccountName") or `:filter` (LDAP filter for searching user entries). If `:filter` is provided, `:uid` is not required. Note: This `:uid` option is the search attribute, not the top-level `auth.uid` in the OmniAuth result.
235
+
236
+ ### Optional Options
237
+
238
+ - `:title` - The title for the authentication form (default: "LDAP Authentication").
239
+ - `:bind_dn` - The DN to bind with for searching users (required if anonymous access is not allowed).
240
+ - `:password` - The password for the bind DN.
241
+ - `:name_proc` - A proc to process the username before using it in the search (default: identity proc that returns the username unchanged).
242
+ - `:try_sasl` - Whether to use SASL authentication (default: false).
243
+ - `:sasl_mechanisms` - Array of SASL mechanisms to use (e.g., ["DIGEST-MD5", "GSS-SPNEGO"]).
244
+ - `:allow_anonymous` - Whether to allow anonymous binding (default: false).
245
+ - `:logger` - A logger instance for debugging (optional, for internal use).
246
+ - `:password_policy` - When true, the strategy will request the LDAP Password Policy response control (OID `1.3.6.1.4.1.42.2.27.8.5.1`) during the user bind. If the server supports it, the adaptor exposes:
247
+ - `adaptor.last_operation_result` — the last Net::LDAP operation result object.
248
+ - `adaptor.last_password_policy_response` — the matching password policy response control (implementation-specific object). This can indicate conditions such as password expired, account locked, reset required, or grace logins remaining (per the draft RFC).
249
+ - `:connect_timeout` - Maximum time in seconds to wait when establishing the TCP connection to the LDAP server. Forwarded to `Net::LDAP`.
250
+ - `:read_timeout` - Maximum time in seconds to wait for reads during LDAP operations (search/bind). Forwarded to `Net::LDAP`.
251
+ - `:mapping` - Customize how LDAP attributes map to the returned `auth.info` hash. A sensible default mapping is built into the strategy and will be merged with your overrides. See `lib/omniauth/strategies/ldap.rb` for the default keys and behavior; values can be a String (single attribute), an Array (first present attribute wins), or a Hash (string pattern with placeholders like `%0` combined from multiple attributes).
252
+
253
+ Example enabling password policy:
254
+
255
+ ```ruby
256
+ use OmniAuth::Builder do
257
+ provider :ldap,
258
+ host: "ldap.example.com",
259
+ base: "dc=example,dc=com",
260
+ uid: "uid",
261
+ bind_dn: "cn=search,dc=example,dc=com",
262
+ password: ENV["LDAP_SEARCH_PASSWORD"],
263
+ password_policy: true
264
+ end
265
+ ```
266
+
267
+ Note: This is best-effort and compatible with a range of net-ldap versions. If your server supports the control, you can inspect the response via the `adaptor` instance during/after authentication (for example in a failure handler) to tailor error messages.
268
+
269
+ ### Auth Hash UID vs LDAP :uid (search attribute)
270
+
271
+ - By design, the top-level `auth.uid` returned by this strategy is the entry's Distinguished Name (DN).
272
+ - The configuration option `:uid` controls which LDAP attribute is used to locate the entry (or to build the filter), not the value exposed as `auth.uid`.
273
+ - Your LDAP "account name" (for example, `sAMAccountName` on Active Directory or `uid` on many schemas) is exposed via `auth.info.nickname` and is also available in `auth.extra.raw_info`.
274
+
275
+ Why DN for `auth.uid`?
276
+
277
+ - DN is the canonical, globally unique identifier for an LDAP entry and is always present in search results. See LDAPv3 and DN syntax: [RFC 4511][rfc4511] (LDAP protocol) and [RFC 4514][rfc4514] (String Representation of Distinguished Names).
278
+ - Attributes like `uid` (defined in [RFC 4519][rfc4519]) or `sAMAccountName` (Active Directory–specific) may be absent, duplicated across parts of the DIT, or vary between directories. Using DN ensures consistent behavior across AD, OpenLDAP, and other servers.
279
+ - This trade-off favors cross-directory interoperability and stability for apps that need a unique identifier.
280
+
281
+ Where to find the "username"-style value
282
+
283
+ - `auth.info.nickname` maps from the first present of: `uid`, `userid`, or `sAMAccountName`.
284
+ - You can also read the raw attribute from `auth.extra.raw_info` (a `Net::LDAP::Entry`):
285
+
286
+ ```ruby
287
+ get "/auth/ldap/callback" do
288
+ auth = request.env["omniauth.auth"]
289
+ dn = auth.uid # => "cn=alice,ou=users,dc=example,dc=com"
290
+ username = auth.info.nickname # => "alice" (from uid/sAMAccountName)
291
+ # Or, directly from raw_info (case-insensitive keys):
292
+ sams = auth.extra.raw_info[:samaccountname]
293
+ sam = sams.first if sams
294
+ # ...
295
+ end
296
+ ```
297
+
298
+ If you need top-level `auth.uid` to be something other than the DN (for example, `sAMAccountName`), you'll currently need to read it from `auth.info.nickname` (or `raw_info`) in your app. Changing the top-level `uid` mapping would be a breaking behavior change for existing users; if you have a use-case, please open an issue to discuss a configurable mapping.
299
+
300
+ ## 🔧 Basic Usage
301
+
302
+ The strategy exposes a simple Rack middleware and can be used in plain Rack apps, Sinatra, or Rails.
303
+ Direct users to `/auth/ldap` to start authentication and handle the callback at `/auth/ldap/callback`.
304
+
305
+ Below are several concrete examples to get you started.
306
+
307
+ ### Minimal Rack setup
308
+
309
+ ```ruby
310
+ # config.ru
311
+ require "rack"
312
+ require "omniauth-ldap"
313
+
314
+ use Rack::Session::Cookie, secret: "change_me"
315
+ use OmniAuth::Builder do
316
+ provider :ldap,
317
+ host: "ldap.example.com",
318
+ port: 389,
319
+ method: :plain,
320
+ base: "dc=example,dc=com",
321
+ uid: "uid",
322
+ title: "Example LDAP"
323
+ end
324
+
325
+ run lambda { |env| [404, {"Content-Type" => "text/plain"}, [env.key?("omniauth.auth").to_s]] }
326
+ ```
327
+
328
+ Visit `GET /auth/ldap` to initiate authentication (the middleware will render a login form unless you POST to `/auth/ldap`).
329
+
330
+ ### Sinatra example
331
+
332
+ ```ruby
333
+ require "sinatra"
334
+ require "omniauth-ldap"
335
+
336
+ use Rack::Session::Cookie, secret: "change_me"
337
+ use OmniAuth::Builder do
338
+ provider :ldap,
339
+ title: "Company LDAP",
340
+ host: "ldap.company.internal",
341
+ base: "dc=company,dc=local",
342
+ uid: "sAMAccountName",
343
+ name_proc: proc { |username| username.gsub(/@.*$/, "") }
344
+ end
345
+
346
+ get "/" do
347
+ '<a href="/auth/ldap">Sign in with LDAP</a>'
348
+ end
349
+
350
+ get "/auth/ldap/callback" do
351
+ auth = request.env["omniauth.auth"]
352
+ "Hello, #{auth.info["name"]}"
353
+ end
354
+ ```
355
+
356
+ ### Rails (initializer) example
357
+
358
+ Create `config/initializers/omniauth.rb`:
359
+
360
+ ```ruby
361
+ Rails.application.config.middleware.use(OmniAuth::Builder) do
362
+ provider :ldap,
363
+ title: "Acme LDAP",
364
+ host: "ldap.acme.internal",
365
+ port: 389,
366
+ base: "dc=acme,dc=corp",
367
+ uid: "uid",
368
+ bind_dn: "cn=search,dc=acme,dc=corp",
369
+ password: ENV["LDAP_SEARCH_PASSWORD"],
370
+ name_proc: proc { |n| n.split("@").first }
371
+ end
372
+ ```
373
+
374
+ Then link users to `/auth/ldap` in your app (for example, in a Devise sign-in page).
375
+
376
+ ### Use JSON Body
377
+
378
+ This gem is compatible with JSON-encoded POST bodies as well as traditional form-encoded.
379
+
380
+ - Set header `Content-Type` to `application/json`.
381
+ - Send a JSON object containing `username` and `password`.
382
+ - Rails automatically exposes parsed JSON params via `env["action_dispatch.request.request_parameters"]`, which this strategy reads first. In non-Rails Rack apps, ensure you use a JSON parser middleware if you post raw JSON.
383
+
384
+ Examples
385
+
386
+ - curl (JSON):
387
+
388
+ ```bash
389
+ curl -i \
390
+ -X POST \
391
+ -H 'Content-Type: application/json' \
392
+ -d '{"username":"alice","password":"secret"}' \
393
+ http://localhost:3000/auth/ldap
394
+ ```
395
+
396
+ The request phase will redirect to `/auth/ldap/callback` when both fields are present.
397
+
398
+ - curl (form-encoded, still supported):
399
+
400
+ ```bash
401
+ curl -i \
402
+ -X POST \
403
+ -H 'Content-Type: application/x-www-form-urlencoded' \
404
+ --data-urlencode 'username=alice' \
405
+ --data-urlencode 'password=secret' \
406
+ http://localhost:3000/auth/ldap
407
+ ```
408
+
409
+ - Browser (JavaScript fetch):
410
+
411
+ ```js
412
+ fetch('/auth/ldap', {
413
+ method: 'POST',
414
+ headers: { 'Content-Type': 'application/json' },
415
+ body: JSON.stringify({ username: 'alice', password: 'secret' })
416
+ }).then(res => {
417
+ if (res.redirected) {
418
+ window.location = res.url; // typically /auth/ldap/callback
419
+ }
420
+ });
421
+ ```
422
+
423
+ Notes
424
+
425
+ - You can still initiate authentication by visiting `GET /auth/ldap` to render the HTML form and then submitting it (form-encoded). JSON is an additional option, not a replacement.
426
+ - In the callback phase (`POST /auth/ldap/callback`), the strategy reads JSON credentials the same way; Rails exposes them via `action_dispatch.request.request_parameters` and non-Rails apps should use a JSON parser middleware.
427
+
428
+ ### Using a custom filter
429
+
430
+ If you need to restrict authentication to a group or use a more complex lookup, pass `:filter`. Use `%{username}` — it will be replaced with the processed username (after `:name_proc`).
431
+
432
+ ```ruby
433
+ provider :ldap,
434
+ host: "ldap.example.com",
435
+ base: "dc=example,dc=com",
436
+ filter: "(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))",
437
+ bind_dn: "cn=search,dc=example,dc=com",
438
+ password: ENV["LDAP_SEARCH_PASSWORD"]
439
+ ```
440
+
441
+ What `:filter` actually does
442
+
443
+ - If `:filter` is provided, the strategy constructs an LDAP filter string by substituting `%{username}` with the submitted username after applying `:name_proc`, escaping special characters per RFC 4515, and passes it to the directory search.
444
+ - In the normal password flow, a successful search returns the user's DN and we then bind as that DN with the submitted password.
445
+ - In trusted header SSO flow (`header_auth: true`), we only perform the search and skip the user password bind; if the search returns no entry, authentication fails.
446
+ - If `:filter` is not provided, the strategy falls back to a simple equality filter using `:uid` (e.g. `(uid=alice)`).
447
+
448
+ Notes on escaping and safety
449
+
450
+ - We escape the interpolated username with `Net::LDAP::Filter.escape`, which protects against LDAP injection and handles special characters like `(`, `)`, `*`, and `\`.
451
+ - Your static filter text is used as-is — keep it to a valid LDAP filter expression and only use `%{username}` for substitution.
452
+
453
+ Group-based recipes
454
+
455
+ - Active Directory (simple group):
456
+
457
+ ```text
458
+ (&(sAMAccountName=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))
459
+ ```
460
+
461
+ - Active Directory (nested groups via matchingRuleInChain):
462
+
463
+ ```text
464
+ (&(sAMAccountName=%{username})(memberOf:1.2.840.113556.1.4.1941:=cn=myapp-users,ou=groups,dc=example,dc=com))
465
+ ```
466
+
467
+ - OpenLDAP (groupOfNames):
468
+
469
+ ```text
470
+ (&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))
471
+ ```
472
+
473
+ or, if you can't use `memberOf` overlays, filter on the group and member DN:
474
+
475
+ ```text
476
+ (&(uid=%{username})(|(uniqueMember=uid=%{username},ou=people,dc=example,dc=com)(member=uid=%{username},ou=people,dc=example,dc=com)))
477
+ ```
478
+
479
+ Username normalization examples
480
+
481
+ - If your users sign in with an email but the directory expects a short name, combine `:name_proc` with `:filter`:
482
+
483
+ ```ruby
484
+ provider :ldap,
485
+ name_proc: proc { |n| n.split("@").first },
486
+ filter: "(&(sAMAccountName=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))"
487
+ # other settings...
488
+ ```
489
+
490
+ Discourse plugin (jonmbake/discourse-ldap-auth)
491
+
492
+ - That plugin forwards its `filter` setting to this gem. You can therefore paste the same filter strings shown above.
493
+ - Example (allow only members of `forum-users`):
494
+
495
+ ```text
496
+ (&(uid=%{username})(memberOf=cn=forum-users,ou=groups,dc=example,dc=com))
497
+ ```
498
+
499
+ - If users type an email address but your directory matches on a short user id, also configure `name_proc` accordingly in your app (or the plugin, if supported).
500
+
501
+ ### SASL (advanced)
502
+
503
+ SASL enables alternative bind mechanisms. Only enable if you understand the server-side requirements.
504
+
505
+ ```ruby
506
+ provider :ldap,
507
+ host: "ldap.example.com",
508
+ base: "dc=example,dc=com",
509
+ try_sasl: true,
510
+ sasl_mechanisms: ["DIGEST-MD5"],
511
+ uid: "uid"
512
+ ```
513
+
514
+ Supported mechanisms include "DIGEST-MD5" and "GSS-SPNEGO" depending on your environment and gems.
515
+
516
+ ### Name processing and examples
517
+
518
+ If users log in with an email but LDAP expects a short username, use `:name_proc` to normalize the submitted value:
519
+
520
+ ```ruby
521
+ provider :ldap,
522
+ host: "ldap.example.com",
523
+ base: "dc=example,dc=com",
524
+ uid: "sAMAccountName",
525
+ name_proc: proc { |name| name.gsub(/@.*$/, "") }
526
+ ```
527
+
528
+ This trims `alice@example.com` to `alice` before searching.
529
+
530
+ ### Mounted under a subdirectory (SCRIPT_NAME)
531
+
532
+ If your app is served from a path prefix (for example, behind a reverse proxy at `/myapp`, or mounted via Rack::URLMap, or Rails `relative_url_root`), the OmniAuth callback must include that subdirectory. This strategy uses `callback_url` for the form action and redirects, so it automatically includes any `SCRIPT_NAME` set by Rack/Rails. In other words, you typically do not need any special configuration beyond ensuring `SCRIPT_NAME` is correct in the request environment.
533
+
534
+ - Works out-of-the-box when:
535
+ - You mount the app at a path using Rack’s `map`/`URLMap`.
536
+ - You set Rails’ `config.relative_url_root` (or `RAILS_RELATIVE_URL_ROOT`) or deploy under a prefix with a reverse proxy that sets `SCRIPT_NAME`.
537
+
538
+ Rack example (mounted at /myapp):
539
+
540
+ ```ruby
541
+ # config.ru
542
+ require "rack"
543
+ require "omniauth-ldap"
544
+
545
+ app = Rack::Builder.new do
546
+ use(Rack::Session::Cookie, secret: "change_me")
547
+ use(OmniAuth::Builder) do
548
+ provider(
549
+ :ldap,
550
+ host: "ldap.example.com",
551
+ base: "dc=example,dc=com",
552
+ uid: "uid",
553
+ title: "Example LDAP",
554
+ )
555
+ end
556
+
557
+ run(->(env) { [404, {"Content-Type" => "text/plain"}, [env.key?("omniauth.auth").to_s]] })
558
+ end
559
+
560
+ run Rack::URLMap.new(
561
+ "/myapp" => app,
562
+ )
563
+ ```
564
+
565
+ - Visiting `POST /myapp/auth/ldap` renders the login form with `action='http://host/myapp/auth/ldap/callback'`.
566
+ - Any redirects (including header-based SSO fast path) will also point to `http://host/myapp/auth/ldap/callback`.
567
+
568
+ Rails example (relative_url_root):
569
+
570
+ ```ruby
571
+ # config/environments/production.rb (or an initializer)
572
+ Rails.application.configure do
573
+ config.relative_url_root = "/myapp" # or set ENV["RAILS_RELATIVE_URL_ROOT"]
574
+ end
575
+
576
+ # config/initializers/omniauth.rb
577
+ Rails.application.config.middleware.use(OmniAuth::Builder) do
578
+ provider :ldap,
579
+ title: "Acme LDAP",
580
+ host: "ldap.acme.internal",
581
+ base: "dc=acme,dc=corp",
582
+ uid: "uid",
583
+ bind_dn: "cn=search,dc=acme,dc=corp",
584
+ password: ENV["LDAP_SEARCH_PASSWORD"],
585
+ name_proc: proc { |n| n.split("@").first }
586
+ end
587
+ ```
588
+
589
+ - With `relative_url_root` set, Rails/Rack provide `SCRIPT_NAME=/myapp`, and this strategy will issue a form with `action='.../myapp/auth/ldap/callback'` and redirect accordingly.
590
+
591
+ Behind proxies with unusual host/proto handling (optional):
592
+
593
+ OmniAuth usually derives the correct scheme/host/prefix from Rack (and standard `X-Forwarded-*` headers). If your environment produces incorrect absolute URLs, you can override the computed host and prefix by setting `OmniAuth.config.full_host`:
594
+
595
+ ```ruby
596
+ OmniAuth.config.full_host = lambda do |env|
597
+ scheme = (env["HTTP_X_FORWARDED_PROTO"] || env["rack.url_scheme"]).to_s.split(",").first
598
+ host = env["HTTP_X_FORWARDED_HOST"] || env["HTTP_HOST"] || [env["SERVER_NAME"], env["SERVER_PORT"]].compact.join(":")
599
+ script = env["SCRIPT_NAME"].to_s
600
+ "#{scheme}://#{host}#{script}"
601
+ end
602
+ ```
603
+
604
+ Note: You generally do not need this override. Prefer configuring your proxy to pass standard `X-Forwarded-Proto` and `X-Forwarded-Host` headers and let Rack/OmniAuth compute the full URL.
605
+
606
+ - Header-based SSO (`header_auth: true`) also respects `SCRIPT_NAME`; when a trusted header is present on `POST /myapp/auth/ldap`, the strategy redirects to `http://host/myapp/auth/ldap/callback`.
607
+
608
+ ### Trusted header SSO (REMOTE_USER and friends)
609
+
610
+ Some deployments terminate SSO at a reverse proxy or portal and forward the already-authenticated user identity via an HTTP header such as `REMOTE_USER`.
611
+ When you enable this mode, the LDAP strategy will trust the upstream header, perform a directory lookup for that user, and complete OmniAuth without asking the user for a password.
612
+
613
+ Important: Only enable this behind a trusted front-end that strips and sets the header itself. Never enable on a public endpoint without such a gateway, or an attacker could spoof the header.
614
+
615
+ Configuration options:
616
+
617
+ - `:header_auth` (Boolean, default: false) — Enable trusted header SSO.
618
+ - `:header_name` (String, default: "REMOTE_USER") — The env/header key to read. The strategy checks both `env["REMOTE_USER"]` and the Rack variant `env["HTTP_REMOTE_USER"]`.
619
+ - `:name_proc` is applied to the header value before search (e.g., to strip a domain part).
620
+ - Search is done using your configured `:uid` or `:filter` and the service bind (`:bind_dn`/`:password`) or anonymous bind if allowed.
621
+
622
+ Minimal Rack example:
623
+
624
+ ```ruby
625
+ use OmniAuth::Builder do
626
+ provider :ldap,
627
+ host: "ldap.example.com",
628
+ base: "dc=example,dc=com",
629
+ uid: "uid",
630
+ bind_dn: "cn=search,dc=example,dc=com",
631
+ password: ENV["LDAP_SEARCH_PASSWORD"],
632
+ header_auth: true, # trust REMOTE_USER
633
+ header_name: "REMOTE_USER", # default
634
+ name_proc: proc { |n| n.split("@").first }
635
+ end
636
+ ```
637
+
638
+ Rails initializer example:
639
+
640
+ ```ruby
641
+ Rails.application.config.middleware.use(OmniAuth::Builder) do
642
+ provider :ldap,
643
+ title: "Acme LDAP",
644
+ host: "ldap.acme.internal",
645
+ base: "dc=acme,dc=corp",
646
+ uid: "sAMAccountName",
647
+ bind_dn: "cn=search,dc=acme,dc=corp",
648
+ password: ENV["LDAP_SEARCH_PASSWORD"],
649
+ header_auth: true,
650
+ header_name: "REMOTE_USER",
651
+ # Optionally restrict with a group filter while using the header value
652
+ filter: "(&(sAMAccountName=%{username})(memberOf=cn=myapp-users,ou=groups,dc=acme,dc=corp))",
653
+ name_proc: proc { |n| n.gsub(/@.*$/, "") }
654
+ end
655
+ ```
656
+
657
+ Flow:
658
+
659
+ - If `header_auth` is on and the header is present when the request hits `/auth/ldap`, the strategy immediately redirects to `/auth/ldap/callback`.
660
+ - In the callback, the strategy searches the directory for that user and maps their attributes; no user password bind is attempted.
661
+ - If the header is missing (or `header_auth` is false), the normal username/password form flow is used.
662
+
663
+ Security checklist:
664
+
665
+ - Ensure your reverse proxy strips user-controlled copies of the header and sets the canonical `REMOTE_USER` itself.
666
+ - Prefer TLS-secured internal links between the proxy and your app.
667
+ - Consider also restricting with a group-based `:filter` so only authorized users can sign in.
668
+
669
+ ## 🦷 FLOSS Funding
670
+
671
+ While these tools are free software and will always be, the project would benefit immensely from some funding.
672
+ Raising a monthly budget of... "dollars" would make the project more sustainable.
673
+
674
+ We welcome both individual and corporate sponsors! We also offer a
675
+ wide array of funding channels to account for your preferences.
676
+ Currently, [GitHub Sponsors][🖇sponsor], and [Liberapay][⛳liberapay] are our preferred funding platforms.
677
+
678
+ **If you're working in a company that's making significant use of omniauth tools we'd
679
+ appreciate it if you suggest to your company to become a omniauth sponsor.**
680
+
681
+ You can support me in development of OmniAuth tools via
682
+ [GitHub Sponsors][🖇sponsor],
683
+ [Liberapay][⛳liberapay],
684
+ [PayPal][🖇paypal],
685
+ and [Tidelift][🏙️entsup-tidelift].
686
+
687
+ | 📍 NOTE |
688
+ |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
689
+ | If doing a sponsorship in the form of donation is problematic for your company <br/> from an accounting standpoint, we'd recommend the use of Tidelift, <br/> where you can get a support-like subscription instead. |
690
+
691
+ ### Another way to support open-source
692
+
693
+ 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).
694
+
695
+ 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`.
696
+
697
+ I’m developing a new library, [floss_funding][🖇floss-funding-gem], designed to empower open-source developers like myself to get paid for the work we do, in a sustainable way. Please give it a look.
698
+
699
+ **[Floss-Funding.dev][🖇floss-funding.dev]: 👉️ No network calls. 👉️ No tracking. 👉️ No oversight. 👉️ Minimal crypto hashing. 💡 Easily disabled nags**
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]
702
+
703
+ ## 🔐 Security
704
+
705
+ See [SECURITY.md][🔐security].
706
+
707
+ ## 🤝 Contributing
708
+
709
+ If you need some ideas of where to help, you could work on adding more code coverage,
710
+ or if it is already 💯 (see [below](#code-coverage)) check [reek](REEK), [issues][🤝gh-issues], or [PRs][🤝gh-pulls],
711
+ or use the gem and think about how it could be better.
712
+
713
+ We [![Keep A Changelog][📗keep-changelog-img]][📗keep-changelog] so if you make changes, remember to update it.
714
+
715
+ See [CONTRIBUTING.md][🤝contributing] for more detailed instructions.
716
+
717
+ ### 🚀 Release Instructions
718
+
719
+ See [CONTRIBUTING.md][🤝contributing].
720
+
721
+ ### Code Coverage
722
+
723
+ [![Coverage Graph][🏀codecov-g]][🏀codecov]
724
+
725
+ [![Coveralls Test Coverage][🏀coveralls-img]][🏀coveralls]
726
+
727
+ ### 🪇 Code of Conduct
728
+
729
+ Everyone interacting with this project's codebases, issue trackers,
730
+ chat rooms and mailing lists agrees to follow the [![Contributor Covenant 2.1][🪇conduct-img]][🪇conduct].
731
+
732
+ ## 🌈 Contributors
733
+
734
+ [![Contributors][🖐contributors-img]][🖐contributors]
735
+
736
+ Made with [contributors-img][🖐contrib-rocks].
737
+
738
+ <details>
739
+ <summary>⭐️ Star History</summary>
740
+
741
+ <a href="https://star-history.com/#omniauth/omniauth-ldap&Date">
742
+ <picture>
743
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=omniauth/omniauth-ldap&type=Date&theme=dark" />
744
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=omniauth/omniauth-ldap&type=Date" />
745
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=omniauth/omniauth-ldap&type=Date" />
746
+ </picture>
747
+ </a>
748
+
749
+ </details>
750
+
751
+ ## 📌 Versioning
752
+
753
+ This Library adheres to [![Semantic Versioning 2.0.0][📌semver-img]][📌semver].
754
+ Violations of this scheme should be reported as bugs.
755
+ Specifically, if a minor or patch version is released that breaks backward compatibility,
756
+ a new version should be immediately released that restores compatibility.
757
+ Breaking changes to the public API will only be introduced with new major versions.
758
+
759
+ > dropping support for a platform is both obviously and objectively a breaking change <br/>
760
+ >—Jordan Harband ([@ljharb](https://github.com/ljharb), maintainer of SemVer) [in SemVer issue 716][📌semver-breaking]
761
+
762
+ I understand that policy doesn't work universally ("exceptions to every rule!"),
763
+ but it is the policy here.
764
+ As such, in many cases it is good to specify a dependency on this library using
765
+ the [Pessimistic Version Constraint][📌pvc] with two digits of precision.
766
+
767
+ For example:
768
+
769
+ ```ruby
770
+ spec.add_dependency("omniauth-ldap", "~> 1.0")
771
+ ```
772
+
773
+ <details>
774
+ <summary>📌 Is "Platform Support" part of the public API? More details inside.</summary>
775
+
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.
779
+
780
+ To get a better understanding of how SemVer is intended to work over a project's lifetime,
781
+ read this article from the creator of SemVer:
782
+
783
+ - ["Major Version Numbers are Not Sacred"][📌major-versions-not-sacred]
784
+
785
+ </details>
786
+
787
+ See [CHANGELOG.md][📌changelog] for a list of releases.
788
+
789
+ ## 📄 License
790
+
791
+ The gem is available as open source under the terms of
792
+ the [MIT License][📄license] [![License: MIT][📄license-img]][📄license-ref].
793
+ See [LICENSE.txt][📄license] for the official [Copyright Notice][📄copyright-notice-explainer].
794
+
795
+ ### © Copyright
796
+
797
+ <ul>
798
+ <li>
799
+ Copyright (c) 2025 Peter H. Boling, of
800
+ <a href="https://discord.gg/3qme4XHNKN">
801
+ Galtzo.com
802
+ <picture>
803
+ <img src="https://logos.galtzo.com/assets/images/galtzo-floss/avatar-128px-blank.svg" alt="Galtzo.com Logo (Wordless) by Aboling0, CC BY-SA 4.0" width="24">
804
+ </picture>
805
+ </a>, and omniauth-ldap contributors.
806
+ </li>
807
+ <li>
808
+ Copyright (c) 2014 David Benko
809
+ </li>
810
+ <li>
811
+ Copyright (c) 2011 by Ping Yu and Intridea, Inc.
812
+ </li>
813
+ </ul>
814
+
815
+ ## 🤑 A request for help
816
+
817
+ Maintainers have teeth and need to pay their dentists.
818
+ After getting laid off in an RIF in March, and encountering difficulty finding a new one,
819
+ I began spending most of my time building open source tools.
820
+ I'm hoping to be able to pay for my kids' health insurance this month,
821
+ so if you value the work I am doing, I need your support.
822
+ Please consider sponsoring me or the project.
823
+
824
+ To join the community or get help 👇️ Join the Discord.
825
+
826
+ [![Live Chat on Discord][✉️discord-invite-img-ftb]][✉️discord-invite]
827
+
828
+ To say "thanks!" ☝️ Join the Discord or 👇️ send money.
829
+
830
+ [![Sponsor me on GitHub Sponsors][🖇sponsor-bottom-img]][🖇sponsor] 💌 [![Sponsor me on Liberapay][⛳liberapay-bottom-img]][⛳liberapay] 💌 [![Donate on PayPal][🖇paypal-bottom-img]][🖇paypal]
831
+
832
+ ### Please give the project a star ⭐ ♥.
833
+
834
+ Thanks for RTFM. ☺️
835
+
836
+ [⛳liberapay-img]: https://img.shields.io/liberapay/goal/pboling.svg?logo=liberapay&color=a51611&style=flat
837
+ [⛳liberapay-bottom-img]: https://img.shields.io/liberapay/goal/pboling.svg?style=for-the-badge&logo=liberapay&color=a51611
838
+ [⛳liberapay]: https://liberapay.com/pboling/donate
839
+ [🖇sponsor-img]: https://img.shields.io/badge/Sponsor_Me!-pboling.svg?style=social&logo=github
840
+ [🖇sponsor-bottom-img]: https://img.shields.io/badge/Sponsor_Me!-pboling-blue?style=for-the-badge&logo=github
841
+ [🖇sponsor]: https://github.com/sponsors/pboling
842
+ [🖇polar-img]: https://img.shields.io/badge/polar-donate-a51611.svg?style=flat
843
+ [🖇polar]: https://polar.sh/pboling
844
+ [🖇kofi-img]: https://img.shields.io/badge/ko--fi-%E2%9C%93-a51611.svg?style=flat
845
+ [🖇kofi]: https://ko-fi.com/O5O86SNP4
846
+ [🖇patreon-img]: https://img.shields.io/badge/patreon-donate-a51611.svg?style=flat
847
+ [🖇patreon]: https://patreon.com/galtzo
848
+ [🖇buyme-small-img]: https://img.shields.io/badge/buy_me_a_coffee-%E2%9C%93-a51611.svg?style=flat
849
+ [🖇buyme-img]: https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20latte&emoji=&slug=pboling&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff
850
+ [🖇buyme]: https://www.buymeacoffee.com/pboling
851
+ [🖇paypal-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=flat&logo=paypal
852
+ [🖇paypal-bottom-img]: https://img.shields.io/badge/donate-paypal-a51611.svg?style=for-the-badge&logo=paypal&color=0A0A0A
853
+ [🖇paypal]: https://www.paypal.com/paypalme/peterboling
854
+ [🖇floss-funding.dev]: https://floss-funding.dev
855
+ [🖇floss-funding-gem]: https://github.com/galtzo-floss/floss_funding
856
+ [✉️discord-invite]: https://discord.gg/3qme4XHNKN
857
+ [✉️discord-invite-img-ftb]: https://img.shields.io/discord/1373797679469170758?style=for-the-badge&logo=discord
858
+ [✉️ruby-friends-img]: https://img.shields.io/badge/daily.dev-%F0%9F%92%8E_Ruby_Friends-0A0A0A?style=for-the-badge&logo=dailydotdev&logoColor=white
859
+ [✉️ruby-friends]: https://app.daily.dev/squads/rubyfriends
860
+
861
+ [✇bundle-group-pattern]: https://gist.github.com/pboling/4564780
862
+ [⛳️gem-namespace]: https://github.com/omniauth/omniauth-ldap
863
+ [⛳️namespace-img]: https://img.shields.io/badge/namespace-Omniauth::Ldap-3C2D2D.svg?style=square&logo=ruby&logoColor=white
864
+ [⛳️gem-name]: https://bestgems.org/gems/omniauth-ldap
865
+ [⛳️name-img]: https://img.shields.io/badge/name-omniauth--ldap-3C2D2D.svg?style=square&logo=rubygems&logoColor=red
866
+ [⛳️tag-img]: https://img.shields.io/github/tag/omniauth/omniauth-ldap.svg
867
+ [⛳️tag]: http://github.com/omniauth/omniauth-ldap/releases
868
+ [🚂maint-blog]: http://www.railsbling.com/tags/omniauth-ldap
869
+ [🚂maint-blog-img]: https://img.shields.io/badge/blog-railsbling-0093D0.svg?style=for-the-badge&logo=rubyonrails&logoColor=orange
870
+ [🚂maint-contact]: http://www.railsbling.com/contact
871
+ [🚂maint-contact-img]: https://img.shields.io/badge/Contact-Maintainer-0093D0.svg?style=flat&logo=rubyonrails&logoColor=red
872
+ [💖🖇linkedin]: http://www.linkedin.com/in/peterboling
873
+ [💖🖇linkedin-img]: https://img.shields.io/badge/PeterBoling-LinkedIn-0B66C2?style=flat&logo=newjapanprowrestling
874
+ [💖✌️wellfound]: https://wellfound.com/u/peter-boling
875
+ [💖✌️wellfound-img]: https://img.shields.io/badge/peter--boling-orange?style=flat&logo=wellfound
876
+ [💖💲crunchbase]: https://www.crunchbase.com/person/peter-boling
877
+ [💖💲crunchbase-img]: https://img.shields.io/badge/peter--boling-purple?style=flat&logo=crunchbase
878
+ [💖🐘ruby-mast]: https://ruby.social/@galtzo
879
+ [💖🐘ruby-mast-img]: https://img.shields.io/mastodon/follow/109447111526622197?domain=https://ruby.social&style=flat&logo=mastodon&label=Ruby%20@galtzo
880
+ [💖🦋bluesky]: https://bsky.app/profile/galtzo.com
881
+ [💖🦋bluesky-img]: https://img.shields.io/badge/@galtzo.com-0285FF?style=flat&logo=bluesky&logoColor=white
882
+ [💖🌳linktree]: https://linktr.ee/galtzo
883
+ [💖🌳linktree-img]: https://img.shields.io/badge/galtzo-purple?style=flat&logo=linktree
884
+ [💖💁🏼‍♂️devto]: https://dev.to/galtzo
885
+ [💖💁🏼‍♂️devto-img]: https://img.shields.io/badge/dev.to-0A0A0A?style=flat&logo=devdotto&logoColor=white
886
+ [💖💁🏼‍♂️aboutme]: https://about.me/peter.boling
887
+ [💖💁🏼‍♂️aboutme-img]: https://img.shields.io/badge/about.me-0A0A0A?style=flat&logo=aboutme&logoColor=white
888
+ [💖🧊berg]: https://codeberg.org/pboling
889
+ [💖🐙hub]: https://github.org/pboling
890
+ [💖🛖hut]: https://sr.ht/~galtzo/
891
+ [💖🧪lab]: https://gitlab.com/pboling
892
+ [👨🏼‍🏫expsup-upwork]: https://www.upwork.com/freelancers/~014942e9b056abdf86?mp_source=share
893
+ [👨🏼‍🏫expsup-upwork-img]: https://img.shields.io/badge/UpWork-13544E?style=for-the-badge&logo=Upwork&logoColor=white
894
+ [👨🏼‍🏫expsup-codementor]: https://www.codementor.io/peterboling?utm_source=github&utm_medium=button&utm_term=peterboling&utm_campaign=github
895
+ [👨🏼‍🏫expsup-codementor-img]: https://img.shields.io/badge/CodeMentor-Get_Help-1abc9c?style=for-the-badge&logo=CodeMentor&logoColor=white
896
+ [🏙️entsup-tidelift]: https://tidelift.com/subscription/pkg/rubygems-omniauth-ldap?utm_source=rubygems-omniauth-ldap&utm_medium=referral&utm_campaign=readme
897
+ [🏙️entsup-tidelift-img]: https://img.shields.io/badge/Tidelift_and_Sonar-Enterprise_Support-FD3456?style=for-the-badge&logo=sonar&logoColor=white
898
+ [🏙️entsup-tidelift-sonar]: https://blog.tidelift.com/tidelift-joins-sonar
899
+ [💁🏼‍♂️peterboling]: http://www.peterboling.com
900
+ [🚂railsbling]: http://www.railsbling.com
901
+ [📜src-gh-img]: https://img.shields.io/badge/GitHub-238636?style=for-the-badge&logo=Github&logoColor=green
902
+ [📜src-gh]: https://github.com/omniauth/omniauth-ldap
903
+ [📜docs-cr-rd-img]: https://img.shields.io/badge/RubyDoc-Current_Release-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
904
+ [📜docs-head-rd-img]: https://img.shields.io/badge/YARD_on_Galtzo.com-HEAD-943CD2?style=for-the-badge&logo=readthedocs&logoColor=white
905
+ [📜gh-wiki]: https://github.com/omniauth/omniauth-ldap/wiki
906
+ [📜gh-wiki-img]: https://img.shields.io/badge/wiki-examples-943CD2.svg?style=for-the-badge&logo=github&logoColor=white
907
+ [👽dl-rank]: https://bestgems.org/gems/omniauth-ldap
908
+ [👽dl-ranki]: https://img.shields.io/gem/rd/omniauth-ldap.svg
909
+ [👽oss-help]: https://www.codetriage.com/omniauth/omniauth-ldap
910
+ [👽oss-helpi]: https://www.codetriage.com/omniauth/omniauth-ldap/badges/users.svg
911
+ [👽version]: https://bestgems.org/gems/omniauth-ldap
912
+ [👽versioni]: https://img.shields.io/gem/v/omniauth-ldap.svg
913
+ [🏀codecov]: https://codecov.io/gh/omniauth/omniauth-ldap
914
+ [🏀codecovi]: https://codecov.io/gh/omniauth/omniauth-ldap/graph/badge.svg
915
+ [🏀coveralls]: https://coveralls.io/github/omniauth/omniauth-ldap?branch=main
916
+ [🏀coveralls-img]: https://coveralls.io/repos/github/omniauth/omniauth-ldap/badge.svg?branch=main
917
+ [🖐codeQL]: https://github.com/omniauth/omniauth-ldap/security/code-scanning
918
+ [🖐codeQL-img]: https://github.com/omniauth/omniauth-ldap/actions/workflows/codeql-analysis.yml/badge.svg
919
+ [🚎1-an-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/ancient.yml
920
+ [🚎1-an-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/ancient.yml/badge.svg
921
+ [🚎2-cov-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/coverage.yml
922
+ [🚎2-cov-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/coverage.yml/badge.svg
923
+ [🚎3-hd-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/heads.yml
924
+ [🚎3-hd-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/heads.yml/badge.svg
925
+ [🚎4-lg-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/legacy.yml
926
+ [🚎4-lg-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/legacy.yml/badge.svg
927
+ [🚎5-st-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/style.yml
928
+ [🚎5-st-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/style.yml/badge.svg
929
+ [🚎6-s-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/supported.yml
930
+ [🚎6-s-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/supported.yml/badge.svg
931
+ [🚎7-us-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/unsupported.yml
932
+ [🚎7-us-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/unsupported.yml/badge.svg
933
+ [🚎8-ho-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/hoary.yml
934
+ [🚎8-ho-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/hoary.yml/badge.svg
935
+ [🚎9-t-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/truffle.yml
936
+ [🚎9-t-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/truffle.yml/badge.svg
937
+ [🚎10-j-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/jruby.yml
938
+ [🚎10-j-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/jruby.yml/badge.svg
939
+ [🚎11-c-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/current.yml
940
+ [🚎11-c-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/current.yml/badge.svg
941
+ [🚎12-crh-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/dep-heads.yml
942
+ [🚎12-crh-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/dep-heads.yml/badge.svg
943
+ [🚎13-🔒️-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/locked_deps.yml
944
+ [🚎13-🔒️-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/locked_deps.yml/badge.svg
945
+ [🚎14-🔓️-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/unlocked_deps.yml
946
+ [🚎14-🔓️-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/unlocked_deps.yml/badge.svg
947
+ [🚎15-🪪-wf]: https://github.com/omniauth/omniauth-ldap/actions/workflows/license-eye.yml
948
+ [🚎15-🪪-wfi]: https://github.com/omniauth/omniauth-ldap/actions/workflows/license-eye.yml/badge.svg
949
+ [💎ruby-2.0i]: https://img.shields.io/badge/Ruby-2.0_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
950
+ [💎ruby-2.1i]: https://img.shields.io/badge/Ruby-2.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
951
+ [💎ruby-2.2i]: https://img.shields.io/badge/Ruby-2.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=white
952
+ [💎ruby-2.3i]: https://img.shields.io/badge/Ruby-2.3-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
953
+ [💎ruby-2.4i]: https://img.shields.io/badge/Ruby-2.4-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
954
+ [💎ruby-2.5i]: https://img.shields.io/badge/Ruby-2.5-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
955
+ [💎ruby-2.6i]: https://img.shields.io/badge/Ruby-2.6-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
956
+ [💎ruby-2.7i]: https://img.shields.io/badge/Ruby-2.7-DF00CA?style=for-the-badge&logo=ruby&logoColor=white
957
+ [💎ruby-3.0i]: https://img.shields.io/badge/Ruby-3.0-CC342D?style=for-the-badge&logo=ruby&logoColor=white
958
+ [💎ruby-3.1i]: https://img.shields.io/badge/Ruby-3.1-CC342D?style=for-the-badge&logo=ruby&logoColor=white
959
+ [💎ruby-3.2i]: https://img.shields.io/badge/Ruby-3.2-CC342D?style=for-the-badge&logo=ruby&logoColor=white
960
+ [💎ruby-3.3i]: https://img.shields.io/badge/Ruby-3.3-CC342D?style=for-the-badge&logo=ruby&logoColor=white
961
+ [💎ruby-c-i]: https://img.shields.io/badge/Ruby-current-CC342D?style=for-the-badge&logo=ruby&logoColor=green
962
+ [💎ruby-headi]: https://img.shields.io/badge/Ruby-HEAD-CC342D?style=for-the-badge&logo=ruby&logoColor=blue
963
+ [💎truby-22.3i]: https://img.shields.io/badge/Truffle_Ruby-22.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink
964
+ [💎truby-23.0i]: https://img.shields.io/badge/Truffle_Ruby-23.0_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=pink
965
+ [💎truby-23.1i]: https://img.shields.io/badge/Truffle_Ruby-23.1-34BCB1?style=for-the-badge&logo=ruby&logoColor=pink
966
+ [💎truby-c-i]: https://img.shields.io/badge/Truffle_Ruby-current-34BCB1?style=for-the-badge&logo=ruby&logoColor=green
967
+ [💎truby-headi]: https://img.shields.io/badge/Truffle_Ruby-HEAD-34BCB1?style=for-the-badge&logo=ruby&logoColor=blue
968
+ [💎jruby-9.1i]: https://img.shields.io/badge/JRuby-9.1_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red
969
+ [💎jruby-9.2i]: https://img.shields.io/badge/JRuby-9.2_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red
970
+ [💎jruby-9.3i]: https://img.shields.io/badge/JRuby-9.3_(%F0%9F%9A%ABCI)-AABBCC?style=for-the-badge&logo=ruby&logoColor=red
971
+ [💎jruby-9.4i]: https://img.shields.io/badge/JRuby-9.4-FBE742?style=for-the-badge&logo=ruby&logoColor=red
972
+ [💎jruby-c-i]: https://img.shields.io/badge/JRuby-current-FBE742?style=for-the-badge&logo=ruby&logoColor=green
973
+ [💎jruby-headi]: https://img.shields.io/badge/JRuby-HEAD-FBE742?style=for-the-badge&logo=ruby&logoColor=blue
974
+ [🤝gh-issues]: https://github.com/omniauth/omniauth-ldap/issues
975
+ [🤝gh-pulls]: https://github.com/omniauth/omniauth-ldap/pulls
976
+ [🤝contributing]: CONTRIBUTING.md
977
+ [🏀codecov-g]: https://codecov.io/gh/omniauth/omniauth-ldap/graphs/tree.svg
978
+ [🖐contrib-rocks]: https://contrib.rocks
979
+ [🖐contributors]: https://github.com/omniauth/omniauth-ldap/graphs/contributors
980
+ [🖐contributors-img]: https://contrib.rocks/image?repo=omniauth/omniauth-ldap
981
+ [🪇conduct]: CODE_OF_CONDUCT.md
982
+ [🪇conduct-img]: https://img.shields.io/badge/Contributor_Covenant-2.1-259D6C.svg
983
+ [📌pvc]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
984
+ [📌semver]: https://semver.org/spec/v2.0.0.html
985
+ [📌semver-img]: https://img.shields.io/badge/semver-2.0.0-259D6C.svg?style=flat
986
+ [📌semver-breaking]: https://github.com/semver/semver/issues/716#issuecomment-869336139
987
+ [📌major-versions-not-sacred]: https://tom.preston-werner.com/2022/05/23/major-version-numbers-are-not-sacred.html
988
+ [📌changelog]: CHANGELOG.md
989
+ [📗keep-changelog]: https://keepachangelog.com/en/1.0.0/
990
+ [📗keep-changelog-img]: https://img.shields.io/badge/keep--a--changelog-1.0.0-34495e.svg?style=flat
991
+ [📌gitmoji]: https://gitmoji.dev
992
+ [📌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
+ [🧮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
995
+ [🔐security]: SECURITY.md
996
+ [🔐security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
997
+ [📄copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
998
+ [📄license]: LICENSE.txt
999
+ [📄license-ref]: https://opensource.org/licenses/MIT
1000
+ [📄license-img]: https://img.shields.io/badge/License-MIT-259D6C.svg
1001
+ [📄license-compat]: https://dev.to/galtzo/how-to-check-license-compatibility-41h0
1002
+ [📄license-compat-img]: https://img.shields.io/badge/Apache_Compatible:_Category_A-%E2%9C%93-259D6C.svg?style=flat&logo=Apache
1003
+ [📄ilo-declaration]: https://www.ilo.org/declaration/lang--en/index.htm
1004
+ [📄ilo-declaration-img]: https://img.shields.io/badge/ILO_Fundamental_Principles-✓-259D6C.svg?style=flat
1005
+ [🚎yard-current]: http://rubydoc.info/gems/omniauth-ldap
1006
+ [🚎yard-head]: https://omniauth-ldap.galtzo.com
1007
+ [💎stone_checksums]: https://github.com/galtzo-floss/stone_checksums
1008
+ [💎SHA_checksums]: https://gitlab.com/omniauth/omniauth-ldap/-/tree/main/checksums
1009
+ [💎rlts]: https://github.com/rubocop-lts/rubocop-lts
1010
+ [💎rlts-img]: https://img.shields.io/badge/code_style_&_linting-rubocop--lts-34495e.svg?plastic&logo=ruby&logoColor=white
1011
+ [💎appraisal2]: https://github.com/appraisal-rb/appraisal2
1012
+ [💎appraisal2-img]: https://img.shields.io/badge/appraised_by-appraisal2-34495e.svg?plastic&logo=ruby&logoColor=white
1013
+ [💎d-in-dvcs]: https://railsbling.com/posts/dvcs/put_the_d_in_dvcs/
1014
+
1015
+ [//]: # (LDAP RFC references)
1016
+ [rfc4511]: https://datatracker.ietf.org/doc/html/rfc4511
1017
+ [rfc4514]: https://datatracker.ietf.org/doc/html/rfc4514
1018
+ [rfc4519]: https://datatracker.ietf.org/doc/html/rfc4519