omniauth-ldap 2.3.1 โ†’ 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6521def9ea4ad73235bbf1c8147822ab3b59e211fdc5f4959dfa2178a82a5bd4
4
- data.tar.gz: 4f6f08af41228c4e0a20f6a957d9927165f1d8109835a4c239abdcc3c6f79f4f
3
+ metadata.gz: 64fced98d7ab577e6c9abc446ace5678b829670e9d241f0a759c61efc47ecf5e
4
+ data.tar.gz: 7fda93687c96509833b9d72f277995f53eb2368ebe44740180ceadd3afac6ebe
5
5
  SHA512:
6
- metadata.gz: 51fca6cf72c8331c82ecb35b89d4cca511bd6de423da5c24c659b2de9a0f7c29dd74eca283edeb45672ca0a745a1a970478d42b972f518d68fc9a9dd155fef80
7
- data.tar.gz: f08359c99200c2192abc312a80859d7cd9671061318605b680ef6bf03c14ea667b9b7d288dd4754939e062da7f5de1b7c887d33fa56846b10e6f826172bfbe76
6
+ metadata.gz: 8750eeed19ed13d89d041b14123b68cc459e7f5595d04d6d0fe67227c06c312f7952264f6fdd19a8f57957ce98c9ea3620d125fd01c445446c8848400a668e12
7
+ data.tar.gz: ed9dc416b2eba5c6e9d9cc5e889f50bd70347ff4a6ce9261cb8703e77d010c7834a5a566ef8ed7f93f5fbaf4baba0afa9965191decebdf84ed8ffd8b79590a8d
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -32,6 +32,33 @@ Please file a bug if you notice a violation of semantic versioning.
32
32
 
33
33
  ### Security
34
34
 
35
+ ## [2.3.2] - 2025-11-06
36
+
37
+ - TAG: [v2.3.2][2.3.2t]
38
+ - COVERAGE: 97.64% -- 290/297 lines in 4 files
39
+ - BRANCH COVERAGE: 79.69% -- 102/128 branches in 4 files
40
+ - 44.12% documented
41
+
42
+ ### Added
43
+
44
+ - Support for SCRIPT_NAME for proper URL generation
45
+ - behind certain proxies/load balancers, or
46
+ - under a subdirectory
47
+ - Password Policy for LDAP Directories
48
+ - password_policy: true|false (default: false)
49
+ - on authentication failure, if the server returns password policy controls, the info will be included in the failure message
50
+ - https://datatracker.ietf.org/doc/html/draft-behera-ldap-password-policy-11
51
+ - Support for JSON bodies
52
+ - 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
+ - Documentation of TLS verification options
56
+
57
+ ### Changed
58
+
59
+ - Make support for OmniAuth v1.2+ explicit
60
+ - Versions < 1.2 do not support SCRIPT_NAME properly, and may cause other issues
61
+
35
62
  ## [2.3.1] - 2025-11-05
36
63
 
37
64
  - TAG: [v2.3.1][2.3.1t]
@@ -197,6 +224,8 @@ Please file a bug if you notice a violation of semantic versioning.
197
224
  [1.0.0]: https://github.com/omniauth/omniauth-ldap/compare/5656da80d4193e0d0584f44bac493a87695e580f...v1.0.0
198
225
  [1.0.0t]: https://github.com/omniauth/omniauth-ldap/releases/tag/v1.0.0
199
226
 
200
- [Unreleased]: https://github.com/omniauth/omniauth-ldap/compare/v2.3.1...HEAD
227
+ [Unreleased]: https://github.com/omniauth/omniauth-ldap/compare/v2.3.2...HEAD
228
+ [2.3.2]: https://github.com/omniauth/omniauth-ldap/compare/v2.3.1...v2.3.2
229
+ [2.3.2t]: https://github.com/omniauth/omniauth-ldap/releases/tag/v2.3.2
201
230
  [2.3.1]: https://github.com/omniauth/omniauth-ldap/compare/v2.0.0...v2.3.1
202
231
  [2.3.1t]: https://github.com/omniauth/omniauth-ldap/releases/tag/v2.3.1
data/LICENSE.txt CHANGED
@@ -1,6 +1,7 @@
1
1
  MIT License
2
2
 
