Add support for username/password authentication.

Closes gh-668
This commit is contained in:
sokomishalov
2021-11-03 14:44:22 +03:00
committed by Mark Paluch
parent 0e7b65d5c0
commit 1b33f4e195
8 changed files with 552 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
/*
* 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 <a href="https://www.vaultproject.io/docs/auth/ldap">LDAP</a>
* @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);
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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);
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* 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;
/**
* Username and password implementation of {@link ClientAuthentication}.
*
* @author Mikhael Sokolov
* @see UsernamePasswordAuthenticationOptions
* @see RestOperations
* @see <a href="https://www.vaultproject.io/docs/auth/userpass">Username & password</a>
* @since 2.4
*/
public class UsernamePasswordAuthentication implements ClientAuthentication, AuthenticationStepsFactory {
private static final Log logger = LogFactory.getLog(UsernamePasswordAuthentication.class);
private final UsernamePasswordAuthenticationOptions options;
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");
this.options = options;
this.restOperations = restOperations;
}
@Override
public VaultToken login() throws VaultException {
return createTokenUsingUsernamePasswordAuthentication();
}
@Override
public AuthenticationSteps getAuthenticationSteps() {
return AuthenticationSteps
.fromSupplier(() -> singletonMap("password", options.getPassword()))
.login(String.format("%s/%s", getLoginPath(options.getPath()), options.getUsername()));
}
private VaultToken createTokenUsingUsernamePasswordAuthentication() {
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 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);
}
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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 UsernamePasswordAuthenticationOptions {
public static final String DEFAULT_USERPASS_AUTHENTICATION_PATH = "userpass";
/**
* Path of the userpass authentication backend mount.
*/
private final String path;
/**
* Username of the userpass authentication backend mount.
*/
private final String username;
/**
* 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;
}
public String getUsername() {
return username;
}
public CharSequence getPassword() {
return password;
}
public String getPath() {
return path;
}
public static UsernamePasswordAuthenticationBuilder builder() {
return new UsernamePasswordAuthenticationBuilder();
}
public static class UsernamePasswordAuthenticationBuilder {
@Nullable
private String username;
@Nullable
private CharSequence password;
private String path = DEFAULT_USERPASS_AUTHENTICATION_PATH;
UsernamePasswordAuthenticationBuilder() {
}
public UsernamePasswordAuthenticationBuilder username(String username) {
this.username = username;
return this;
}
public UsernamePasswordAuthenticationBuilder password(CharSequence password) {
this.password = password;
return this;
}
public UsernamePasswordAuthenticationBuilder path(String path) {
this.path = path;
return this;
}
public UsernamePasswordAuthenticationOptions build() {
Assert.notNull(this.username, "Username must not be null");
Assert.notNull(this.password, "Password must not be null");
return new UsernamePasswordAuthenticationOptions(username, password, path);
}
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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));
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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();
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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.UsernamePasswordAuthenticationOptions.DEFAULT_USERPASS_AUTHENTICATION_PATH;
import static org.springframework.vault.support.Policy.BuiltinCapabilities.*;
/**
* Integration test base class for {@link UsernamePasswordAuthentication} tests.
*
* @author Mikhael Sokolov
*/
public abstract class UsernamePasswordAuthenticationIntegrationTestBase 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_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));
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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 UsernamePasswordAuthentication}.
*
* @author Mikhael Sokolov
*/
class UsernamePasswordAuthenticationIntegrationTests extends UsernamePasswordAuthenticationIntegrationTestBase {
@Test
void shouldLoginSuccessfully() {
RestTemplate restTemplate = VaultClients.createRestTemplate();
UsernamePasswordAuthenticationOptions options = UsernamePasswordAuthenticationOptions.builder()
.username(username)
.password(password)
.build();
UsernamePasswordAuthentication authentication = new UsernamePasswordAuthentication(options, restTemplate);
VaultToken login = authentication.login();
assertThat(login.getToken()).isNotEmpty();
}
}