diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LdapAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LdapAuthentication.java deleted file mode 100644 index f75587ca..00000000 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LdapAuthentication.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - * - * 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 - * - * https://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 org.springframework.vault.authentication; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.util.Assert; -import org.springframework.vault.VaultException; -import org.springframework.vault.client.VaultResponses; -import org.springframework.vault.support.VaultResponse; -import org.springframework.vault.support.VaultToken; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestOperations; - -import static java.util.Collections.singletonMap; -import static org.springframework.vault.authentication.AuthenticationUtil.getLoginPath; - -/** - * LDAP implementation of {@link ClientAuthentication}. - * - * @author Mikhael Sokolov - * @see LdapAuthenticationOptions - * @see RestOperations - * @see LDAP - * @since 2.4 - */ -public class LdapAuthentication implements ClientAuthentication, AuthenticationStepsFactory { - - private static final Log logger = LogFactory.getLog(LdapAuthentication.class); - - private final LdapAuthenticationOptions options; - - private final RestOperations restOperations; - - public LdapAuthentication(LdapAuthenticationOptions options, RestOperations restOperations) { - Assert.notNull(options, "LdapAuthenticationOptions must not be null"); - Assert.notNull(restOperations, "RestOperations must not be null"); - - this.options = options; - this.restOperations = restOperations; - } - - @Override - public VaultToken login() throws VaultException { - return createTokenUsingLdapAuthentication(); - } - - @Override - public AuthenticationSteps getAuthenticationSteps() { - return AuthenticationSteps - .fromSupplier(() -> singletonMap("password", options.getPassword())) - .login(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername())); - } - - private VaultToken createTokenUsingLdapAuthentication() { - try { - VaultResponse response = restOperations.postForObject(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()), singletonMap("password", options.getPassword()), VaultResponse.class); - - logger.debug("Login successful using LDAP credentials"); - - return LoginTokenUtil.from(response.getAuth()); - } catch (HttpStatusCodeException e) { - throw new VaultException(String.format("Cannot login using LDAP: %s", VaultResponses.getError(e.getResponseBodyAsString())), e); - } - } -} \ No newline at end of file diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LdapAuthenticationOptions.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/LdapAuthenticationOptions.java deleted file mode 100644 index d54ba0d4..00000000 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/LdapAuthenticationOptions.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2021 the original author or authors. - * - * 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 - * - * https://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 org.springframework.vault.authentication; - -import org.springframework.util.Assert; - -import javax.annotation.Nullable; - -/** - * @author Mikhael Sokolov - */ -public class LdapAuthenticationOptions { - - public static final String DEFAULT_LDAP_AUTHENTICATION_PATH = "ldap"; - - /** - * Path of the ldap authentication backend mount. - */ - private final String path; - - /** - * Username of the ldap authentication backend mount. - */ - private final String username; - - /** - * Password of the ldap authentication backend mount. - */ - private final CharSequence password; - - private LdapAuthenticationOptions(String username, CharSequence password, String path) { - this.username = username; - this.password = password; - this.path = path; - } - - public static LdapAuthenticationOptionsBuilder builder() { - return new LdapAuthenticationOptionsBuilder(); - } - - public String getUsername() { - return username; - } - - public CharSequence getPassword() { - return password; - } - - public String getPath() { - return path; - } - - public static class LdapAuthenticationOptionsBuilder { - - @Nullable - private String username; - - @Nullable - private CharSequence password; - - private String path = DEFAULT_LDAP_AUTHENTICATION_PATH; - - LdapAuthenticationOptionsBuilder() { - } - - public LdapAuthenticationOptionsBuilder username(String username) { - this.username = username; - return this; - } - - public LdapAuthenticationOptionsBuilder password(CharSequence password) { - this.password = password; - return this; - } - - public LdapAuthenticationOptionsBuilder path(String path) { - this.path = path; - return this; - } - - public LdapAuthenticationOptions build() { - Assert.notNull(this.username, "Username must not be null"); - Assert.notNull(this.password, "Password must not be null"); - - return new LdapAuthenticationOptions(username, password, path); - } - } -} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthentication.java index 121478b8..d3ca3a01 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthentication.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthentication.java @@ -15,8 +15,12 @@ */ package org.springframework.vault.authentication; +import java.util.LinkedHashMap; +import java.util.Map; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.util.Assert; import org.springframework.vault.VaultException; import org.springframework.vault.client.VaultResponses; @@ -25,55 +29,94 @@ import org.springframework.vault.support.VaultToken; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestOperations; -import static java.util.Collections.singletonMap; -import static org.springframework.vault.authentication.AuthenticationUtil.getLoginPath; +import static org.springframework.vault.authentication.AuthenticationUtil.*; /** - * Username and password implementation of {@link ClientAuthentication}. + * Username and password implementation of {@link ClientAuthentication}. Can be used for + * {@code userpass}, {@code ldap}, {@code okta}, and {@code radius} authentication + * backends. * * @author Mikhael Sokolov + * @author Mark Paluch * @see UsernamePasswordAuthenticationOptions * @see RestOperations - * @see Username & password + * @see Username and password + * @see LDAP authentication + * @see Okta authentication + * @see RADIUS authentication * @since 2.4 */ public class UsernamePasswordAuthentication implements ClientAuthentication, AuthenticationStepsFactory { - private static final Log logger = LogFactory.getLog(UsernamePasswordAuthentication.class); + private static final Log logger = LogFactory.getLog(UsernamePasswordAuthentication.class); - private final UsernamePasswordAuthenticationOptions options; + private final UsernamePasswordAuthenticationOptions options; - private final RestOperations restOperations; + private final RestOperations restOperations; - public UsernamePasswordAuthentication(UsernamePasswordAuthenticationOptions options, RestOperations restOperations) { - Assert.notNull(options, "UsernamePasswordAuthenticationOptions must not be null"); - Assert.notNull(restOperations, "RestOperations must not be null"); + public UsernamePasswordAuthentication(UsernamePasswordAuthenticationOptions options, + RestOperations restOperations) { - this.options = options; - this.restOperations = restOperations; - } + Assert.notNull(options, "UsernamePasswordAuthenticationOptions must not be null"); + Assert.notNull(restOperations, "RestOperations must not be null"); - @Override - public VaultToken login() throws VaultException { - return createTokenUsingUsernamePasswordAuthentication(); - } + this.options = options; + this.restOperations = restOperations; + } - @Override - public AuthenticationSteps getAuthenticationSteps() { - return AuthenticationSteps - .fromSupplier(() -> singletonMap("password", options.getPassword())) - .login(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername())); - } + /** + * Creates a {@link AuthenticationSteps} for username/password authentication given + * {@link UsernamePasswordAuthenticationOptions}. + * @param options must not be {@literal null}. + * @return {@link AuthenticationSteps} for username/password authentication. + */ + public static AuthenticationSteps createAuthenticationSteps(UsernamePasswordAuthenticationOptions options) { - private VaultToken createTokenUsingUsernamePasswordAuthentication() { - try { - VaultResponse response = restOperations.postForObject(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()), singletonMap("password", options.getPassword()), VaultResponse.class); + Assert.notNull(options, "UsernamePasswordAuthenticationOptions must not be null"); - logger.debug("Login successful using username and password credentials"); + Map body = createLoginBody(options); - return LoginTokenUtil.from(response.getAuth()); - } catch (HttpStatusCodeException e) { - throw new VaultException(String.format("Cannot login using username and password: %s", VaultResponses.getError(e.getResponseBodyAsString())), e); - } - } -} \ No newline at end of file + return AuthenticationSteps.fromSupplier(() -> body) + .login(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername())); + } + + @Override + public VaultToken login() throws VaultException { + return createTokenUsingUsernamePasswordAuthentication(); + } + + @Override + public AuthenticationSteps getAuthenticationSteps() { + return createAuthenticationSteps(this.options); + } + + private VaultToken createTokenUsingUsernamePasswordAuthentication() { + + try { + VaultResponse response = restOperations.postForObject( + String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()), + createLoginBody(options), VaultResponse.class); + + logger.debug("Login successful using username and password credentials"); + + return LoginTokenUtil.from(response.getAuth()); + } + catch (HttpStatusCodeException e) { + throw new VaultException(String.format("Cannot login using username and password: %s", + VaultResponses.getError(e.getResponseBodyAsString())), e); + } + } + + private static Map createLoginBody(UsernamePasswordAuthenticationOptions options) { + + Map body = new LinkedHashMap<>(); + body.put("password", options.getPassword()); + + CharSequence totp = options.getTotp(); + if (totp != null) { + body.put("totp", totp); + } + return body; + } + +} diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationOptions.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationOptions.java index 519ab5d9..2c798b17 100644 --- a/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationOptions.java +++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationOptions.java @@ -15,87 +15,171 @@ */ package org.springframework.vault.authentication; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import javax.annotation.Nullable; - /** + * Authentication options for {@link UsernamePasswordAuthentication}. + * * @author Mikhael Sokolov + * @author Mark Paluch + * @since 2.4 + * @see UsernamePasswordAuthentication + * @see #builder() */ public class UsernamePasswordAuthenticationOptions { - public static final String DEFAULT_USERPASS_AUTHENTICATION_PATH = "userpass"; + public static final String DEFAULT_USERPASS_AUTHENTICATION_PATH = "userpass"; - /** - * Path of the userpass authentication backend mount. - */ - private final String path; + /** + * Path of the userpass authentication backend mount. + */ + private final String path; - /** - * Username of the userpass authentication backend mount. - */ - private final String username; + /** + * Username of the userpass authentication backend mount. + */ + private final String username; - /** - * Password of the userpass authentication backend mount. - */ - private final CharSequence password; + /** + * Password of the userpass authentication backend mount. + */ + private final CharSequence password; - private UsernamePasswordAuthenticationOptions(String username, CharSequence password, String path) { - this.username = username; - this.password = password; - this.path = path; - } + /** + * TOTP (one-time-token, optional). + */ + @Nullable + private final CharSequence totp; - public String getUsername() { - return username; - } + private UsernamePasswordAuthenticationOptions(String path, String username, CharSequence password, + @Nullable CharSequence totp) { + this.username = username; + this.password = password; + this.path = path; + this.totp = totp; + } - public CharSequence getPassword() { - return password; - } + /** + * @return a new {@link UsernamePasswordAuthenticationBuilder}. + */ + public static UsernamePasswordAuthenticationBuilder builder() { + return new UsernamePasswordAuthenticationBuilder(); + } - public String getPath() { - return path; - } + /** + * @return the path of the userpass authentication backend mount. + */ + public String getPath() { + return this.path; + } - public static UsernamePasswordAuthenticationBuilder builder() { - return new UsernamePasswordAuthenticationBuilder(); - } + /** + * @return the username. + */ + public String getUsername() { + return this.username; + } - public static class UsernamePasswordAuthenticationBuilder { + /** + * @return the password. + */ + public CharSequence getPassword() { + return this.password; + } - @Nullable - private String username; + /** + * @return the totp (one-time-token). Can be {@code null}. + */ + @Nullable + public CharSequence getTotp() { + return this.totp; + } - @Nullable - private CharSequence password; + /** + * Builder for {@link UsernamePasswordAuthenticationOptions}. + */ + public static class UsernamePasswordAuthenticationBuilder { - private String path = DEFAULT_USERPASS_AUTHENTICATION_PATH; + private String path = DEFAULT_USERPASS_AUTHENTICATION_PATH; - UsernamePasswordAuthenticationBuilder() { - } + @Nullable + private String username; - public UsernamePasswordAuthenticationBuilder username(String username) { - this.username = username; - return this; - } + @Nullable + private CharSequence password; - public UsernamePasswordAuthenticationBuilder password(CharSequence password) { - this.password = password; - return this; - } + @Nullable + private CharSequence totp; - public UsernamePasswordAuthenticationBuilder path(String path) { - this.path = path; - return this; - } + UsernamePasswordAuthenticationBuilder() { + } - public UsernamePasswordAuthenticationOptions build() { - Assert.notNull(this.username, "Username must not be null"); - Assert.notNull(this.password, "Password must not be null"); + /** + * Configure a {@code username} for userpass authentication. + * @param username must not be empty or {@literal null}. + * @return {@code this} {@link UsernamePasswordAuthenticationBuilder}. + */ + public UsernamePasswordAuthenticationBuilder username(String username) { + + Assert.hasText(username, "Username must not be null and not be empty"); + + this.username = username; + return this; + } + + /** + * Configure a {@code password} for userpass authentication. + * @param password must not be {@literal null}. + * @return {@code this} {@link UsernamePasswordAuthenticationBuilder}. + */ + public UsernamePasswordAuthenticationBuilder password(CharSequence password) { + + Assert.notNull(password, "Password must not be null"); + + this.password = password; + return this; + } + + /** + * Configure an optional {@code totp} (time-based one-time token) for + * userpass/Okta authentication. + * @param totp must not be {@literal null}. + * @return {@code this} {@link UsernamePasswordAuthenticationBuilder}. + */ + public UsernamePasswordAuthenticationBuilder totp(CharSequence totp) { + + Assert.notNull(password, "One-time token must not be null"); + + this.totp = totp; + return this; + } + + /** + * Configure the mount path. + * @param path must not be {@literal null} or empty. + * @return {@code this} {@link UsernamePasswordAuthenticationBuilder}. + */ + public UsernamePasswordAuthenticationBuilder path(String path) { + + Assert.hasText(path, "Path must not be empty"); + + this.path = path; + return this; + } + + /** + * Build a new {@link UsernamePasswordAuthenticationOptions} instance. + * @return a new {@link UsernamePasswordAuthenticationOptions}. + */ + public UsernamePasswordAuthenticationOptions build() { + + Assert.hasText(this.username, "Username must not be null and not be empty"); + Assert.notNull(this.password, "Password must not be null"); + + return new UsernamePasswordAuthenticationOptions(path, username, password, totp); + } + + } - return new UsernamePasswordAuthenticationOptions(username, password, path); - } - } } diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/LdapAuthenticationIntegrationTestBase.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LdapAuthenticationIntegrationTestBase.java deleted file mode 100644 index e751cdcd..00000000 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/LdapAuthenticationIntegrationTestBase.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2017-2021 the original author or authors. - * - * 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 - * - * https://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 org.springframework.vault.authentication; - -import org.junit.jupiter.api.BeforeEach; -import org.springframework.vault.support.Policy; -import org.springframework.vault.util.IntegrationTestSupport; - -import java.util.Map; - -import static java.util.Collections.singletonMap; -import static org.springframework.vault.authentication.LdapAuthenticationOptions.DEFAULT_LDAP_AUTHENTICATION_PATH; -import static org.springframework.vault.support.Policy.BuiltinCapabilities.*; - -/** - * Integration test base class for {@link LdapAuthentication} tests. - * - * @author Mikhael Sokolov - */ -public abstract class LdapAuthenticationIntegrationTestBase extends IntegrationTestSupport { - - static final Policy POLICY = Policy.of(Policy.Rule.builder().path("/*").capabilities(READ, CREATE, UPDATE).build()); - - protected final String username = "admin"; - protected final String password = "qwerty"; - - @BeforeEach - public void before() { - - if (!prepare().hasAuth(DEFAULT_LDAP_AUTHENTICATION_PATH)) { - prepare().mountAuth(DEFAULT_LDAP_AUTHENTICATION_PATH); - } - - prepare().getVaultOperations().opsForSys().createOrUpdatePolicy(DEFAULT_LDAP_AUTHENTICATION_PATH, POLICY); - prepare().getVaultOperations().doWithSession(restOperations -> restOperations.postForEntity(String.format("auth/%s/users/%s", DEFAULT_LDAP_AUTHENTICATION_PATH, username), singletonMap("password", password), Map.class)); - } -} \ No newline at end of file diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/LdapAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/LdapAuthenticationIntegrationTests.java deleted file mode 100644 index d4c4417a..00000000 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/LdapAuthenticationIntegrationTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2016-2021 the original author or authors. - * - * 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 - * - * https://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 org.springframework.vault.authentication; - -import org.junit.jupiter.api.Test; -import org.springframework.vault.client.VaultClients; -import org.springframework.vault.support.VaultToken; -import org.springframework.web.client.RestTemplate; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests for {@link LdapAuthentication}. - * - * @author Mikhael Sokolov - */ -class LdapAuthenticationIntegrationTests extends LdapAuthenticationIntegrationTestBase { - - @Test - void shouldLoginSuccessfully() { - - RestTemplate restTemplate = VaultClients.createRestTemplate(); - LdapAuthenticationOptions options = LdapAuthenticationOptions.builder() - .username(username) - .password(password) - .build(); - - LdapAuthentication authentication = new LdapAuthentication(options, restTemplate); - VaultToken login = authentication.login(); - - assertThat(login.getToken()).isNotEmpty(); - } -} \ No newline at end of file diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationIntegrationTestBase.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationIntegrationTestBase.java index 50780ab1..9100028b 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationIntegrationTestBase.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationIntegrationTestBase.java @@ -15,14 +15,15 @@ */ package org.springframework.vault.authentication; +import java.util.Map; + import org.junit.jupiter.api.BeforeEach; + import org.springframework.vault.support.Policy; import org.springframework.vault.util.IntegrationTestSupport; -import java.util.Map; - -import static java.util.Collections.singletonMap; -import static org.springframework.vault.authentication.UsernamePasswordAuthenticationOptions.DEFAULT_USERPASS_AUTHENTICATION_PATH; +import static java.util.Collections.*; +import static org.springframework.vault.authentication.UsernamePasswordAuthenticationOptions.*; import static org.springframework.vault.support.Policy.BuiltinCapabilities.*; /** @@ -32,19 +33,24 @@ import static org.springframework.vault.support.Policy.BuiltinCapabilities.*; */ public abstract class UsernamePasswordAuthenticationIntegrationTestBase extends IntegrationTestSupport { - static final Policy POLICY = Policy.of(Policy.Rule.builder().path("/*").capabilities(READ, CREATE, UPDATE).build()); + static final Policy POLICY = Policy.of(Policy.Rule.builder().path("/*").capabilities(READ, CREATE, UPDATE).build()); - protected final String username = "admin"; - protected final String password = "qwerty"; + protected final String username = "admin"; - @BeforeEach - public void before() { + protected final String password = "qwerty"; - if (!prepare().hasAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH)) { - prepare().mountAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH); - } + @BeforeEach + public void before() { - prepare().getVaultOperations().opsForSys().createOrUpdatePolicy(DEFAULT_USERPASS_AUTHENTICATION_PATH, POLICY); - prepare().getVaultOperations().doWithSession(restOperations -> restOperations.postForEntity(String.format("auth/%s/users/%s", DEFAULT_USERPASS_AUTHENTICATION_PATH, username), singletonMap("password", password), Map.class)); - } -} \ No newline at end of file + if (!prepare().hasAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH)) { + prepare().mountAuth(DEFAULT_USERPASS_AUTHENTICATION_PATH); + } + + prepare().getVaultOperations().opsForSys().createOrUpdatePolicy(DEFAULT_USERPASS_AUTHENTICATION_PATH, POLICY); + prepare().getVaultOperations() + .doWithSession(restOperations -> restOperations.postForEntity( + String.format("auth/%s/users/%s", DEFAULT_USERPASS_AUTHENTICATION_PATH, username), + singletonMap("password", password), Map.class)); + } + +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationIntegrationTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationIntegrationTests.java index 4c12a4c0..3e6e8a1f 100644 --- a/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationIntegrationTests.java +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationIntegrationTests.java @@ -16,11 +16,13 @@ package org.springframework.vault.authentication; import org.junit.jupiter.api.Test; -import org.springframework.vault.client.VaultClients; + import org.springframework.vault.support.VaultToken; +import org.springframework.vault.util.Settings; +import org.springframework.vault.util.TestRestTemplateFactory; import org.springframework.web.client.RestTemplate; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.*; /** * Integration tests for {@link UsernamePasswordAuthentication}. @@ -29,18 +31,31 @@ import static org.assertj.core.api.Assertions.assertThat; */ class UsernamePasswordAuthenticationIntegrationTests extends UsernamePasswordAuthenticationIntegrationTestBase { - @Test - void shouldLoginSuccessfully() { + RestTemplate restTemplate = TestRestTemplateFactory.create(Settings.createSslConfiguration()); - RestTemplate restTemplate = VaultClients.createRestTemplate(); - UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder() - .username(username) - .password(password) - .build(); + @Test + void shouldLoginSuccessfully() { - UsernamePasswordAuthentication authentication = new UsernamePasswordAuthentication(options, restTemplate); - VaultToken login = authentication.login(); + UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder() + .username(username).password(password).build(); - assertThat(login.getToken()).isNotEmpty(); - } -} \ No newline at end of file + UsernamePasswordAuthentication authentication = new UsernamePasswordAuthentication(options, restTemplate); + VaultToken login = authentication.login(); + + assertThat(login.getToken()).isNotEmpty(); + } + + @Test + void shouldLoginUsingAuthenticationSteps() { + + UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder() + .username(username).password(password).build(); + + AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor( + UsernamePasswordAuthentication.createAuthenticationSteps(options), restTemplate); + VaultToken login = executor.login(); + + assertThat(login.getToken()).isNotEmpty(); + } + +} diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationUnitTests.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationUnitTests.java new file mode 100644 index 00000000..14c8f636 --- /dev/null +++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/UsernamePasswordAuthenticationUnitTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021 the original author or authors. + * + * 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 + * + * https://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 org.springframework.vault.authentication; + +import java.time.Duration; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.vault.client.VaultClients; +import org.springframework.vault.support.VaultToken; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.*; +import static org.springframework.test.web.client.response.MockRestResponseCreators.*; + +/** + * Unit tests for {@link UsernamePasswordAuthentication}. + * + * @author Mark Paluch + */ +class UsernamePasswordAuthenticationUnitTests { + + RestTemplate restTemplate; + + MockRestServiceServer mockRest; + + @BeforeEach + void before() { + + RestTemplate restTemplate = VaultClients.createRestTemplate(); + restTemplate.setUriTemplateHandler(new VaultClients.PrefixAwareUriTemplateHandler()); + + this.mockRest = MockRestServiceServer.createServer(restTemplate); + this.restTemplate = restTemplate; + } + + @Test + void shouldLoginWithTotp() { + + UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder().path("okta") + .username("walter").password("heisenberg").totp("123456").build(); + + UsernamePasswordAuthentication sut = new UsernamePasswordAuthentication(options, this.restTemplate); + + this.mockRest.expect(requestTo("/auth/okta/login/walter")).andExpect(method(HttpMethod.POST)) + .andExpect(jsonPath("$.password").value("heisenberg")).andExpect(jsonPath("$.totp").value("123456")) + .andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON).body( + "{" + "\"auth\":{\"client_token\":\"my-token\", \"renewable\": true, \"lease_duration\": 10}" + + "}")); + + VaultToken login = sut.login(); + + assertThat(login).isInstanceOf(LoginToken.class); + assertThat(login.getToken()).isEqualTo("my-token"); + assertThat(((LoginToken) login).getLeaseDuration()).isEqualTo(Duration.ofSeconds(10)); + assertThat(((LoginToken) login).isRenewable()).isTrue(); + } + +} diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 158e35fb..9609ea9c 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -1,6 +1,11 @@ [[new-features]] == New & Noteworthy +[[new-features.2-4-0]] +=== What's new in Spring Vault 2.4 + +* Support for <> for Username/Password, LDAP, Okta, and RADIUS authentication. + [[new-features.2-3-0]] === What's new in Spring Vault 2.3 diff --git a/src/main/asciidoc/reference/authentication.adoc b/src/main/asciidoc/reference/authentication.adoc index ae6f658f..0a5a9717 100644 --- a/src/main/asciidoc/reference/authentication.adoc +++ b/src/main/asciidoc/reference/authentication.adoc @@ -784,6 +784,50 @@ See also: * https://www.vaultproject.io/docs/auth/kubernetes.html[Vault Documentation: Using the Kubernetes auth backend] * https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/[Kubernetes Documentation: Configure Service Accounts for Pods] +[[vault.authentication.userpass]] +== Username/Password authentication + +Username/Password is typically a end-user authentication scheme. +Using username and password is supported by multiple Vault authentication backends: + +* Username and Password (`userpass`) +* LDAP (`ldap`) +* Okta (`okta`, supports additionaly time-based one-time tokens) +* RADIUS (`radius`) + +`UserPasswordAuthenticationOptions` can be used with all above mentioned authentication backends as the Login API is similar across all mechanisms. +Please ensure to use the appropriate auth mount path when configuring `UserPasswordAuthenticationOptions`. + +.Configuring `UserPasswordAuthentication` +==== +[source,java] +---- +@Configuration +class AppConfig extends AbstractVaultConfiguration { + + // … + + @Override + public ClientAuthentication clientAuthentication() { + + UserPasswordAuthenticationOptions options = UserPasswordAuthenticationOptions.builder() + .username(…).password(…).build(); + + return new UserPasswordAuthentication(options, restOperations()); + } + + // … +} +---- +==== + +See also: + +* https://www.vaultproject.io/api-docs/auth/userpass[Vault Documentation: Using the Userpass auth backend] +* https://www.vaultproject.io/api-docs/auth/ldap[Vault Documentation: Using the LDAP auth backend] +* https://www.vaultproject.io/api-docs/auth/radius[Vault Documentation: Using the RADIUS auth backend] +* https://www.vaultproject.io/api-docs/auth/okta[Vault Documentation: Using the Okta auth backend] + [[vault.authentication.steps]] == Authentication Steps