3
3
  Copyright (c) 2025 Peter H. Boling, and omniauth-ldap contributors
4
+ Copyright (c) 2014 David Benko
4
5
  Copyright (c) 2011 by Ping Yu and Intridea, Inc.
5
6
 
6
7
  Permission is hereby granted, free of charge, to any person obtaining
data/README.md CHANGED
@@ -38,7 +38,7 @@
38
38
 
39
39
  # ๐Ÿ“ OmniAuth LDAP
40
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] [![QLTY Test Coverage][๐Ÿ€qlty-covi]][๐Ÿ€qlty-cov] [![QLTY Maintainability][๐Ÿ€qlty-mnti]][๐Ÿ€qlty-mnt] [![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]
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
42
 
43
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
44
 
@@ -63,9 +63,17 @@ use OmniAuth::Strategies::LDAP,
63
63
  name_proc: proc { |name| name.gsub(/@.*$/, "") },
64
64
  bind_dn: "default_bind_dn",
65
65
  password: "password",
66
+ # Optional timeouts (seconds)
67
+ connect_timeout: 3,
68
+ read_timeout: 7,
66
69
  tls_options: {
67
70
  ssl_version: "TLSv1_2",
68
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"],
69
77
  }
70
78
  # Or, alternatively:
71
79
  # use OmniAuth::Strategies::LDAP, filter: '(&(uid=%{username})(memberOf=cn=myapp-users,ou=groups,dc=example,dc=com))'
@@ -73,6 +81,50 @@ use OmniAuth::Strategies::LDAP,
73
81
 
74
82
  All of the listed options are required, with the exception of `:title`, `:name_proc`, `:bind_dn`, and `:password`.
75
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
+
76
128
  ## ๐Ÿ’ก Info you can shake a stick at
77
129
 
78
130
  | Tokens to Remember | [![Gem name][โ›ณ๏ธname-img]][โ›ณ๏ธgem-name] [![Gem namespace][โ›ณ๏ธnamespace-img]][โ›ณ๏ธgem-namespace] |
@@ -191,6 +243,28 @@ The following options are available for configuring the OmniAuth LDAP strategy:
191
243
  - `:sasl_mechanisms` - Array of SASL mechanisms to use (e.g., ["DIGEST-MD5", "GSS-SPNEGO"]).
192
244
  - `:allow_anonymous` - Whether to allow anonymous binding (default: false).
193
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.
194
268
 
195
269
  ### Auth Hash UID vs LDAP :uid (search attribute)
196
270
 
@@ -299,6 +373,58 @@ end
299
373
 
300
374
  Then link users to `/auth/ldap` in your app (for example, in a Devise sign-in page).
301
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
+
302
428
  ### Using a custom filter
303
429
 
304
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`).
@@ -401,6 +527,84 @@ provider :ldap,
401
527
 
402
528
  This trims `alice@example.com` to `alice` before searching.
403
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
+
404
608
  ### Trusted header SSO (REMOTE_USER and friends)
405
609
 
406
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`.
@@ -520,8 +724,6 @@ See [CONTRIBUTING.md][๐Ÿคcontributing].
520
724
 
521
725
  [![Coveralls Test Coverage][๐Ÿ€coveralls-img]][๐Ÿ€coveralls]
522
726
 
523
- [![QLTY Test Coverage][๐Ÿ€qlty-covi]][๐Ÿ€qlty-cov]
524
-
525
727
  ### ๐Ÿช‡ Code of Conduct
526
728
 
527
729
  Everyone interacting with this project's codebases, issue trackers,
@@ -602,6 +804,9 @@ See [LICENSE.txt][๐Ÿ“„license] for the official [Copyright Notice][๐Ÿ“„copyright
602
804
  </picture>
603
805
  </a>, and omniauth-ldap contributors.
604
806
  </li>
807
+ <li>
808
+ Copyright (c) 2014 David Benko
809
+ </li>
605
810
  <li>
606
811
  Copyright (c) 2011 by Ping Yu and Intridea, Inc.
607
812
  </li>
@@ -622,7 +827,7 @@ To join the community or get help ๐Ÿ‘‡๏ธ Join the Discord.
622
827
 
623
828
  To say "thanks!" โ˜๏ธ Join the Discord or ๐Ÿ‘‡๏ธ send money.
624
829
 
625
- [![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]
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]
626
831
 
627
832
  ### Please give the project a star โญ โ™ฅ.
628
833
 
@@ -705,10 +910,6 @@ Thanks for RTFM. โ˜บ๏ธ
705
910
  [๐Ÿ‘ฝoss-helpi]: https://www.codetriage.com/omniauth/omniauth-ldap/badges/users.svg
706
911
  [๐Ÿ‘ฝversion]: https://bestgems.org/gems/omniauth-ldap
707
912
  [๐Ÿ‘ฝversioni]: https://img.shields.io/gem/v/omniauth-ldap.svg
708
- [๐Ÿ€qlty-mnt]: https://qlty.sh/gh/omniauth/projects/omniauth-ldap
709
- [๐Ÿ€qlty-mnti]: https://qlty.sh/gh/omniauth/projects/omniauth-ldap/maintainability.svg
710
- [๐Ÿ€qlty-cov]: https://qlty.sh/gh/omniauth/projects/omniauth-ldap/metrics/code?sort=coverageRating
711
- [๐Ÿ€qlty-covi]: https://qlty.sh/gh/omniauth/projects/omniauth-ldap/coverage.svg
712
913
  [๐Ÿ€codecov]: https://codecov.io/gh/omniauth/omniauth-ldap
713
914
  [๐Ÿ€codecovi]: https://codecov.io/gh/omniauth/omniauth-ldap/graph/badge.svg
714
915
  [๐Ÿ€coveralls]: https://coveralls.io/github/omniauth/omniauth-ldap?branch=main
@@ -790,7 +991,7 @@ Thanks for RTFM. โ˜บ๏ธ
790
991
  [๐Ÿ“Œgitmoji]: https://gitmoji.dev
791
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
792
993
  [๐Ÿงฎkloc]: https://www.youtube.com/watch?v=dQw4w9WgXcQ
793
- [๐Ÿงฎkloc-img]: https://img.shields.io/badge/KLOC-0.233-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
994
+ [๐Ÿงฎkloc-img]: https://img.shields.io/badge/KLOC-0.297-FFDD67.svg?style=for-the-badge&logo=YouTube&logoColor=blue
794
995
  [๐Ÿ”security]: SECURITY.md
795
996
  [๐Ÿ”security-img]: https://img.shields.io/badge/security-policy-259D6C.svg?style=flat
796
997
  [๐Ÿ“„copyright-notice-explainer]: https://opensource.stackexchange.com/questions/5778/why-do-licenses-such-as-the-mit-license-specify-a-single-year
@@ -9,7 +9,7 @@ module OmniAuth
9
9
 
10
10
  InvalidCredentialsError = Class.new(StandardError)
11
11
 
12
- CONFIG = {
12
+ option :mapping, {
13
13
  "name" => "cn",
14
14
  "first_name" => "givenName",
15
15
  "last_name" => "sn",
@@ -23,7 +23,7 @@ module OmniAuth
23
23
  "url" => ["wwwhomepage"],
24
24
  "image" => "jpegPhoto",
25
25
  "description" => "description",
26
- }.freeze
26
+ }
27
27
  option :title, "LDAP Authentication" # default title for authentication form
28
28
  # For OmniAuth >= 2.0 the default allowed request method is POST only.
29
29
  # Ensure the strategy follows that default so GET /auth/:provider returns 404 as expected in tests.
@@ -46,6 +46,9 @@ module OmniAuth
46
46
  # standard Rack "HTTP_" variant automatically.
47
47
  option :header_auth, false
48
48
  option :header_name, "REMOTE_USER"
49
+ # Optional timeouts (forwarded to Net::LDAP when supported)
50
+ option :connect_timeout, nil
51
+ option :read_timeout, nil
49
52
 
50
53
  def request_phase
51
54
  # OmniAuth >= 2.0 expects the request phase to be POST-only for /auth/:provider.
@@ -57,18 +60,18 @@ module OmniAuth
57
60
  # Fast-path: if a trusted identity header is present, skip the login form
58
61
  # and jump to the callback where we will complete using directory lookup.
59
62
  if header_username
60
- return Rack::Response.new([], 302, "Location" => callback_path).finish
63
+ return Rack::Response.new([], 302, "Location" => callback_url).finish
61
64
  end
62
65
 
63
66
  # If credentials were POSTed directly to /auth/:provider, redirect to the callback path.
64
67
  # This mirrors the behavior of many OmniAuth providers and allows test helpers (like
65
68
  # OmniAuth::Test::PhonySession) to populate `env['omniauth.auth']` on the callback request.
66
- if request.post? && request.params["username"].to_s != "" && request.params["password"].to_s != ""
67
- return Rack::Response.new([], 302, "Location" => callback_path).finish
69
+ if request.post? && request_data["username"].to_s != "" && request_data["password"].to_s != ""
70
+ return Rack::Response.new([], 302, "Location" => callback_url).finish
68
71
  end
69
72
 
70
73
  OmniAuth::LDAP::Adaptor.validate(@options)
71
- f = OmniAuth::Form.new(title: options[:title] || "LDAP Authentication", url: callback_path)
74
+ f = OmniAuth::Form.new(title: options[:title] || "LDAP Authentication", url: callback_url)
72
75
  f.text_field("Login", "username")
73
76
  f.password_field("Password", "password")
74
77
  f.button("Sign In")
@@ -88,7 +91,7 @@ module OmniAuth
88
91
  return fail!(:invalid_credentials, InvalidCredentialsError.new("User not found for header #{hu}"))
89
92
  end
90
93
  @ldap_user_info = entry
91
- @user_info = self.class.map_user(CONFIG, @ldap_user_info)
94
+ @user_info = self.class.map_user(@options[:mapping], @ldap_user_info)
92
95
  return super
93
96
  rescue => e
94
97
  return fail!(:ldap_error, e)
@@ -97,13 +100,18 @@ module OmniAuth
97
100
 
98
101
  return fail!(:missing_credentials) if missing_credentials?
99
102
  begin
100
- @ldap_user_info = @adaptor.bind_as(filter: filter(@adaptor), size: 1, password: request.params["password"])
103
+ @ldap_user_info = @adaptor.bind_as(filter: filter(@adaptor), size: 1, password: request_data["password"])
101
104
 
102
105
  unless @ldap_user_info
103
- return fail!(:invalid_credentials, InvalidCredentialsError.new("Invalid credentials for #{request.params["username"]}"))
106
+ # Attach password policy info to env if available (best-effort)
107
+ attach_password_policy_env(@adaptor)
108
+ return fail!(:invalid_credentials, InvalidCredentialsError.new("Invalid credentials for #{request_data["username"]}"))
104
109
  end
105
110
 
106
- @user_info = self.class.map_user(CONFIG, @ldap_user_info)
111
+ # Optionally attach policy info even on success (e.g., timeBeforeExpiration)
112
+ attach_password_policy_env(@adaptor)
113
+
114
+ @user_info = self.class.map_user(@options[:mapping], @ldap_user_info)
107
115
  super
108
116
  rescue => e
109
117
  fail!(:ldap_error, e)
@@ -111,11 +119,12 @@ module OmniAuth
111
119
  end
112
120
 
113
121
  def filter(adaptor, username_override = nil)
114
- if adaptor.filter && !adaptor.filter.empty?
115
- username = Net::LDAP::Filter.escape(@options[:name_proc].call(username_override || request.params["username"]))
116
- Net::LDAP::Filter.construct(adaptor.filter % {username: username})
122
+ flt = adaptor.filter
123
+ if flt && !flt.to_s.empty?
124
+ username = Net::LDAP::Filter.escape(@options[:name_proc].call(username_override || request_data["username"]))
125
+ Net::LDAP::Filter.construct(flt % {username: username})
117
126
  else
118
- Net::LDAP::Filter.equals(adaptor.uid, @options[:name_proc].call(username_override || request.params["username"]))
127
+ Net::LDAP::Filter.equals(adaptor.uid, @options[:name_proc].call(username_override || request_data["username"]))
119
128
  end
120
129
  end
121
130
 
@@ -173,8 +182,12 @@ module OmniAuth
173
182
  end
174
183
 
175
184
  def missing_credentials?
176
- request.params["username"].nil? || request.params["username"].empty? || request.params["password"].nil? || request.params["password"].empty?
177
- end # missing_credentials?
185
+ request_data["username"].nil? || request_data["username"].empty? || request_data["password"].nil? || request_data["password"].empty?
186
+ end
187
+
188
+ def request_data
189
+ @env["action_dispatch.request.request_parameters"] || request.params
190
+ end
178
191
 
179
192
  # Extract a normalized username from a trusted header when enabled.
180
193
  # Returns nil when not configured or not present.
@@ -193,13 +206,54 @@ module OmniAuth
193
206
  # (bind_dn/password or anonymous). Does not attempt to bind as the user.
194
207
  def directory_lookup(adaptor, username)
195
208
  entry = nil
196
- filter = filter(adaptor, username)
209
+ search_filter = filter(adaptor, username)
197
210
  adaptor.connection.open do |conn|
198
- rs = conn.search(filter: filter, size: 1)
199
- entry = rs.first if rs && rs.first
211
+ rs = conn.search(filter: search_filter, size: 1)
212
+ entry = rs && rs.first
200
213
  end
201
214
  entry
202
215
  end
216
+
217
+ # If the adaptor captured a Password Policy response control, expose a minimal, stable hash
218
+ # in the Rack env for applications to inspect.
219
+ def attach_password_policy_env(adaptor)
220
+ return unless adaptor.respond_to?(:password_policy) && adaptor.password_policy
221
+ ctrl = adaptor.respond_to?(:last_password_policy_response) ? adaptor.last_password_policy_response : nil
222
+ op = adaptor.respond_to?(:last_operation_result) ? adaptor.last_operation_result : nil
223
+ return unless ctrl || op
224
+
225
+ request.env["omniauth.ldap.password_policy"] = extract_password_policy(ctrl, op)
226
+ end
227
+
228
+ # Best-effort extraction across net-ldap versions; if fields are not available, returns a raw payload.
229
+ def extract_password_policy(control, operation)
230
+ data = {raw: control}
231
+ if control
232
+ # Prefer named readers if present
233
+ if control.respond_to?(:error)
234
+ data[:error] = control.public_send(:error)
235
+ elsif control.respond_to?(:ppolicy_error)
236
+ data[:error] = control.public_send(:ppolicy_error)
237
+ end
238
+ if control.respond_to?(:time_before_expiration)
239
+ data[:time_before_expiration] = control.public_send(:time_before_expiration)
240
+ end
241
+ if control.respond_to?(:grace_authns_remaining)
242
+ data[:grace_authns_remaining] = control.public_send(:grace_authns_remaining)
243
+ elsif control.respond_to?(:grace_logins_remaining)
244
+ data[:grace_authns_remaining] = control.public_send(:grace_logins_remaining)
245
+ end
246
+ if control.respond_to?(:oid)
247
+ data[:oid] = control.public_send(:oid)
248
+ end
249
+ end
250
+ if operation
251
+ code = operation.respond_to?(:code) ? operation.code : nil
252
+ message = operation.respond_to?(:message) ? operation.message : nil
253
+ data[:operation] = {code: code, message: message}
254
+ end
255
+ data
256
+ end
203
257
  end
204
258
  end
205
259
  end
@@ -31,6 +31,10 @@ module OmniAuth
31
31
  :allow_anonymous,
32
32
  :filter,
33
33
  :tls_options,
34
+ :password_policy,
35
+ # Timeouts
36
+ :connect_timeout,
37
+ :read_timeout,
34
38
 
35
39
  # Deprecated
36
40
  :method,
@@ -59,7 +63,7 @@ module OmniAuth
59
63
  }
60
64
 
61
65
  attr_accessor :bind_dn, :password
62
- attr_reader :connection, :uid, :base, :auth, :filter
66
+ attr_reader :connection, :uid, :base, :auth, :filter, :password_policy, :last_operation_result, :last_password_policy_response
63
67
 
64
68
  def self.validate(configuration = {})
65
69
  message = []
@@ -88,6 +92,8 @@ module OmniAuth
88
92
  port: @port,
89
93
  encryption: encryption_options,
90
94
  }
