|
11 | 11 | # :name_proc => Proc.new {|name| name.gsub(/@.*$/,'')} |
12 | 12 | # :bind_dn => 'default_bind_dn' |
13 | 13 | # :password => 'password' |
14 | | - class MyLdapProvider < OmniAuth::Strategies::LDAP; end |
| 14 | + before do |
| 15 | + ldap_strategy = Class.new(OmniAuth::Strategies::LDAP) |
| 16 | + stub_const("MyLdapProvider", ldap_strategy) |
| 17 | + end |
15 | 18 |
|
16 | 19 | let(:app) do |
17 | 20 | Rack::Builder.new { |
@@ -181,6 +184,19 @@ def make_env(path = "/auth/ldap", props = {}) |
181 | 184 | expect(last_response.headers["Location"]).to match("invalid_credentials") |
182 | 185 | expect(last_request.env["omniauth.error"].message).to eq("Invalid credentials for ping") |
183 | 186 | end |
| 187 | + |
| 188 | + it "supports group restriction filters and applies name_proc" do |
| 189 | + # Complex filter with %{username} placeholder and group membership |
| 190 | + group_filter = "(&(uid=%{username})(memberOf=cn=forum-users,ou=groups,dc=example,dc=com))" |
| 191 | + allow(@adaptor).to receive(:filter).and_return(group_filter) |
| 192 | + # username has a domain part; name_proc on strategy under test strips it |
| 193 | + expect(Net::LDAP::Filter).to receive(:construct).with("(&(uid=alice)(memberOf=cn=forum-users,ou=groups,dc=example,dc=com))") |
| 194 | + |
| 195 | + post("/auth/ldap/callback", {username: "[email protected]", password: "password"}) |
| 196 | + |
| 197 | + expect(last_response).to be_redirect |
| 198 | + expect(last_response.headers["Location"]).to match("invalid_credentials") |
| 199 | + end |
184 | 200 | end |
185 | 201 | end |
186 | 202 |
|
@@ -240,6 +256,29 @@ def make_env(path = "/auth/ldap", props = {}) |
240 | 256 |
|
241 | 257 | expect(last_response).not_to be_redirect |
242 | 258 | end |
| 259 | + |
| 260 | + it "escapes special characters in username when building filter" do |
| 261 | + allow(@adaptor).to receive(:filter).and_return("uid=%{username}") |
| 262 | + # '(' => \28 and ')' => \29 per RFC 4515 escaping |
| 263 | + expect(Net::LDAP::Filter).to receive(:construct).with("uid=al\\28ice\\29") |
| 264 | + post("/auth/ldap/callback", {username: "al(ice)", password: "secret"}) |
| 265 | + end |
| 266 | + |
| 267 | + it "binds with complex group filter and applies name_proc" do |
| 268 | + allow(@adaptor).to receive(:bind_as) { |
| 269 | + Net::LDAP::Entry.from_single_ldif_string( |
| 270 | + %{dn: cn=alice, dc=example, dc=com |
| 271 | +uid: alice |
| 272 | +}, |
| 273 | + ) |
| 274 | + } |
| 275 | + allow(@adaptor).to receive(:filter).and_return("(&(uid=%{username})(memberOf=cn=forum-users,ou=groups,dc=example,dc=com))") |
| 276 | + expect(Net::LDAP::Filter).to receive(:construct).with("(&(uid=alice)(memberOf=cn=forum-users,ou=groups,dc=example,dc=com))") |
| 277 | + |
| 278 | + post("/auth/ldap/callback", {username: "[email protected]", password: "secret"}) |
| 279 | + expect(last_response).not_to be_redirect |
| 280 | + expect(last_request.env["omniauth.auth"].info.nickname).to eq "alice" |
| 281 | + end |
243 | 282 | end |
244 | 283 |
|
245 | 284 | it "maps user info to Auth Hash" do |
@@ -440,11 +479,41 @@ def connection_returning(entry) |
440 | 479 | expect(last_response).not_to be_redirect |
441 | 480 | end |
442 | 481 |
|
| 482 | + it "escapes special characters in header SSO username when building filter" do |
| 483 | + entry = Net::LDAP::Entry.from_single_ldif_string(%{dn: cn=al\\28ice\\29, dc=example, dc=com |
| 484 | +uid: al(ice) |
| 485 | +}) |
| 486 | + allow(@adaptor).to receive_messages( |
| 487 | + connection: connection_returning(entry), |
| 488 | + filter: "uid=%{username}", |
| 489 | + ) |
| 490 | + expect(Net::LDAP::Filter).to receive(:construct).with("uid=al\\28ice\\29").and_call_original |
| 491 | + |
| 492 | + post "/auth/ldap/callback", nil, {"REMOTE_USER" => "al(ice)"} |
| 493 | + expect(last_response).not_to be_redirect |
| 494 | + end |
| 495 | + |
443 | 496 | it "fails when directory lookup returns no entry" do |
444 | 497 | allow(@adaptor).to receive(:connection).and_return(connection_returning(nil)) |
445 | 498 | post "/auth/ldap/callback", nil, {"REMOTE_USER" => "missing"} |
446 | 499 | expect(last_response).to be_redirect |
447 | 500 | expect(last_response.headers["Location"]).to match(/invalid_credentials/) |
448 | 501 | end |
| 502 | + |
| 503 | + it "supports complex group filter with %{username} in header SSO path" do |
| 504 | + # Expect that the complex filter string is constructed with the processed username |
| 505 | + expect(Net::LDAP::Filter).to receive(:construct).with("(&(uid=alice)(memberOf=cn=forum-users,ou=groups,dc=example,dc=com))").and_call_original |
| 506 | + |
| 507 | + entry = Net::LDAP::Entry.from_single_ldif_string(%{dn: cn=alice, dc=example, dc=com |
| 508 | +uid: alice |
| 509 | +}) |
| 510 | + allow(@adaptor).to receive_messages( |
| 511 | + filter: "(&(uid=%{username})(memberOf=cn=forum-users,ou=groups,dc=example,dc=com))", |
| 512 | + connection: connection_returning(entry), |
| 513 | + ) |
| 514 | + |
| 515 | + post "/auth/ldap/callback", nil, {"REMOTE_USER" => "[email protected]"} |
| 516 | + expect(last_response).not_to be_redirect |
| 517 | + end |
449 | 518 | end |
450 | 519 | end |
0 commit comments