summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorDouwe Maan <douwe@gitlab.com>2017-10-07 19:16:03 +0000
committerDouwe Maan <douwe@gitlab.com>2017-10-07 19:16:03 +0000
commit3e96288aa4715658c055894513fb640d3637b933 (patch)
tree81667f23df73c13f13eddb058dbce438ec1c4a63 /spec
parent4c020c8ca49c6e7a38cef86f061cc6b60e63a1bd (diff)
parentac5784368fb23260abd4050a48c4443017c87bfc (diff)
downloadgitlab-ce-3e96288aa4715658c055894513fb640d3637b933.tar.gz
Merge branch 'mk-normalize-ldap-user-dns' into 'master'
Normalize LDAP user DNs (downcase and remove excess spaces) See merge request gitlab-org/gitlab-ce!14697
Diffstat (limited to 'spec')
-rw-r--r--spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb36
-rw-r--r--spec/lib/gitlab/ldap/auth_hash_spec.rb24
-rw-r--r--spec/lib/gitlab/ldap/dn_spec.rb224
-rw-r--r--spec/lib/gitlab/ldap/person_spec.rb33
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb16
-rw-r--r--spec/migrations/normalize_ldap_extern_uids_spec.rb56
-rw-r--r--spec/support/ldap_shared_examples.rb69
7 files changed, 449 insertions, 9 deletions
diff --git a/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb b/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb
new file mode 100644
index 00000000000..dfbf1bb681a
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/normalize_ldap_extern_uids_range_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::NormalizeLdapExternUidsRange, :migration, schema: 20170921101004 do
+ let!(:identities) { table(:identities) }
+
+ before do
+ # LDAP identities
+ (1..4).each do |i|
+ identities.create!(id: i, provider: 'ldapmain', extern_uid: " uid = foo #{i}, ou = People, dc = example, dc = com ", user_id: i)
+ end
+
+ # Non-LDAP identity
+ identities.create!(id: 5, provider: 'foo', extern_uid: " uid = foo 5, ou = People, dc = example, dc = com ", user_id: 5)
+
+ # Another LDAP identity
+ identities.create!(id: 6, provider: 'ldapmain', extern_uid: " uid = foo 6, ou = People, dc = example, dc = com ", user_id: 6)
+ end
+
+ it 'normalizes the LDAP identities in the range' do
+ described_class.new.perform(1, 3)
+ expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com")
+ expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com")
+ expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com")
+ expect(identities.find(4).extern_uid).to eq(" uid = foo 4, ou = People, dc = example, dc = com ")
+ expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ")
+ expect(identities.find(6).extern_uid).to eq(" uid = foo 6, ou = People, dc = example, dc = com ")
+
+ described_class.new.perform(4, 6)
+ expect(identities.find(1).extern_uid).to eq("uid=foo 1,ou=people,dc=example,dc=com")
+ expect(identities.find(2).extern_uid).to eq("uid=foo 2,ou=people,dc=example,dc=com")
+ expect(identities.find(3).extern_uid).to eq("uid=foo 3,ou=people,dc=example,dc=com")
+ expect(identities.find(4).extern_uid).to eq("uid=foo 4,ou=people,dc=example,dc=com")
+ expect(identities.find(5).extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ")
+ expect(identities.find(6).extern_uid).to eq("uid=foo 6,ou=people,dc=example,dc=com")
+ end
+end
diff --git a/spec/lib/gitlab/ldap/auth_hash_spec.rb b/spec/lib/gitlab/ldap/auth_hash_spec.rb
index 8370adf9211..1785094af10 100644
--- a/spec/lib/gitlab/ldap/auth_hash_spec.rb
+++ b/spec/lib/gitlab/ldap/auth_hash_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::LDAP::AuthHash do
let(:auth_hash) do
described_class.new(
OmniAuth::AuthHash.new(
- uid: '123456',
+ uid: given_uid,
provider: 'ldapmain',
info: info,
extra: {
@@ -32,6 +32,8 @@ describe Gitlab::LDAP::AuthHash do
end
context "without overridden attributes" do
+ let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' }
+
it "has the correct username" do
expect(auth_hash.username).to eq("123456")
end
@@ -42,6 +44,8 @@ describe Gitlab::LDAP::AuthHash do
end
context "with overridden attributes" do
+ let(:given_uid) { 'uid=John Smith,ou=People,dc=example,dc=com' }
+
let(:attributes) do
{
'username' => %w(mail email),
@@ -61,4 +65,22 @@ describe Gitlab::LDAP::AuthHash do
expect(auth_hash.name).to eq("John Smith")
end
end
+
+ describe '#uid' do
+ context 'when there is extraneous (but valid) whitespace' do
+ let(:given_uid) { 'uid =john smith , ou = people, dc= example,dc =com' }
+
+ it 'removes the extraneous whitespace' do
+ expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com')
+ end
+ end
+
+ context 'when there are upper case characters' do
+ let(:given_uid) { 'UID=John Smith,ou=People,dc=example,dc=com' }
+
+ it 'downcases' do
+ expect(auth_hash.uid).to eq('uid=john smith,ou=people,dc=example,dc=com')
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/ldap/dn_spec.rb b/spec/lib/gitlab/ldap/dn_spec.rb
new file mode 100644
index 00000000000..8e21ecdf9ab
--- /dev/null
+++ b/spec/lib/gitlab/ldap/dn_spec.rb
@@ -0,0 +1,224 @@
+require 'spec_helper'
+
+describe Gitlab::LDAP::DN do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#normalize_value' do
+ subject { described_class.normalize_value(given) }
+
+ it_behaves_like 'normalizes a DN attribute value'
+
+ context 'when the given DN is malformed' do
+ context 'when ending with a comma' do
+ let(:given) { 'John Smith,' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ end
+ end
+
+ context 'when given a BER encoded attribute value with a space in it' do
+ let(:given) { '#aa aa' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"")
+ end
+ end
+
+ context 'when given a BER encoded attribute value with a non-hex character in it' do
+ let(:given) { '#aaXaaa' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"")
+ end
+ end
+
+ context 'when given a BER encoded attribute value with a non-hex character in it' do
+ let(:given) { '#aaaYaa' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"")
+ end
+ end
+
+ context 'when given a hex pair with a non-hex character in it, inside double quotes' do
+ let(:given) { '"Sebasti\\cX\\a1n"' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"")
+ end
+ end
+
+ context 'with an open (as opposed to closed) double quote' do
+ let(:given) { '"James' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ end
+ end
+
+ context 'with an invalid escaped hex code' do
+ let(:given) { 'J\ames' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"')
+ end
+ end
+
+ context 'with a value ending with the escape character' do
+ let(:given) { 'foo\\' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ end
+ end
+ end
+ end
+
+ describe '#to_normalized_s' do
+ subject { described_class.new(given).to_normalized_s }
+
+ it_behaves_like 'normalizes a DN'
+
+ context 'when we do not support the given DN format' do
+ context 'multivalued RDNs' do
+ context 'without extraneous whitespace' do
+ let(:given) { 'uid=john smith+telephonenumber=+1 555-555-5555,ou=people,dc=example,dc=com' }
+
+ it 'raises UnsupportedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError)
+ end
+ end
+
+ context 'with extraneous whitespace' do
+ context 'around the phone number plus sign' do
+ let(:given) { 'uid = John Smith + telephoneNumber = + 1 555-555-5555 , ou = People,dc=example,dc=com' }
+
+ it 'raises UnsupportedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError)
+ end
+ end
+
+ context 'not around the phone number plus sign' do
+ let(:given) { 'uid = John Smith + telephoneNumber = +1 555-555-5555 , ou = People,dc=example,dc=com' }
+
+ it 'raises UnsupportedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::UnsupportedError)
+ end
+ end
+ end
+ end
+ end
+
+ context 'when the given DN is malformed' do
+ context 'when ending with a comma' do
+ let(:given) { 'uid=John Smith,' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ end
+ end
+
+ context 'when given a BER encoded attribute value with a space in it' do
+ let(:given) { '0.9.2342.19200300.100.1.25=#aa aa' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the end of an attribute value, but got \"a\"")
+ end
+ end
+
+ context 'when given a BER encoded attribute value with a non-hex character in it' do
+ let(:given) { '0.9.2342.19200300.100.1.25=#aaXaaa' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the first character of a hex pair, but got \"X\"")
+ end
+ end
+
+ context 'when given a BER encoded attribute value with a non-hex character in it' do
+ let(:given) { '0.9.2342.19200300.100.1.25=#aaaYaa' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair, but got \"Y\"")
+ end
+ end
+
+ context 'when given a hex pair with a non-hex character in it, inside double quotes' do
+ let(:given) { 'uid="Sebasti\\cX\\a1n"' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, "Expected the second character of a hex pair inside a double quoted value, but got \"X\"")
+ end
+ end
+
+ context 'without a name value pair' do
+ let(:given) { 'John' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ end
+ end
+
+ context 'with an open (as opposed to closed) double quote' do
+ let(:given) { 'cn="James' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ end
+ end
+
+ context 'with an invalid escaped hex code' do
+ let(:given) { 'cn=J\ames' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Invalid escaped hex code "\am"')
+ end
+ end
+
+ context 'with a value ending with the escape character' do
+ let(:given) { 'cn=\\' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'DN string ended unexpectedly')
+ end
+ end
+
+ context 'with an invalid OID attribute type name' do
+ let(:given) { '1.2.d=Value' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN OID attribute type name character "d"')
+ end
+ end
+
+ context 'with a period in a non-OID attribute type name' do
+ let(:given) { 'd1.2=Value' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "."')
+ end
+ end
+
+ context 'when starting with non-space, non-alphanumeric character' do
+ let(:given) { ' -uid=John Smith' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized first character of an RDN attribute type name "-"')
+ end
+ end
+
+ context 'when given a UID with an escaped equal sign' do
+ let(:given) { 'uid\\=john' }
+
+ it 'raises MalformedError' do
+ expect { subject }.to raise_error(Gitlab::LDAP::DN::MalformedError, 'Unrecognized RDN attribute type name character "\\"')
+ end
+ end
+ end
+ end
+
+ def assert_generic_test(test_description, got, expected)
+ test_failure_message = "Failed test description: '#{test_description}'\n\n expected: \"#{expected}\"\n got: \"#{got}\""
+ expect(got).to eq(expected), test_failure_message
+ end
+end
diff --git a/spec/lib/gitlab/ldap/person_spec.rb b/spec/lib/gitlab/ldap/person_spec.rb
index 087c4d8c92c..d204050ef66 100644
--- a/spec/lib/gitlab/ldap/person_spec.rb
+++ b/spec/lib/gitlab/ldap/person_spec.rb
@@ -16,6 +16,34 @@ describe Gitlab::LDAP::Person do
)
end
+ describe '.normalize_dn' do
+ subject { described_class.normalize_dn(given) }
+
+ it_behaves_like 'normalizes a DN'
+
+ context 'with an exception during normalization' do
+ let(:given) { 'John "Smith,' } # just something that will cause an exception
+
+ it 'returns the given DN unmodified' do
+ expect(subject).to eq(given)
+ end
+ end
+ end
+
+ describe '.normalize_uid' do
+ subject { described_class.normalize_uid(given) }
+
+ it_behaves_like 'normalizes a DN attribute value'
+
+ context 'with an exception during normalization' do
+ let(:given) { 'John "Smith,' } # just something that will cause an exception
+
+ it 'returns the given UID unmodified' do
+ expect(subject).to eq(given)
+ end
+ end
+ end
+
describe '#name' do
it 'uses the configured name attribute and handles values as an array' do
name = 'John Doe'
@@ -43,4 +71,9 @@ describe Gitlab::LDAP::Person do
expect(person.email).to eq([user_principal_name])
end
end
+
+ def assert_generic_test(test_description, got, expected)
+ test_failure_message = "Failed test description: '#{test_description}'\n\n expected: #{expected}\n got: #{got}"
+ expect(got).to eq(expected), test_failure_message
+ end
end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 6a6e465cea2..9a4705d1cee 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::LDAP::User do
}
end
let(:auth_hash) do
- OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info)
+ OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info)
end
let(:ldap_user_upper_case) { described_class.new(auth_hash_upper_case) }
let(:info_upper_case) do
@@ -22,12 +22,12 @@ describe Gitlab::LDAP::User do
}
end
let(:auth_hash_upper_case) do
- OmniAuth::AuthHash.new(uid: 'my-uid', provider: 'ldapmain', info: info_upper_case)
+ OmniAuth::AuthHash.new(uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain', info: info_upper_case)
end
describe '#changed?' do
it "marks existing ldap user as changed" do
- create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
+ create(:omniauth_user, extern_uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain')
expect(ldap_user.changed?).to be_truthy
end
@@ -37,7 +37,7 @@ describe Gitlab::LDAP::User do
end
it "does not mark existing ldap user as changed" do
- create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain')
+ create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=john smith,ou=people,dc=example,dc=com', provider: 'ldapmain')
ldap_user.gl_user.user_synced_attributes_metadata(provider: 'ldapmain', email: true)
expect(ldap_user.changed?).to be_falsey
end
@@ -60,7 +60,7 @@ describe Gitlab::LDAP::User do
describe 'find or create' do
it "finds the user if already existing" do
- create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
+ create(:omniauth_user, extern_uid: 'uid=John Smith,ou=People,dc=example,dc=com', provider: 'ldapmain')
expect { ldap_user.save }.not_to change { User.count }
end
@@ -70,7 +70,7 @@ describe Gitlab::LDAP::User do
expect { ldap_user.save }.not_to change { User.count }
existing_user.reload
- expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
+ expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com'
expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
end
@@ -79,7 +79,7 @@ describe Gitlab::LDAP::User do
expect { ldap_user.save }.not_to change { User.count }
existing_user.reload
- expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
+ expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com'
expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
expect(existing_user.id).to eql ldap_user.gl_user.id
end
@@ -89,7 +89,7 @@ describe Gitlab::LDAP::User do
expect { ldap_user_upper_case.save }.not_to change { User.count }
existing_user.reload
- expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
+ expect(existing_user.ldap_identity.extern_uid).to eql 'uid=john smith,ou=people,dc=example,dc=com'
expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
expect(existing_user.id).to eql ldap_user.gl_user.id
end
diff --git a/spec/migrations/normalize_ldap_extern_uids_spec.rb b/spec/migrations/normalize_ldap_extern_uids_spec.rb
new file mode 100644
index 00000000000..262d7742aaf
--- /dev/null
+++ b/spec/migrations/normalize_ldap_extern_uids_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170921101004_normalize_ldap_extern_uids')
+
+describe NormalizeLdapExternUids, :migration, :sidekiq do
+ let!(:identities) { table(:identities) }
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ before do
+ stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_BATCH_SIZE", 2)
+ stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE", 2)
+
+ # LDAP identities
+ (1..4).each do |i|
+ identities.create!(id: i, provider: 'ldapmain', extern_uid: " uid = foo #{i}, ou = People, dc = example, dc = com ", user_id: i)
+ end
+
+ # Non-LDAP identity
+ identities.create!(id: 5, provider: 'foo', extern_uid: " uid = foo 5, ou = People, dc = example, dc = com ", user_id: 5)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([described_class::MIGRATION, [1, 2]])
+ expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(10.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([described_class::MIGRATION, [3, 4]])
+ expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(20.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs[2]['args']).to eq([described_class::MIGRATION, [5, 5]])
+ expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(30.seconds.from_now.to_f)
+ expect(BackgroundMigrationWorker.jobs.size).to eq 3
+ end
+ end
+ end
+
+ it 'migrates the LDAP identities' do
+ Sidekiq::Testing.inline! do
+ migrate!
+ identities.where(id: 1..4).each do |identity|
+ expect(identity.extern_uid).to eq("uid=foo #{identity.id},ou=people,dc=example,dc=com")
+ end
+ end
+ end
+
+ it 'does not modify non-LDAP identities' do
+ Sidekiq::Testing.inline! do
+ migrate!
+ identity = identities.last
+ expect(identity.extern_uid).to eq(" uid = foo 5, ou = People, dc = example, dc = com ")
+ end
+ end
+end
diff --git a/spec/support/ldap_shared_examples.rb b/spec/support/ldap_shared_examples.rb
new file mode 100644
index 00000000000..52c34e78965
--- /dev/null
+++ b/spec/support/ldap_shared_examples.rb
@@ -0,0 +1,69 @@
+shared_examples_for 'normalizes a DN' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:test_description, :given, :expected) do
+ 'strips extraneous whitespace' | 'uid =John Smith , ou = People, dc= example,dc =com' | 'uid=john smith,ou=people,dc=example,dc=com'
+ 'strips extraneous whitespace for a DN with a single RDN' | 'uid = John Smith' | 'uid=john smith'
+ 'unescapes non-reserved, non-special Unicode characters' | 'uid = Sebasti\\c3\\a1n\\ C.\\20Smith, ou=People (aka. \\22humans\\") ,dc=example, dc=com' | 'uid=sebastián c. smith,ou=people (aka. \\"humans\\"),dc=example,dc=com'
+ 'downcases the whole string' | 'UID=John Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com'
+ 'for a null DN (empty string), returns empty string and does not error' | '' | ''
+ 'does not strip an escaped leading space in an attribute value' | 'uid=\\ John Smith,ou=People,dc=example,dc=com' | 'uid=\\ john smith,ou=people,dc=example,dc=com'
+ 'does not strip an escaped leading space in the last attribute value' | 'uid=\\ John Smith' | 'uid=\\ john smith'
+ 'does not strip an escaped trailing space in an attribute value' | 'uid=John Smith\\ ,ou=People,dc=example,dc=com' | 'uid=john smith\\ ,ou=people,dc=example,dc=com'
+ 'strips extraneous spaces after an escaped trailing space' | 'uid=John Smith\\ ,ou=People,dc=example,dc=com' | 'uid=john smith\\ ,ou=people,dc=example,dc=com'
+ 'strips extraneous spaces after an escaped trailing space at the end of the DN' | 'uid=John Smith,ou=People,dc=example,dc=com\\ ' | 'uid=john smith,ou=people,dc=example,dc=com\\ '
+ 'properly preserves escaped trailing space after unescaped trailing spaces' | 'uid=John Smith \\ ,ou=People,dc=example,dc=com' | 'uid=john smith \\ ,ou=people,dc=example,dc=com'
+ 'preserves multiple inner spaces in an attribute value' | 'uid=John Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com'
+ 'preserves inner spaces after an escaped space' | 'uid=John\\ Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com'
+ 'hex-escapes an escaped leading newline in an attribute value' | "uid=\\\nJohn Smith,ou=People,dc=example,dc=com" | "uid=\\0ajohn smith,ou=people,dc=example,dc=com"
+ 'hex-escapes and does not strip an escaped trailing newline in an attribute value' | "uid=John Smith\\\n,ou=People,dc=example,dc=com" | "uid=john smith\\0a,ou=people,dc=example,dc=com"
+ 'hex-escapes an unescaped leading newline (actually an invalid DN?)' | "uid=\nJohn Smith,ou=People,dc=example,dc=com" | "uid=\\0ajohn smith,ou=people,dc=example,dc=com"
+ 'strips an unescaped trailing newline (actually an invalid DN?)' | "uid=John Smith\n,ou=People,dc=example,dc=com" | "uid=john smith,ou=people,dc=example,dc=com"
+ 'does not strip if no extraneous whitespace' | 'uid=John Smith,ou=People,dc=example,dc=com' | 'uid=john smith,ou=people,dc=example,dc=com'
+ 'does not modify an escaped equal sign in an attribute value' | 'uid= foo \\= bar' | 'uid=foo \\= bar'
+ 'converts an escaped hex equal sign to an escaped equal sign in an attribute value' | 'uid= foo \\3D bar' | 'uid=foo \\= bar'
+ 'does not modify an escaped comma in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\, CA' | 'uid=john c. smith,ou=san francisco\\, ca'
+ 'converts an escaped hex comma to an escaped comma in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\2C CA' | 'uid=john c. smith,ou=san francisco\\, ca'
+ 'does not modify an escaped hex carriage return character in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\,\\0DCA' | 'uid=john c. smith,ou=san francisco\\,\\0dca'
+ 'does not modify an escaped hex line feed character in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\,\\0ACA' | 'uid=john c. smith,ou=san francisco\\,\\0aca'
+ 'does not modify an escaped hex CRLF in an attribute value' | 'uid= John C. Smith, ou=San Francisco\\,\\0D\\0ACA' | 'uid=john c. smith,ou=san francisco\\,\\0d\\0aca'
+ 'allows attribute type name OIDs' | '0.9.2342.19200300.100.1.25=Example,0.9.2342.19200300.100.1.25=Com' | '0.9.2342.19200300.100.1.25=example,0.9.2342.19200300.100.1.25=com'
+ 'strips extraneous whitespace from attribute type name OIDs' | '0.9.2342.19200300.100.1.25 = Example, 0.9.2342.19200300.100.1.25 = Com' | '0.9.2342.19200300.100.1.25=example,0.9.2342.19200300.100.1.25=com'
+ end
+
+ with_them do
+ it 'normalizes the DN' do
+ assert_generic_test(test_description, subject, expected)
+ end
+ end
+end
+
+shared_examples_for 'normalizes a DN attribute value' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:test_description, :given, :expected) do
+ 'strips extraneous whitespace' | ' John Smith ' | 'john smith'
+ 'unescapes non-reserved, non-special Unicode characters' | 'Sebasti\\c3\\a1n\\ C.\\20Smith' | 'sebastián c. smith'
+ 'downcases the whole string' | 'JoHn C. Smith' | 'john c. smith'
+ 'does not strip an escaped leading space in an attribute value' | '\\ John Smith' | '\\ john smith'
+ 'does not strip an escaped trailing space in an attribute value' | 'John Smith\\ ' | 'john smith\\ '
+ 'hex-escapes an escaped leading newline in an attribute value' | "\\\nJohn Smith" | "\\0ajohn smith"
+ 'hex-escapes and does not strip an escaped trailing newline in an attribute value' | "John Smith\\\n" | "john smith\\0a"
+ 'hex-escapes an unescaped leading newline (actually an invalid DN value?)' | "\nJohn Smith" | "\\0ajohn smith"
+ 'strips an unescaped trailing newline (actually an invalid DN value?)' | "John Smith\n" | "john smith"
+ 'does not strip if no extraneous whitespace' | 'John Smith' | 'john smith'
+ 'does not modify an escaped equal sign in an attribute value' | ' foo \\= bar' | 'foo \\= bar'
+ 'converts an escaped hex equal sign to an escaped equal sign in an attribute value' | ' foo \\3D bar' | 'foo \\= bar'
+ 'does not modify an escaped comma in an attribute value' | 'San Francisco\\, CA' | 'san francisco\\, ca'
+ 'converts an escaped hex comma to an escaped comma in an attribute value' | 'San Francisco\\2C CA' | 'san francisco\\, ca'
+ 'does not modify an escaped hex carriage return character in an attribute value' | 'San Francisco\\,\\0DCA' | 'san francisco\\,\\0dca'
+ 'does not modify an escaped hex line feed character in an attribute value' | 'San Francisco\\,\\0ACA' | 'san francisco\\,\\0aca'
+ 'does not modify an escaped hex CRLF in an attribute value' | 'San Francisco\\,\\0D\\0ACA' | 'san francisco\\,\\0d\\0aca'
+ end
+
+ with_them do
+ it 'normalizes the DN attribute value' do
+ assert_generic_test(test_description, subject, expected)
+ end
+ end
+end