95
+ # Remove passing timeouts here to avoid issues on older net-ldap versions.
96
+ # We'll set them after initialization if the connection responds to writers.
91
97
  @bind_method = if @try_sasl
92
98
  :sasl
93
99
  else
@@ -102,6 +108,21 @@ module OmniAuth
102
108
  }
103
109
  config[:auth] = @auth
104
110
  @connection = Net::LDAP.new(config)
111
+ # Apply optional timeout settings if supported by the installed net-ldap version
112
+ if !@connect_timeout.nil?
113
+ if @connection.respond_to?(:connect_timeout=)
114
+ @connection.connect_timeout = @connect_timeout
115
+ else
116
+ @connection.instance_variable_set(:@connect_timeout, @connect_timeout)
117
+ end
118
+ end
119
+ if !@read_timeout.nil?
120
+ if @connection.respond_to?(:read_timeout=)
121
+ @connection.read_timeout = @read_timeout
122
+ else
123
+ @connection.instance_variable_set(:@read_timeout, @read_timeout)
124
+ end
125
+ end
105
126
  end
106
127
 
107
128
  #:base => "dc=yourcompany, dc=com",
@@ -109,20 +130,47 @@ module OmniAuth
109
130
  # :password => psw
110
131
  def bind_as(args = {})
111
132
  result = false
