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