Skip to content

Commit 1356c66

Browse files
authored
Rewrite Password setting class in Java (second attempt) (#18231)
Secoind attempt to translates Password setting class into plain Java, which exposes a logger used by the Ruby ValidatedPassword subclass. Fixes a bug happened in previous #18183. Co-authored: @donoghuc
1 parent a994c7c commit 1356c66

File tree

6 files changed

+155
-38
lines changed

6 files changed

+155
-38
lines changed

logstash-core/lib/logstash/environment.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def self.as_java_range(r)
7575
Setting::StringSetting.new("api.environment", "production"),
7676
Setting::StringSetting.new("api.auth.type", "none", true, %w(none basic)),
7777
Setting::StringSetting.new("api.auth.basic.username", nil, false).nullable,
78-
Setting::Password.new("api.auth.basic.password", nil, false).nullable,
78+
Setting::PasswordSetting.new("api.auth.basic.password", nil, false).nullable,
7979
Setting::StringSetting.new("api.auth.basic.password_policy.mode", "WARN", true, %w[WARN ERROR]),
8080
Setting::NumericSetting.new("api.auth.basic.password_policy.length.minimum", 8),
8181
Setting::StringSetting.new("api.auth.basic.password_policy.include.upper", "REQUIRED", true, %w[REQUIRED OPTIONAL]),
@@ -84,7 +84,7 @@ def self.as_java_range(r)
8484
Setting::StringSetting.new("api.auth.basic.password_policy.include.symbol", "OPTIONAL", true, %w[REQUIRED OPTIONAL]),
8585
Setting::BooleanSetting.new("api.ssl.enabled", false),
8686
Setting::ExistingFilePath.new("api.ssl.keystore.path", nil, false).nullable,
87-
Setting::Password.new("api.ssl.keystore.password", nil, false).nullable,
87+
Setting::PasswordSetting.new("api.ssl.keystore.password", nil, false).nullable,
8888
Setting::StringArray.new("api.ssl.supported_protocols", nil, true, %w[TLSv1 TLSv1.1 TLSv1.2 TLSv1.3]),
8989
Setting::StringSetting.new("pipeline.batch.metrics.sampling_mode", "minimal", true, ["disabled", "minimal", "full"]),
9090
Setting::StringSetting.new("queue.type", "memory", true, ["persisted", "memory"]),

logstash-core/lib/logstash/settings.rb

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -439,27 +439,9 @@ def validate(value)
439439

440440
java_import org.logstash.settings.NullableStringSetting
441441

442-
class Password < Coercible
443-
def initialize(name, default = nil, strict = true)
444-
super(name, LogStash::Util::Password, default, strict)
445-
end
446-
447-
def coerce(value)
448-
return value if value.kind_of?(LogStash::Util::Password)
449-
450-
if value && !value.kind_of?(::String)
451-
raise(ArgumentError, "Setting `#{name}` could not coerce non-string value to password")
452-
end
453-
454-
LogStash::Util::Password.new(value)
455-
end
456-
457-
def validate(value)
458-
super(value)
459-
end
460-
end
442+
java_import org.logstash.settings.PasswordSetting
461443

462-
class ValidatedPassword < Setting::Password
444+
class ValidatedPassword < Setting::PasswordSetting
463445
def initialize(name, value, password_policies)
464446
@password_policies = password_policies
465447
super(name, value, true)

logstash-core/spec/logstash/settings/password_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
require "spec_helper"
1919
require "logstash/settings"
2020

21-
describe LogStash::Setting::Password do
21+
describe LogStash::Setting::PasswordSetting do
2222
let(:setting_name) { "secure" }
2323
subject(:password_setting) { described_class.new(setting_name, nil, true) }
2424

@@ -55,7 +55,7 @@
5555
context 'with an invalid non-string value' do
5656
let(:setting_value) { 867_5309 }
5757
it 'rejects the invalid value' do
58-
expect { password_setting.set(setting_value) }.to raise_error(ArgumentError, "Setting `#{setting_name}` could not coerce non-string value to password")
58+
expect { password_setting.set(setting_value) }.to raise_error(IllegalArgumentException, "Setting `#{setting_name}` could not coerce non-string value to password")
5959
expect(password_setting).to_not be_set
6060
end
6161
end

logstash-core/spec/logstash/settings_spec.rb

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -300,22 +300,22 @@
300300
end
301301
end
302302

303-
context "containing a mode WARN policy" do
304-
before :each do
305-
# Needs to mock the logger method at LogStash::Settings instead of LogStash::Setting::ValidatedPassword
306-
# else the LOGGABLE_PROXY hide the mock itself.
307-
allow(LogStash::Settings).to receive(:logger).at_least(:once).and_return(mock_logger)
308-
allow(mock_logger).to receive(:warn)
309-
end
310-
let(:mock_logger) { double("logger") }
311-
let(:password_policies) { super().merge({ "mode": "WARN" }) }
303+
describe "mode WARN" do
304+
let(:password_policies) { super().merge("mode": "WARN") }
305+
306+
context "when the password does not conform to the policy" do
307+
let(:password) { LogStash::Util::Password.new("NoNumbers!") }
308+
let(:mock_logger) { double("logger") }
309+
310+
before :each do
311+
allow_any_instance_of(LogStash::Setting::ValidatedPassword).to receive(:logger).and_return(mock_logger)
312+
end
313+
314+
it "logs a warning on validation failure" do
315+
expect(mock_logger).to receive(:warn).with(a_string_including("Password must contain at least one digit between 0 and 9."))
312316

313-
it "logs a warning on validation failure" do
314-
password = LogStash::Util::Password.new("Password!")
315-
expect {
316317
LogStash::Setting::ValidatedPassword.new("test.validated.password", password, password_policies)
317-
}.not_to raise_error
318-
expect(mock_logger).to have_received(:warn).with(a_string_including("Password must contain at least one digit between 0 and 9."))
318+
end
319319
end
320320
end
321321
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.logstash.settings;
21+
22+
import co.elastic.logstash.api.Password;
23+
import org.apache.logging.log4j.LogManager;
24+
import org.apache.logging.log4j.Logger;
25+
26+
27+
public class PasswordSetting extends Coercible<Object> {
28+
29+
private static final Logger LOG = LogManager.getLogger();
30+
31+
public PasswordSetting(String name, Object defaultValue) {
32+
this(name, defaultValue, true);
33+
}
34+
35+
public PasswordSetting(String name, Object defaultValue, boolean strict) {
36+
super(name, defaultValue, strict, noValidator());
37+
}
38+
39+
@Override
40+
public Password coerce(Object obj) {
41+
if (obj instanceof Password) {
42+
return (Password) obj;
43+
}
44+
if (obj != null && !(obj instanceof String)) {
45+
throw new IllegalArgumentException("Setting `" + getName() + "` could not coerce non-string value to password");
46+
}
47+
return new Password((String) obj);
48+
}
49+
50+
public Logger getLogger() {
51+
return LOG;
52+
}
53+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.logstash.settings;
21+
22+
import org.junit.Before;
23+
import org.junit.Test;
24+
25+
import static org.hamcrest.CoreMatchers.instanceOf;
26+
import static org.hamcrest.CoreMatchers.is;
27+
import static org.hamcrest.MatcherAssert.assertThat;
28+
import static org.junit.Assert.*;
29+
30+
public class PasswordSettingTest {
31+
32+
private final String SETTING_NAME = "setting_name";
33+
private PasswordSetting sut;
34+
35+
@Before
36+
public void setUp() {
37+
sut = new PasswordSetting(SETTING_NAME, null, true);
38+
}
39+
40+
@Test
41+
public void givenUnsetPasswordSetting_thenIsConsideredAsValid() {
42+
assertNotThrown(() -> sut.validateValue());
43+
assertThat(sut.value(), is(instanceOf(co.elastic.logstash.api.Password.class)));
44+
assertNull(((co.elastic.logstash.api.Password) sut.value()).getValue());
45+
}
46+
47+
@Test
48+
public void givenUnsetPasswordSetting_whenIsSetIsInvoked_thenReturnFalse() {
49+
assertFalse(sut.isSet());
50+
}
51+
52+
@Test
53+
public void givenSetPasswordSetting_thenIsValid() {
54+
sut.set("s3cUr3p4$$w0rd");
55+
56+
assertNotThrown(() -> sut.validateValue());
57+
assertThat(sut.value(), is(instanceOf(co.elastic.logstash.api.Password.class)));
58+
assertEquals("s3cUr3p4$$w0rd", ((co.elastic.logstash.api.Password) sut.value()).getValue());
59+
}
60+
61+
@Test
62+
public void givenSetPasswordSetting_whenIsSetIsInvoked_thenReturnTrue() {
63+
sut.set("s3cUr3p4$$w0rd");
64+
65+
assertTrue(sut.isSet());
66+
}
67+
68+
@Test
69+
public void givenSetPasswordSettingWithInvalidNonStringValue_thenRejectsTheInvalidValue() {
70+
Exception e = assertThrows(IllegalArgumentException.class, () -> sut.set(867_5309));
71+
assertThat(e.getMessage(), is("Setting `" + SETTING_NAME + "` could not coerce non-string value to password"));
72+
}
73+
74+
private void assertNotThrown(Runnable test) {
75+
try {
76+
test.run();
77+
} catch (Exception e) {
78+
fail("Exception should not be thrown");
79+
}
80+
}
81+
82+
}

0 commit comments

Comments
 (0)