133
+ @last_operation_result = nil
134
+ @last_password_policy_response = nil
112
135
  @connection.open do |me|
113
136
  rs = me.search(args)
114
- if rs and rs.first and dn = rs.first.dn
115
- password = args[:password]
116
- method = args[:method] || @method
117
- password = password.call if password.respond_to?(:call)
118
- if method == "sasl"
119
- result = rs.first if me.bind(sasl_auths({username: dn, password: password}).first)
120
- elsif me.bind(
121
- method: :simple,
122
- username: dn,
123
- password: password,
124
- )
125
- result = rs.first
137
+ raise ConnectionError.new("LDAP search operation failed") unless rs
138
+
139
+ if rs && rs.first
140
+ dn = rs.first.dn
141
+ if dn
142
+ password = args[:password]
143
+ password = password.call if password.respond_to?(:call)
144
+
145
+ bind_args = if @bind_method == :sasl
146
+ sasl_auths({username: dn, password: password}).first
147
+ else
148
+ {
149
+ method: :simple,
150
+ username: dn,
151
+ password: password,
152
+ }
153
+ end
154
+
155
+ # Optionally request LDAP Password Policy control (RFC Draft - de facto standard)
156
+ if @password_policy
157
+ # Always request by OID using a simple hash; avoids depending on gem-specific control classes
158
+ control = {oid: "1.3.6.1.4.1.42.2.27.8.5.1", criticality: true, value: nil}
159
+ if bind_args.is_a?(Hash)
160
+ bind_args = bind_args.merge({controls: [control]})
161
+ else
162
+ # Some Net::LDAP versions allow passing a block for SASL only; ensure we still can add controls if hash
163
+ # When not a Hash, we can't merge; rely on server default behavior.
164
+ end
165
+ end
166
+
167
+ begin
168
+ success = bind_args ? me.bind(bind_args) : me.bind
169
+ ensure
170
+ capture_password_policy(me)
171
+ end
172
+
173
+ result = rs.first if success
126
174
  end
