diff --git a/build.gradle b/build.gradle index a54e995a..5a883ebe 100644 --- a/build.gradle +++ b/build.gradle @@ -36,7 +36,7 @@ dependencies { testCompile "junit:junit:4.11" testCompile 'org.spockframework:spock-core:1.0-groovy-2.4' - testCompile "org.mockito:mockito-core:1.9.5" + testCompile "org.mockito:mockito-core:2.8.47" testCompile "org.apache.sshd:sshd-core:1.2.0" testRuntime "ch.qos.logback:logback-classic:1.1.2" testCompile 'org.glassfish.grizzly:grizzly-http-server:2.3.17' diff --git a/src/main/java/net/schmizz/sshj/userauth/password/ConsolePasswordFinder.java b/src/main/java/net/schmizz/sshj/userauth/password/ConsolePasswordFinder.java new file mode 100644 index 00000000..60e3bd85 --- /dev/null +++ b/src/main/java/net/schmizz/sshj/userauth/password/ConsolePasswordFinder.java @@ -0,0 +1,71 @@ +/* + * Copyright (C)2009 - SSHJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.schmizz.sshj.userauth.password; + +import java.io.Console; +import java.util.IllegalFormatException; + +/** A PasswordFinder that reads a password from a console */ +public class ConsolePasswordFinder implements PasswordFinder { + + public static final String DEFAULT_FORMAT = "Enter passphrase for %s:"; + + private final Console console; + private final String promptFormat; + private final int maxTries; + + private int numTries; + + public ConsolePasswordFinder() { + this(System.console()); + } + + public ConsolePasswordFinder(Console console) { + this(console, DEFAULT_FORMAT, 3); + } + + public ConsolePasswordFinder(Console console, String promptFormat, int maxTries) { + checkFormatString(promptFormat); + this.console = console; + this.promptFormat = promptFormat; + this.maxTries = maxTries; + this.numTries = 0; + } + + @Override + public char[] reqPassword(Resource resource) { + numTries++; + if (console == null) { + // the request cannot be serviced + return null; + } + return console.readPassword(promptFormat, resource.toString()); + } + + @Override + public boolean shouldRetry(Resource resource) { + return numTries < maxTries; + } + + private static void checkFormatString(String promptFormat) { + try { + String.format(promptFormat, ""); + } catch (IllegalFormatException e) { + throw new IllegalArgumentException("promptFormat must have no more than one %s and no other markers", e); + } + } + +} diff --git a/src/test/java/net/schmizz/sshj/userauth/password/TestConsolePasswordFinder.java b/src/test/java/net/schmizz/sshj/userauth/password/TestConsolePasswordFinder.java new file mode 100644 index 00000000..f0bc3d17 --- /dev/null +++ b/src/test/java/net/schmizz/sshj/userauth/password/TestConsolePasswordFinder.java @@ -0,0 +1,83 @@ +/* + * Copyright (C)2009 - SSHJ Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.schmizz.sshj.userauth.password; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.Console; + +public class TestConsolePasswordFinder { + + private static final String FORMAT = "%s"; + + @Test + public void testReqPassword() { + char[] expectedPassword = "password".toCharArray(); + + Console console = Mockito.mock(Console.class); + Mockito.when(console.readPassword(Mockito.anyString(), Mockito.any())) + .thenReturn(expectedPassword); + + Resource resource = Mockito.mock(Resource.class); + char[] password = new ConsolePasswordFinder(console).reqPassword(resource); + + Assert.assertArrayEquals("Password should match mocked return value", + expectedPassword, password); + Mockito.verifyNoMoreInteractions(resource); + } + + @Test + public void testReqPasswordNullConsole() { + Resource resource = Mockito.mock(Resource.class); + char[] password = new ConsolePasswordFinder(null, FORMAT, 1).reqPassword(resource); + + Assert.assertNull("Password should be null with null console", password); + Mockito.verifyNoMoreInteractions(resource); + } + + @Test + public void testShouldRetry() { + Resource resource = new PrivateKeyStringResource(""); + ConsolePasswordFinder finder = new ConsolePasswordFinder(null, FORMAT, 1); + Assert.assertTrue("Should allow a retry at first", finder.shouldRetry(resource)); + + finder.reqPassword(resource); + Assert.assertFalse("Should stop allowing retries after one interaction", finder.shouldRetry(resource)); + } + + @Test + public void testPromptFormat() { + Assert.assertNotNull( + "Empty format should create valid ConsolePasswordFinder", + new ConsolePasswordFinder(null, "", 1)); + Assert.assertNotNull( + "Single-string format should create valid ConsolePasswordFinder", + new ConsolePasswordFinder(null, FORMAT, 1)); + } + + @Test(expected = IllegalArgumentException.class) + public void testPromptFormatTooManyMarkers() { + new ConsolePasswordFinder(null, "%s%s", 1); + } + + @Test(expected = IllegalArgumentException.class) + public void testPromptFormatWrongMarkerType() { + new ConsolePasswordFinder(null, "%d", 1); + } + +} diff --git a/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 00000000..ef9230d2 --- /dev/null +++ b/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1,2 @@ +# incubating feature to allow mocking final classes +mock-maker-inline