127
175
  end
128
176
  end
@@ -250,6 +298,32 @@ module OmniAuth
250
298
  result[key.to_sym] = value
251
299
  end
252
300
  end
301
+
302
+ # Capture the operation result and extract any Password Policy response control if present.
303
+ def capture_password_policy(conn)
304
+ return unless @password_policy
305
+ return unless conn.respond_to?(:get_operation_result)
306
+
307
+ begin
308
+ @last_operation_result = conn.get_operation_result
309
+ controls = if @last_operation_result && @last_operation_result.respond_to?(:controls)
310
+ @last_operation_result.controls || []
311
+ else
312
+ []
313
+ end
314
+ if controls.any?
315
+ # Find Password Policy response control by OID
316
+ ppolicy_oid = "1.3.6.1.4.1.42.2.27.8.5.1"
317
+ ctrl = controls.find do |c|
318
+ (c.respond_to?(:oid) && c.oid == ppolicy_oid) || (c.is_a?(Hash) && c[:oid] == ppolicy_oid)
319
+ end
320
+ @last_password_policy_response = ctrl if ctrl
321
+ end
322
+ rescue StandardError
323
+ # Swallow errors to keep authentication flow unaffected when server or gem doesn't support controls
324
+ @last_password_policy_response = nil
325
+ end
326
+ end
253
327
  end
254
328
  end
255
329
  end
@@ -1,7 +1,7 @@
1
1
  module OmniAuth
2
2
  module LDAP
3
3
  module Version
4
- VERSION = "2.3.1"
4
+ VERSION = "2.3.2"
5
5
  end
6
6
  VERSION = Version::VERSION # Make VERSION available in traditional way
7
7
  end
@@ -15,7 +15,7 @@ module OmniAuth
15
15
 
16
16
  VALID_ADAPTER_CONFIGURATION_KEYS: Array[Symbol]
17
17
  MUST_HAVE_KEYS: Array[untyped]
18
- METHOD: Hash[Symbol, Symbol?]
18
+ ENCRYPTION_METHOD: Hash[Symbol, Symbol?]
19
19
 
20
20
  attr_accessor bind_dn: String?
21
21
  attr_accessor password: String?
@@ -28,6 +28,10 @@ module OmniAuth
28
28
  attr_reader auth: Hash[Symbol, untyped]
29
29
  # filter is an LDAP filter string when configured
30
30
  attr_reader filter: String?
31
+ # optional: request password policy control and capture response
32
+ attr_reader password_policy: bool?
33
+ attr_reader last_operation_result: untyped
34
+ attr_reader last_password_policy_response: untyped
31
35
 
32
36
  # Validate that required keys exist in the configuration
33
37
  def self.validate: (?Hash[Symbol, untyped]) -> void
@@ -38,17 +42,31 @@ module OmniAuth
38
42
 
39
43
  private
40
44
 
41
- # Returns a Net::LDAP encryption symbol (e.g. :simple_tls, :start_tls) or nil
42
- def ensure_method: (untyped) -> Symbol?
45
+ # Returns encryption settings hash or nil
46
+ def encryption_options: () -> Hash[Symbol, untyped]?
47
+ # Translate configured method/encryption into Net::LDAP symbol
48
+ def translate_method: () -> Symbol?
49
+ # Returns a Net::LDAP encryption default options hash
50
+ def default_options: () -> Hash[Symbol, untyped]
51
+ # Sanitize provided TLS options
52
+ def sanitize_hash_values: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
53
+ # Symbolize option keys
54
+ def symbolize_hash_keys: (Hash[untyped, untyped]) -> Hash[Symbol, untyped]
55
+ # Capture password policy control from last operation
56
+ def capture_password_policy: (Net::LDAP) -> void
43
57
 
44
58
  # Returns an array of SASL auth hashes
45
59
  def sasl_auths: (?Hash[Symbol, untyped]) -> Array[Hash[Symbol, untyped]]
46
60
 
47
- # Returns initial credential (string) and a proc that accepts a challenge and returns the response
48
- # Use Array[untyped] here to avoid tuple syntax issues in some linters; the runtime value
49
- # is commonly a two-element array [initial_credential, proc].
61
+ # Returns initial credential and a proc that accepts a challenge and returns the response
50
62
  def sasl_bind_setup_digest_md5: (?Hash[Symbol, untyped]) -> Array[untyped]
51
63
  def sasl_bind_setup_gss_spnego: (?Hash[Symbol, untyped]) -> Array[untyped]
64
+
65
+ @try_sasl: bool?
66
+ @allow_anonymous: bool?
67
+ @tls_options: Hash[untyped, untyped]?
68
+ @sasl_mechanisms: Array[String]?
69
+ @disable_verify_certificates: bool?
52
70
  end
53
71
  end
54
72
  end
@@ -3,9 +3,6 @@ module OmniAuth
3
3
  class LDAP
4
4
  OMNIAUTH_GTE_V2: bool
5
5
 
6
- # CONFIG is a read-only mapping of string keys to mapping definitions
7
- CONFIG: Hash[String, untyped]
8
-
9
6
  # The request_phase either returns a Rack-compatible response or the form response.
10
7
  def request_phase: () -> (Rack::Response | Array[untyped] | String)
11
8
 
@@ -27,6 +24,12 @@ module OmniAuth
27
24
 
28
25
  # Perform a directory lookup for a given username; returns an Entry or nil
29
26
  def directory_lookup: (OmniAuth::LDAP::Adaptor, String) -> untyped
27
+
28
+ def uid: () { () -> String } -> void
29
+
30
+ def info: () { () -> Hash[untyped, untyped] } -> void
31
+
32
+ def extra: () { () -> Hash[Symbol, untyped] } -> void
30
33
  end
31
34
  end
32
35
  end
@@ -3,3 +3,8 @@
3
3
  # - sig/omniauth/ldap/adaptor.rbs
4
4
  # - sig/omniauth/strategies/ldap.rbs
5
5
  # This file is intentionally minimal to avoid duplicating declarations.
6
+
7
+ module OmniAuth
8
+ module LDAP
9
+ end
10
+ end
data/sig/rbs/net-ldap.rbs CHANGED
@@ -5,6 +5,7 @@ module Net
5
5
  def open: () { (self) -> untyped } -> untyped
6
6
  def search: (?Hash[Symbol, untyped]) -> Array[Net::LDAP::Entry]
7
7
  def bind: (?Hash[Symbol, untyped]) -> bool
8
+ def get_operation_result: () -> Net::LDAP::PDU
8
9
  end
9
10
 
10
11
  class LDAP::Entry
@@ -15,5 +16,20 @@ module Net
15
16
  def self.construct: (String) -> Net::LDAP::Filter
16
17
  def self.eq: (String, String) -> Net::LDAP::Filter
17
18
  end
18
- end
19
19
 
20
+ class LDAP::Control
21
+ def initialize: (String, bool, untyped) -> void
22
+ def oid: () -> String
23
+ end
24
+
25
+ module LDAP::Controls
26
+ class PasswordPolicy
27
+ def initialize: () -> void
28
+ def oid: () -> String
29
+ end
30
+ end
31
+
32
+ class LDAP::PDU
33
+ def controls: () -> Array[untyped]
34
+ end
35
+ end
data/sig/rbs/net-ntlm.rbs CHANGED
@@ -4,6 +4,8 @@ module Net
4
4
  class Message
5
5
  def self.parse: (untyped) -> Net::NTLM::Message
6
6
  def response: (?Hash[Symbol, untyped], ?Hash[Symbol, untyped]) -> Net::NTLM::Message
7
+ # writer used by adaptor to set target name when a domain is present
8
+ def target_name=: (String) -> String
7
9
  end
8
10
 
9
11
  class Message::Type1
@@ -13,4 +15,3 @@ module Net
13
15
  def self.encode_utf16le: (String) -> String
14
16
  end
15
17
  end
16
-
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.1
4
+ version: 2.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Boling
@@ -65,7 +65,7 @@ dependencies:
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '1'
68
+ version: '1.2'
69
69
  - - "<"
70
70
  - !ruby/object:Gem::Version
71
71
  version: '3'
@@ -75,7 +75,7 @@ dependencies:
75
75
  requirements:
76
76
  - - ">="
77
77
  - !ruby/object:Gem::Version
78
- version: '1'
78
+ version: '1.2'
79
79
  - - "<"
80
80
  - !ruby/object:Gem::Version
81
81
  version: '3'
@@ -408,10 +408,10 @@ licenses:
408
408
  - MIT
409
409
  metadata:
410
410
  homepage_uri: https://omniauth-ldap.galtzo.com/
411
- source_code_uri: https://github.com/omniauth/omniauth-ldap/tree/v2.3.1
412
- changelog_uri: https://github.com/omniauth/omniauth-ldap/blob/v2.3.1/CHANGELOG.md
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
413
413
  bug_tracker_uri: https://github.com/omniauth/omniauth-ldap/issues
414
- documentation_uri: https://www.rubydoc.info/gems/omniauth-ldap/2.3.1
414
+ documentation_uri: https://www.rubydoc.info/gems/omniauth-ldap/2.3.2
415
415
  funding_uri: https://github.com/sponsors/pboling
416
416
  wiki_uri: https://github.com/omniauth/omniauth-ldap/wiki
417
417
  news_uri: https://www.railsbling.com/tags/omniauth-ldap
metadata.gz.sig CHANGED
Binary file