mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-07-02 21:18:05 +02:00
Integrate Plugin Center myCloudogu Authentication (#1884)
Allows scm-manager instances to authenticate with the configured plugin center. If the default plugin center is used, a myCloudogu account is used for authentication which in turn enables downloading special myCloudogu plugins directly through the plugin administration page. Co-authored-by: Konstantin Schaper <konstantin.schaper@cloudogu.com> Co-authored-by: Matthias Thieroff <93515444+mthieroff@users.noreply.github.com> Co-authored-by: Philipp Ahrendt <philipp.ahrendt@cloudogu.com>
This commit is contained in:
@@ -93,7 +93,7 @@ class AvailablePluginResourceTest {
|
||||
|
||||
@BeforeEach
|
||||
void prepareEnvironment() {
|
||||
pluginRootResource = new PluginRootResource(null, availablePluginResourceProvider, null);
|
||||
pluginRootResource = new PluginRootResource(null, availablePluginResourceProvider, null, null);
|
||||
when(availablePluginResourceProvider.get()).thenReturn(availablePluginResource);
|
||||
dispatcher.addSingletonResource(pluginRootResource);
|
||||
}
|
||||
|
||||
@@ -24,21 +24,18 @@
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.internal.util.collections.Sets;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.security.AnonymousMode;
|
||||
|
||||
import java.util.Arrays;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
public class ConfigDtoToScmConfigurationMapperTest {
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ConfigDtoToScmConfigurationMapperTest {
|
||||
|
||||
@InjectMocks
|
||||
private ConfigDtoToScmConfigurationMapperImpl mapper;
|
||||
@@ -46,53 +43,49 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
||||
private final String[] expectedExcludes = {"ex", "clude"};
|
||||
private final String[] expectedUsers = {"trillian", "arthur"};
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFields() {
|
||||
void shouldMapFields() {
|
||||
ConfigDto dto = createDefaultDto();
|
||||
ScmConfiguration config = mapper.map(dto);
|
||||
|
||||
assertEquals("prPw", config.getProxyPassword());
|
||||
assertEquals(42, config.getProxyPort());
|
||||
assertEquals("srvr", config.getProxyServer());
|
||||
assertEquals("user", config.getProxyUser());
|
||||
assertTrue(config.isEnableProxy());
|
||||
assertEquals("realm", config.getRealmDescription());
|
||||
assertTrue(config.isDisableGroupingGrid());
|
||||
assertEquals("yyyy", config.getDateFormat());
|
||||
assertEquals(AnonymousMode.PROTOCOL_ONLY, config.getAnonymousMode());
|
||||
assertEquals("baseurl", config.getBaseUrl());
|
||||
assertTrue(config.isForceBaseUrl());
|
||||
assertEquals(41, config.getLoginAttemptLimit());
|
||||
assertTrue("proxyExcludes", config.getProxyExcludes().containsAll(Arrays.asList(expectedExcludes)));
|
||||
assertTrue(config.isSkipFailedAuthenticators());
|
||||
assertEquals("https://plug.ins", config.getPluginUrl());
|
||||
assertEquals(40, config.getLoginAttemptLimitTimeout());
|
||||
assertTrue(config.isEnabledXsrfProtection());
|
||||
assertFalse(config.isEnabledUserConverter());
|
||||
assertEquals("username", config.getNamespaceStrategy());
|
||||
assertEquals("https://scm-manager.org/login-info", config.getLoginInfoUrl());
|
||||
assertEquals("hitchhiker.mail", config.getMailDomainName());
|
||||
assertTrue("emergencyContacts", config.getEmergencyContacts().containsAll(Arrays.asList(expectedUsers)));
|
||||
assertThat(config.getProxyPassword()).isEqualTo("prPw");
|
||||
assertThat(config.getProxyPort()).isEqualTo(42);
|
||||
assertThat(config.getProxyServer()).isEqualTo("srvr");
|
||||
assertThat(config.getProxyUser()).isEqualTo("user");
|
||||
assertThat(config.isEnableProxy()).isTrue();
|
||||
assertThat(config.getRealmDescription()).isEqualTo("realm");
|
||||
assertThat(config.isDisableGroupingGrid()).isTrue();
|
||||
assertThat(config.getDateFormat()).isEqualTo("yyyy");
|
||||
assertThat(config.getAnonymousMode()).isSameAs(AnonymousMode.PROTOCOL_ONLY);
|
||||
assertThat(config.getBaseUrl()).isEqualTo("baseurl");
|
||||
assertThat(config.isForceBaseUrl()).isTrue();
|
||||
assertThat(config.getLoginAttemptLimit()).isEqualTo(41);
|
||||
assertThat(config.getProxyExcludes()).contains(expectedExcludes);
|
||||
assertThat(config.isSkipFailedAuthenticators()).isTrue();
|
||||
assertThat(config.getPluginUrl()).isEqualTo("https://plug.ins");
|
||||
assertThat(config.getPluginAuthUrl()).isEqualTo("https://plug.ins/oidc");
|
||||
assertThat(config.getLoginAttemptLimitTimeout()).isEqualTo(40);
|
||||
assertThat(config.isEnabledXsrfProtection()).isTrue();
|
||||
assertThat(config.isEnabledUserConverter()).isFalse();
|
||||
assertThat(config.getNamespaceStrategy()).isEqualTo("username");
|
||||
assertThat(config.getLoginInfoUrl()).isEqualTo("https://scm-manager.org/login-info");
|
||||
assertThat(config.getMailDomainName()).isEqualTo("hitchhiker.mail");
|
||||
assertThat(config.getEmergencyContacts()).contains(expectedUsers);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapAnonymousAccessFieldToAnonymousMode() {
|
||||
void shouldMapAnonymousAccessFieldToAnonymousMode() {
|
||||
ConfigDto dto = createDefaultDto();
|
||||
|
||||
ScmConfiguration config = mapper.map(dto);
|
||||
|
||||
assertEquals(AnonymousMode.PROTOCOL_ONLY, config.getAnonymousMode());
|
||||
assertThat(config.getAnonymousMode()).isSameAs(AnonymousMode.PROTOCOL_ONLY);
|
||||
|
||||
dto.setAnonymousMode(null);
|
||||
dto.setAnonymousAccessEnabled(false);
|
||||
ScmConfiguration config2 = mapper.map(dto);
|
||||
|
||||
assertEquals(AnonymousMode.OFF, config2.getAnonymousMode());
|
||||
assertThat(config2.getAnonymousMode()).isSameAs(AnonymousMode.OFF);
|
||||
}
|
||||
|
||||
private ConfigDto createDefaultDto() {
|
||||
@@ -112,6 +105,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
||||
configDto.setProxyExcludes(Sets.newSet(expectedExcludes));
|
||||
configDto.setSkipFailedAuthenticators(true);
|
||||
configDto.setPluginUrl("https://plug.ins");
|
||||
configDto.setPluginAuthUrl("https://plug.ins/oidc");
|
||||
configDto.setLoginAttemptLimitTimeout(40);
|
||||
configDto.setEnabledXsrfProtection(true);
|
||||
configDto.setNamespaceStrategy("username");
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.junit.Test;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.initialization.InitializationFinisher;
|
||||
import sonia.scm.plugin.PluginCenterAuthenticator;
|
||||
import sonia.scm.search.SearchEngine;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -51,7 +52,6 @@ public class IndexResourceTest {
|
||||
private SCMContextProvider scmContextProvider;
|
||||
private IndexResource indexResource;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUpObjectUnderTest() {
|
||||
this.configuration = new ScmConfiguration();
|
||||
@@ -63,10 +63,28 @@ public class IndexResourceTest {
|
||||
ResourceLinksMock.createMock(URI.create("/")),
|
||||
scmContextProvider,
|
||||
configuration,
|
||||
initializationFinisher, searchEngine);
|
||||
initializationFinisher,
|
||||
searchEngine
|
||||
);
|
||||
this.indexResource = new IndexResource(generator);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "dent", password = "secret")
|
||||
public void shouldRenderPluginCenterAuthLink() {
|
||||
IndexDto index = indexResource.getIndex();
|
||||
|
||||
Assertions.assertThat(index.getLinks().getLinkBy("pluginCenterAuth")).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(username = "trillian", password = "secret")
|
||||
public void shouldNotRenderPluginCenterLoginLinkIfPermissionsAreMissing() {
|
||||
IndexDto index = indexResource.getIndex();
|
||||
|
||||
Assertions.assertThat(index.getLinks().getLinkBy("pluginCenterAuth")).isNotPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldRenderLoginUrlsForUnauthenticatedRequest() {
|
||||
IndexDto index = indexResource.getIndex();
|
||||
|
||||
@@ -89,7 +89,7 @@ class InstalledPluginResourceTest {
|
||||
|
||||
@BeforeEach
|
||||
void prepareEnvironment() {
|
||||
pluginRootResource = new PluginRootResource(installedPluginResourceProvider, null, null);
|
||||
pluginRootResource = new PluginRootResource(installedPluginResourceProvider, null, null, null);
|
||||
when(installedPluginResourceProvider.get()).thenReturn(installedPluginResource);
|
||||
dispatcher.addSingletonResource(pluginRootResource);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class PendingPluginResourceTest {
|
||||
@BeforeEach
|
||||
void prepareEnvironment() {
|
||||
dispatcher.registerException(ShiroException.class, Response.Status.UNAUTHORIZED);
|
||||
PluginRootResource pluginRootResource = new PluginRootResource(null, null, Providers.of(pendingPluginResource));
|
||||
PluginRootResource pluginRootResource = new PluginRootResource(null, null, Providers.of(pendingPluginResource), null);
|
||||
dispatcher.addSingletonResource(pluginRootResource);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,469 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import com.google.inject.util.Providers;
|
||||
import lombok.Value;
|
||||
import org.github.sdorra.jse.ShiroExtension;
|
||||
import org.github.sdorra.jse.SubjectAware;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.plugin.AuthenticationInfo;
|
||||
import sonia.scm.plugin.FetchAccessTokenFailedException;
|
||||
import sonia.scm.plugin.PluginCenterAuthenticator;
|
||||
import sonia.scm.security.Impersonator;
|
||||
import sonia.scm.security.SecureParameterSerializer;
|
||||
import sonia.scm.security.XsrfExcludes;
|
||||
import sonia.scm.user.DisplayUser;
|
||||
import sonia.scm.user.UserDisplayManager;
|
||||
import sonia.scm.user.UserTestData;
|
||||
import sonia.scm.web.RestDispatcher;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static sonia.scm.api.v2.resources.PluginCenterAuthResource.*;
|
||||
|
||||
@ExtendWith({MockitoExtension.class, ShiroExtension.class})
|
||||
class PluginCenterAuthResourceTest {
|
||||
|
||||
private final RestDispatcher dispatcher = new RestDispatcher();
|
||||
|
||||
private final ScmConfiguration scmConfiguration = new ScmConfiguration();
|
||||
|
||||
@Mock
|
||||
private PluginCenterAuthenticator authenticator;
|
||||
|
||||
@Mock
|
||||
private XsrfExcludes excludes;
|
||||
|
||||
@Mock
|
||||
private ChallengeGenerator challengeGenerator;
|
||||
|
||||
@Mock
|
||||
private UserDisplayManager userDisplayManager;
|
||||
|
||||
@Mock
|
||||
private SecureParameterSerializer parameterSerializer;
|
||||
|
||||
@Mock
|
||||
private Impersonator impersonator;
|
||||
|
||||
@BeforeEach
|
||||
void setUpDispatcher() {
|
||||
ScmPathInfoStore pathInfoStore = new ScmPathInfoStore();
|
||||
pathInfoStore.set(rootPathInfo);
|
||||
|
||||
PluginCenterAuthResource resource = new PluginCenterAuthResource(
|
||||
pathInfoStore, authenticator, userDisplayManager,
|
||||
scmConfiguration, excludes, challengeGenerator,
|
||||
parameterSerializer, impersonator
|
||||
);
|
||||
|
||||
dispatcher.addSingletonResource(
|
||||
new PluginRootResource(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Providers.of(resource)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class GetAuthenticationInfo {
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyAuthenticationInfo() throws URISyntaxException, IOException {
|
||||
JsonNode root = getJson("/v2/plugins/auth");
|
||||
|
||||
assertThat(root.has("principal")).isFalse();
|
||||
assertThat(root.has("pluginCenterSubject")).isFalse();
|
||||
assertThat(root.has("date")).isFalse();
|
||||
assertThat(root.get("_links").get("self").get("href").asText()).isEqualTo("/v2/plugins/auth");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnTrueForIsDefault() throws URISyntaxException, IOException {
|
||||
JsonNode root = getJson("/v2/plugins/auth");
|
||||
|
||||
assertThat(root.get("default").asBoolean()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnFalseIfTheAuthUrlIsNotDefault() throws URISyntaxException, IOException {
|
||||
scmConfiguration.setPluginAuthUrl("https://plug.ins");
|
||||
|
||||
JsonNode root = getJson("/v2/plugins/auth");
|
||||
|
||||
assertThat(root.get("default").asBoolean()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(value = "marvin", permissions = "plugin:write")
|
||||
void shouldReturnLoginLinkIfPermitted() throws URISyntaxException, IOException {
|
||||
JsonNode root = getJson("/v2/plugins/auth");
|
||||
|
||||
assertThat(root.get("_links").get("login").get("href").asText()).isEqualTo("/v2/plugins/auth/login");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAuthenticationInfo() throws IOException, URISyntaxException {
|
||||
JsonNode root = requestAuthInfo();
|
||||
|
||||
assertThat(root.get("principal").asText()).isEqualTo("Tricia McMillan");
|
||||
assertThat(root.get("pluginCenterSubject").asText()).isEqualTo("tricia.mcmillan@hitchhiker.com");
|
||||
assertThat(root.get("date").asText()).isNotEmpty();
|
||||
assertThat(root.get("_links").get("self").get("href").asText()).isEqualTo("/v2/plugins/auth");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotReturnLogoutLinkWithoutWritePermission() throws IOException, URISyntaxException {
|
||||
JsonNode root = requestAuthInfo();
|
||||
|
||||
assertThat(root.get("_links").has("logout")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(value = "marvin", permissions = "plugin:write")
|
||||
void shouldReturnLogoutLinkIfPermitted() throws IOException, URISyntaxException {
|
||||
JsonNode root = requestAuthInfo();
|
||||
|
||||
assertThat(root.get("_links").get("logout").get("href").asText()).isEqualTo("/v2/plugins/auth");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(value = "marvin", permissions = "plugin:write")
|
||||
void shouldNotReturnLogoutLinkIfPermitted() throws IOException, URISyntaxException {
|
||||
JsonNode root = requestAuthInfo();
|
||||
|
||||
assertThat(root.get("_links").get("logout").get("href").asText()).isEqualTo("/v2/plugins/auth");
|
||||
}
|
||||
|
||||
private JsonNode requestAuthInfo() throws IOException, URISyntaxException {
|
||||
AuthenticationInfo info = new SimpleAuthenticationInfo(
|
||||
"trillian", "tricia.mcmillan@hitchhiker.com", Instant.now()
|
||||
);
|
||||
when(authenticator.getAuthenticationInfo()).thenReturn(Optional.of(info));
|
||||
|
||||
DisplayUser user = DisplayUser.from(UserTestData.createTrillian());
|
||||
when(userDisplayManager.get("trillian")).thenReturn(Optional.of(user));
|
||||
|
||||
return getJson("/v2/plugins/auth");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class Logout {
|
||||
|
||||
@Test
|
||||
void shouldLogout() throws URISyntaxException {
|
||||
MockHttpResponse response = request(MockHttpRequest.delete("/v2/plugins/auth"));
|
||||
|
||||
verify(authenticator).logout();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_NO_CONTENT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AuthRequest {
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectWithoutSourceParameter() throws URISyntaxException {
|
||||
MockHttpResponse response = get("/v2/plugins/auth/login");
|
||||
assertError(response, ERROR_SOURCE_MISSING);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectWithoutPluginAuthUrlParameter() throws URISyntaxException {
|
||||
scmConfiguration.setPluginAuthUrl("");
|
||||
|
||||
MockHttpResponse response = get("/v2/plugins/auth/login?source=/admin/plugins");
|
||||
assertError(response, ERROR_AUTHENTICATION_DISABLED);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectIfAlreadyAuthenticated() throws URISyntaxException {
|
||||
when(authenticator.isAuthenticated()).thenReturn(true);
|
||||
|
||||
MockHttpResponse response = get("/v2/plugins/auth/login?source=/admin/plugins");
|
||||
assertError(response, ERROR_ALREADY_AUTHENTICATED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware("trillian")
|
||||
void shouldReturnRedirectToPluginAuthUrl() throws URISyntaxException, IOException {
|
||||
when(challengeGenerator.create()).thenReturn("abcd");
|
||||
when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("def");
|
||||
|
||||
scmConfiguration.setPluginAuthUrl("https://plug.ins");
|
||||
|
||||
MockHttpResponse response = get("/v2/plugins/auth/login?source=/admin/plugins");
|
||||
assertRedirect(response, "https://plug.ins?instance=%2Fv2%2Fplugins%2Fauth%2Fcallback?params%3Ddef");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware("trillian")
|
||||
void shouldExcludeCallbackFromXsrf() throws URISyntaxException, IOException {
|
||||
when(challengeGenerator.create()).thenReturn("1234");
|
||||
when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("def");
|
||||
scmConfiguration.setPluginAuthUrl("https://plug.ins");
|
||||
|
||||
get("/v2/plugins/auth/login?source=/admin/plugins");
|
||||
|
||||
verify(excludes).add("/v2/plugins/auth/callback");
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware("trillian")
|
||||
void shouldSendAuthParameters() throws URISyntaxException, IOException {
|
||||
when(challengeGenerator.create()).thenReturn("abc123def");
|
||||
when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("xyz");
|
||||
|
||||
get("/v2/plugins/auth/login?source=/admin/plugins");
|
||||
|
||||
ArgumentCaptor<AuthParameter> captor = ArgumentCaptor.forClass(AuthParameter.class);
|
||||
verify(parameterSerializer).serialize(captor.capture());
|
||||
|
||||
AuthParameter parameter = captor.getValue();
|
||||
assertThat(parameter.getChallenge()).isEqualTo("abc123def");
|
||||
assertThat(parameter.getSource()).isEqualTo("/admin/plugins");
|
||||
assertThat(parameter.getPrincipal()).isEqualTo("trillian");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SubjectAware("marvin")
|
||||
class AbortAuthentication {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
lenient().when(challengeGenerator.create()).thenReturn("xyz");
|
||||
lenient().when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("secureParams");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectWithoutParams() throws URISyntaxException {
|
||||
MockHttpResponse response = get("/v2/plugins/auth/callback");
|
||||
assertError(response, ERROR_PARAMS_MISSING);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectWithoutChallenge() throws URISyntaxException, IOException {
|
||||
mockParams("marvin", null, "/");
|
||||
MockHttpResponse response = get("/v2/plugins/auth/callback?params=secureParams");
|
||||
assertError(response, ERROR_CHALLENGE_MISSING);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectWithChallengeMismatch() throws URISyntaxException, IOException {
|
||||
mockParams("marvin", "abc", "/repos");
|
||||
get("/v2/plugins/auth/login?source=/repos");
|
||||
MockHttpResponse response = get("/v2/plugins/auth/callback?params=secureParams");
|
||||
assertError(response, ERROR_CHALLENGE_DOES_NOT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRedirectToRoot() throws URISyntaxException, IOException {
|
||||
mockParams("marvin", "xyz", null);
|
||||
get("/v2/plugins/auth/login?source=/repos");
|
||||
MockHttpResponse response = get("/v2/plugins/auth/callback?params=secureParams");
|
||||
assertRedirect(response, "/");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRedirectToSource() throws URISyntaxException, IOException {
|
||||
mockParams("marvin", "xyz", "/repos");
|
||||
get("/v2/plugins/auth/login?source=/repos");
|
||||
MockHttpResponse response = get("/v2/plugins/auth/callback?params=secureParams");
|
||||
assertRedirect(response, "/repos");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveCallbackFromXsrf() throws URISyntaxException, IOException {
|
||||
mockParams("marvin", "xyz", "/repos");
|
||||
get("/v2/plugins/auth/login?source=/repos");
|
||||
get("/v2/plugins/auth/callback?params=secureParams");
|
||||
verify(excludes).remove("/v2/plugins/auth/callback");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SubjectAware("slarti")
|
||||
class AuthenticationCallback {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
lenient().when(challengeGenerator.create()).thenReturn("abc");
|
||||
lenient().when(parameterSerializer.serialize(any(AuthParameter.class))).thenReturn("secureParams");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectWithoutParameters() throws URISyntaxException {
|
||||
MockHttpResponse response = post("/v2/plugins/auth/callback", "trillian", "rf");
|
||||
assertError(response, ERROR_PARAMS_MISSING);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectWithoutChallengeParameter() throws URISyntaxException, IOException {
|
||||
mockParams("slarti", null, "/");
|
||||
MockHttpResponse response = post("/v2/plugins/auth/callback?params=secureParams", "slarti", "rf");
|
||||
assertError(response, ERROR_CHALLENGE_MISSING);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectWithChallengeMismatch() throws URISyntaxException, IOException {
|
||||
mockParams("slarti", "xyz", "/");
|
||||
get("/v2/plugins/auth/login?source=/repos");
|
||||
MockHttpResponse response = post("/v2/plugins/auth/callback?params=secureParams", "trillian", "rf");
|
||||
assertError(response, ERROR_CHALLENGE_DOES_NOT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorRedirectFromFailedAuthentication() throws URISyntaxException, IOException {
|
||||
mockParams("slarti", "abc", "/");
|
||||
FetchAccessTokenFailedException exception = new FetchAccessTokenFailedException("failed ...");
|
||||
doThrow(exception).when(authenticator).authenticate("slarti", "rf");
|
||||
get("/v2/plugins/auth/login?source=/repos");
|
||||
MockHttpResponse response = post("/v2/plugins/auth/callback?params=secureParams", "slarti", "rf");
|
||||
assertError(response, exception.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAuthenticate() throws URISyntaxException, IOException {
|
||||
mockParams("slarti", "abc", "/");
|
||||
get("/v2/plugins/auth/login?source=/repos");
|
||||
post("/v2/plugins/auth/callback?params=secureParams", "slarti", "refresh_token");
|
||||
verify(authenticator).authenticate("slarti", "refresh_token");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRedirectToSource() throws URISyntaxException, IOException {
|
||||
mockParams("slarti", "abc", "/users");
|
||||
get("/v2/plugins/auth/login?source=/users");
|
||||
MockHttpResponse response = post("/v2/plugins/auth/callback?params=secureParams", "slarti", "rrrrf");
|
||||
assertRedirect(response, "/users");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRemoveCallbackFromXsrf() throws URISyntaxException, IOException {
|
||||
mockParams("slarti", "abc", "/users");
|
||||
get("/v2/plugins/auth/login?source=/repos");
|
||||
post("/v2/plugins/auth/callback?params=secureParams", "slarti", "rf");
|
||||
verify(excludes).remove("/v2/plugins/auth/callback");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void mockParams(String principal, String challenge, String source) throws IOException {
|
||||
AuthParameter params = new AuthParameter(principal, challenge, source);
|
||||
when(parameterSerializer.deserialize("secureParams", AuthParameter.class)).thenReturn(params);
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
private MockHttpResponse post(String uri, String subject, String refreshToken) throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.post(uri);
|
||||
request.addFormHeader("subject", subject);
|
||||
request.addFormHeader("refresh_token", refreshToken);
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
private MockHttpResponse get(String uri) throws URISyntaxException {
|
||||
MockHttpRequest request = MockHttpRequest.get(uri);
|
||||
return request(request);
|
||||
}
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private JsonNode getJson(String uri) throws URISyntaxException, IOException {
|
||||
MockHttpResponse response = get(uri);
|
||||
return mapper.readTree(response.getContentAsString());
|
||||
}
|
||||
|
||||
private MockHttpResponse request(MockHttpRequest request) {
|
||||
MockHttpResponse response = new MockHttpResponse();
|
||||
dispatcher.invoke(request, response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private void assertError(MockHttpResponse response, String code) {
|
||||
assertRedirect(response, "/error/" + code);
|
||||
}
|
||||
|
||||
private void assertRedirect(MockHttpResponse response, String location) {
|
||||
assertRedirect(response, (locationHeader) -> assertThat(locationHeader).isEqualTo(location));
|
||||
}
|
||||
|
||||
private void assertRedirect(MockHttpResponse response, Consumer<String> location) {
|
||||
assertThat(response.getStatus()).isEqualTo(HttpServletResponse.SC_SEE_OTHER);
|
||||
location.accept(response.getOutputHeaders().getFirst("Location").toString());
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class SimpleAuthenticationInfo implements AuthenticationInfo {
|
||||
String principal;
|
||||
String pluginCenterSubject;
|
||||
Instant date;
|
||||
}
|
||||
|
||||
private static final ScmPathInfo rootPathInfo = new ScmPathInfo() {
|
||||
@Override
|
||||
public URI getApiRestUri() {
|
||||
return URI.create("/api");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getRootUri() {
|
||||
return URI.create("/");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -165,6 +165,16 @@ class PluginDtoMapperTest {
|
||||
.isEqualTo("https://hitchhiker.com/v2/plugins/available/scm-cas-plugin/install?restart=true");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotAppendInstallLinkWithEmptyDownloadUrl() {
|
||||
when(subject.isPermitted("plugin:write")).thenReturn(true);
|
||||
AvailablePlugin plugin = createAvailable(createPluginInformation(), "");
|
||||
|
||||
PluginDto dto = mapper.mapAvailable(plugin);
|
||||
assertThat(dto.getLinks().hasLink("install")).isFalse();
|
||||
assertThat(dto.getLinks().hasLink("installWithRestart")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnMiscellaneousIfCategoryIsNull() {
|
||||
PluginInformation information = createPluginInformation();
|
||||
|
||||
@@ -28,26 +28,28 @@ import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.subject.support.SubjectThreadState;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.apache.shiro.util.ThreadState;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.internal.util.collections.Sets;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.security.AnonymousMode;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.MockitoAnnotations.initMocks;
|
||||
|
||||
public class ScmConfigurationToConfigDtoMapperTest {
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ScmConfigurationToConfigDtoMapperTest {
|
||||
|
||||
private final URI baseUri = URI.create("http://example.com/base/");
|
||||
|
||||
@@ -65,78 +67,84 @@ public class ScmConfigurationToConfigDtoMapperTest {
|
||||
|
||||
private URI expectedBaseUri;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
initMocks(this);
|
||||
@BeforeEach
|
||||
void init() {
|
||||
expectedBaseUri = baseUri.resolve(ConfigResource.CONFIG_PATH_V2);
|
||||
subjectThreadState.bind();
|
||||
ThreadContext.bind(subject);
|
||||
}
|
||||
|
||||
@After
|
||||
@AfterEach
|
||||
public void unbindSubject() {
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFields() {
|
||||
void shouldMapFields() {
|
||||
ScmConfiguration config = createConfiguration();
|
||||
|
||||
when(subject.isPermitted("configuration:write:global")).thenReturn(true);
|
||||
ConfigDto dto = mapper.map(config);
|
||||
|
||||
assertEquals("heartOfGold", dto.getProxyPassword());
|
||||
assertEquals(1234, dto.getProxyPort());
|
||||
assertEquals("proxyserver", dto.getProxyServer());
|
||||
assertEquals("trillian", dto.getProxyUser());
|
||||
assertTrue(dto.isEnableProxy());
|
||||
assertEquals("description", dto.getRealmDescription());
|
||||
assertTrue(dto.isDisableGroupingGrid());
|
||||
assertEquals("dd", dto.getDateFormat());
|
||||
assertSame(AnonymousMode.FULL, dto.getAnonymousMode());
|
||||
assertEquals("baseurl", dto.getBaseUrl());
|
||||
assertTrue(dto.isForceBaseUrl());
|
||||
assertEquals(1, dto.getLoginAttemptLimit());
|
||||
assertTrue("proxyExcludes", dto.getProxyExcludes().containsAll(Arrays.asList(expectedExcludes)));
|
||||
assertTrue(dto.isSkipFailedAuthenticators());
|
||||
assertEquals("pluginurl", dto.getPluginUrl());
|
||||
assertEquals(2, dto.getLoginAttemptLimitTimeout());
|
||||
assertTrue(dto.isEnabledXsrfProtection());
|
||||
assertEquals("username", dto.getNamespaceStrategy());
|
||||
assertEquals("https://scm-manager.org/login-info", dto.getLoginInfoUrl());
|
||||
assertEquals("https://www.scm-manager.org/download/rss.xml", dto.getReleaseFeedUrl());
|
||||
assertEquals("scm-manager.local", dto.getMailDomainName());
|
||||
assertTrue("emergencyContacts", dto.getEmergencyContacts().containsAll(Arrays.asList(expectedUsers)));
|
||||
assertThat(dto.getProxyPassword()).isEqualTo("heartOfGold");
|
||||
assertThat(dto.getProxyPort()).isEqualTo(1234);
|
||||
assertThat(dto.getProxyServer()).isEqualTo("proxyserver");
|
||||
assertThat(dto.getProxyUser()).isEqualTo("trillian");
|
||||
assertThat(dto.isEnableProxy()).isTrue();
|
||||
assertThat(dto.getRealmDescription()).isEqualTo("description");
|
||||
assertThat(dto.isDisableGroupingGrid()).isTrue();
|
||||
assertThat(dto.getDateFormat()).isEqualTo("dd");
|
||||
assertThat(dto.getAnonymousMode()).isSameAs(AnonymousMode.FULL);
|
||||
assertThat(dto.getBaseUrl()).isEqualTo("baseurl");
|
||||
assertThat(dto.isForceBaseUrl()).isTrue();
|
||||
assertThat(dto.getLoginAttemptLimit()).isOne();
|
||||
assertThat(dto.getProxyExcludes()).contains(expectedExcludes);
|
||||
assertThat(dto.isSkipFailedAuthenticators()).isTrue();
|
||||
assertThat(dto.getPluginUrl()).isEqualTo("https://plug.ins");
|
||||
assertThat(dto.getPluginAuthUrl()).isEqualTo("https://plug.ins/oidc");
|
||||
assertThat(dto.getLoginAttemptLimitTimeout()).isEqualTo(2);
|
||||
assertThat(dto.isEnabledXsrfProtection()).isTrue();
|
||||
assertThat(dto.getNamespaceStrategy()).isEqualTo("username");
|
||||
assertThat(dto.getLoginInfoUrl()).isEqualTo("https://scm-manager.org/login-info");
|
||||
assertThat(dto.getReleaseFeedUrl()).isEqualTo("https://www.scm-manager.org/download/rss.xml");
|
||||
assertThat(dto.getMailDomainName()).isEqualTo("scm-manager.local");
|
||||
assertThat(dto.getEmergencyContacts()).contains(expectedUsers);
|
||||
assertLinks(dto);
|
||||
}
|
||||
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
|
||||
private void assertLinks(ConfigDto dto) {
|
||||
assertThat(dto.getLinks().getLinkBy("self"))
|
||||
.hasValueSatisfying(link -> assertThat(link.getHref()).isEqualTo(expectedBaseUri.toString()));
|
||||
assertThat(dto.getLinks().getLinkBy("update"))
|
||||
.hasValueSatisfying(link -> assertThat(link.getHref()).isEqualTo(expectedBaseUri.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapFieldsWithoutUpdate() {
|
||||
void shouldMapFieldsWithoutUpdate() {
|
||||
ScmConfiguration config = createConfiguration();
|
||||
|
||||
when(subject.hasRole("configuration:write:global")).thenReturn(false);
|
||||
ConfigDto dto = mapper.map(config);
|
||||
|
||||
assertEquals("baseurl", dto.getBaseUrl());
|
||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||
assertFalse(dto.getLinks().hasLink("update"));
|
||||
assertThat(dto.getBaseUrl()).isEqualTo("baseurl");
|
||||
assertThat(dto.getLinks().getLinkBy("self"))
|
||||
.hasValueSatisfying(link -> assertThat(link.getHref()).isEqualTo(expectedBaseUri.toString()));
|
||||
assertThat(dto.getLinks().hasLink("update")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldMapAnonymousAccessField() {
|
||||
void shouldMapAnonymousAccessField() {
|
||||
ScmConfiguration config = createConfiguration();
|
||||
|
||||
when(subject.hasRole("configuration:write:global")).thenReturn(false);
|
||||
ConfigDto dto = mapper.map(config);
|
||||
|
||||
assertTrue(dto.isAnonymousAccessEnabled());
|
||||
assertThat(dto.isAnonymousAccessEnabled()).isTrue();
|
||||
|
||||
config.setAnonymousMode(AnonymousMode.OFF);
|
||||
ConfigDto secondDto = mapper.map(config);
|
||||
|
||||
assertFalse(secondDto.isAnonymousAccessEnabled());
|
||||
assertThat(secondDto.isAnonymousAccessEnabled()).isFalse();
|
||||
}
|
||||
|
||||
private ScmConfiguration createConfiguration() {
|
||||
@@ -155,7 +163,8 @@ public class ScmConfigurationToConfigDtoMapperTest {
|
||||
config.setLoginAttemptLimit(1);
|
||||
config.setProxyExcludes(Sets.newSet(expectedExcludes));
|
||||
config.setSkipFailedAuthenticators(true);
|
||||
config.setPluginUrl("pluginurl");
|
||||
config.setPluginUrl("https://plug.ins");
|
||||
config.setPluginAuthUrl("https://plug.ins/oidc");
|
||||
config.setLoginAttemptLimitTimeout(2);
|
||||
config.setEnabledXsrfProtection(true);
|
||||
config.setNamespaceStrategy("username");
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.plugin;
|
||||
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.github.sdorra.jse.ShiroExtension;
|
||||
import org.github.sdorra.jse.SubjectAware;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
import sonia.scm.net.ahc.AdvancedHttpRequestWithBody;
|
||||
import sonia.scm.net.ahc.AdvancedHttpResponse;
|
||||
import sonia.scm.plugin.PluginCenterAuthenticator.RefreshResponse;
|
||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static sonia.scm.plugin.PluginCenterAuthenticator.*;
|
||||
import static sonia.scm.plugin.PluginCenterAuthenticator.RefreshRequest;
|
||||
|
||||
@ExtendWith({MockitoExtension.class, ShiroExtension.class})
|
||||
class PluginCenterAuthenticatorTest {
|
||||
|
||||
private PluginCenterAuthenticator authenticator;
|
||||
|
||||
@Mock
|
||||
private AdvancedHttpClient advancedHttpClient;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_SELF)
|
||||
private AdvancedHttpRequestWithBody request;
|
||||
|
||||
private ScmConfiguration scmConfiguration;
|
||||
|
||||
@Mock
|
||||
private ScmEventBus eventBus;
|
||||
|
||||
private final InMemoryConfigurationStoreFactory factory = InMemoryConfigurationStoreFactory.create();
|
||||
|
||||
@BeforeEach
|
||||
void setUpObjectUnderTest() {
|
||||
scmConfiguration = new ScmConfiguration();
|
||||
authenticator = new PluginCenterAuthenticator(factory, scmConfiguration, advancedHttpClient, eventBus);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware("marvin")
|
||||
void shouldFailAuthenticationWithoutPermissions() {
|
||||
assertThrows(AuthorizationException.class, () -> authenticator.authenticate("marvin@hitchhiker.com", "refresh-token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware(value = "marvin", permissions = "plugin:read")
|
||||
void shouldFailAuthenticationWithReadPermissions() {
|
||||
assertThrows(AuthorizationException.class, () -> authenticator.authenticate("marvin@hitchhiker.com", "refresh-token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware("marvin")
|
||||
void shouldFailToFetchAccessTokenWithoutPermission() {
|
||||
assertThrows(AuthorizationException.class, () -> authenticator.fetchAccessToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware("marvin")
|
||||
void shouldFailGetAuthenticationInfoWithoutPermission() {
|
||||
assertThrows(AuthorizationException.class, () -> authenticator.getAuthenticationInfo());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SubjectAware("marvin")
|
||||
void shouldFailLogoutWithoutPermission() {
|
||||
assertThrows(AuthorizationException.class, () -> authenticator.logout());
|
||||
}
|
||||
|
||||
@Nested
|
||||
@SubjectAware(value = "trillian", permissions = {"plugin:read", "plugin:write"})
|
||||
class WithPermissions {
|
||||
|
||||
@Test
|
||||
void shouldReturnFalseWithoutRefreshToken() {
|
||||
assertThat(authenticator.isAuthenticated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithoutRefreshToken() {
|
||||
assertThrows(IllegalArgumentException.class, () -> authenticator.authenticate("tricia.mcmillan@hitchhiker.com", null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithEmptyRefreshToken() {
|
||||
assertThrows(IllegalArgumentException.class, () -> authenticator.authenticate("tricia.mcmillan@hitchhiker.com", ""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithoutSubject() {
|
||||
assertThrows(IllegalArgumentException.class, () -> authenticator.authenticate(null, "rf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithEmptySubject() {
|
||||
assertThrows(IllegalArgumentException.class, () -> authenticator.authenticate("", "rf"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailWithoutPluginAuthUrl() {
|
||||
scmConfiguration.setPluginAuthUrl(null);
|
||||
assertThrows(IllegalStateException.class, () -> authenticator.authenticate("tricia.mcmillan@hitchhiker.com", "my-awesome-refresh-token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAuthenticate() throws IOException {
|
||||
mockAuthProtocol("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh", "access", "refresh");
|
||||
|
||||
authenticator.authenticate("tricia.mcmillan@hitchhiker.com", "my-awesome-refresh-token");
|
||||
assertThat(authenticator.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFireLoginEvent() throws IOException {
|
||||
mockAuthProtocol("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh", "access", "refresh");
|
||||
|
||||
authenticator.authenticate("tricia.mcmillan@hitchhiker.com", "my-awesome-refresh-token");
|
||||
|
||||
ArgumentCaptor<PluginCenterLoginEvent> captor = ArgumentCaptor.forClass(PluginCenterLoginEvent.class);
|
||||
verify(eventBus).post(captor.capture());
|
||||
|
||||
AuthenticationInfo info = captor.getValue().getAuthenticationInfo();
|
||||
assertThat(info.getPluginCenterSubject()).isEqualTo("tricia.mcmillan@hitchhiker.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailFetchWithoutPriorAuthentication() {
|
||||
assertThrows(IllegalStateException.class, () -> authenticator.fetchAccessToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseUrlFromScmConfiguration() throws IOException {
|
||||
preAuth("cool-refresh-token");
|
||||
scmConfiguration.setPluginAuthUrl("https://pca.org/oidc/");
|
||||
mockAuthProtocol("https://pca.org/oidc/refresh", "access", "refresh");
|
||||
|
||||
String accessToken = authenticator.fetchAccessToken();
|
||||
assertThat(accessToken).isEqualTo("access");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfFetchFails() throws IOException {
|
||||
preAuth("cool-refresh-token");
|
||||
scmConfiguration.setPluginAuthUrl("https://plug.ins/oidc/");
|
||||
|
||||
when(advancedHttpClient.post("https://plug.ins/oidc/refresh")).thenReturn(request);
|
||||
when(request.request()).thenThrow(new IOException("network down down down"));
|
||||
|
||||
assertThrows(FetchAccessTokenFailedException.class, () -> authenticator.fetchAccessToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFailIfFetchResponseIsNotSuccessful() throws IOException {
|
||||
preAuth("cool-refresh-token");
|
||||
scmConfiguration.setPluginAuthUrl("https://plug.ins/oidc/");
|
||||
|
||||
when(advancedHttpClient.post("https://plug.ins/oidc/refresh")).thenReturn(request);
|
||||
|
||||
AdvancedHttpResponse response = mock(AdvancedHttpResponse.class);
|
||||
when(request.request()).thenReturn(response);
|
||||
|
||||
when(response.isSuccessful()).thenReturn(false);
|
||||
|
||||
assertThrows(FetchAccessTokenFailedException.class, () -> authenticator.fetchAccessToken());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFetchAccessToken() throws IOException {
|
||||
preAuth("cool-refresh-token");
|
||||
mockAuthProtocol("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh", "access", "refresh");
|
||||
|
||||
String accessToken = authenticator.fetchAccessToken();
|
||||
assertThat(accessToken).isEqualTo("access");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldStoreRefreshTokenAfterFetch() throws IOException {
|
||||
preAuth("refreshOne");
|
||||
mockAuthProtocol("https://plugin-center-api.scm-manager.org/api/v1/auth/oidc/refresh", "accessTwo", "refreshTwo");
|
||||
|
||||
authenticator.fetchAccessToken();
|
||||
authenticator.fetchAccessToken();
|
||||
|
||||
ArgumentCaptor<RefreshRequest> captor = ArgumentCaptor.forClass(RefreshRequest.class);
|
||||
verify(request, times(2)).jsonContent(captor.capture());
|
||||
|
||||
List<String> refreshTokens = captor.getAllValues()
|
||||
.stream()
|
||||
.map(RefreshRequest::getRefreshToken)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
assertThat(refreshTokens).containsExactlyInAnyOrder("refreshOne", "refreshTwo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyWithoutPriorAuthentication() {
|
||||
assertThat(authenticator.getAuthenticationInfo()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnAuthenticationInfo() {
|
||||
preAuth("refresh_token");
|
||||
assertThat(authenticator.getAuthenticationInfo()).hasValueSatisfying(info -> {
|
||||
assertThat(info.getPluginCenterSubject()).isEqualTo("tricia.mcmillan@hitchhiker.com");
|
||||
assertThat(info.getPrincipal()).isEqualTo("trillian");
|
||||
assertThat(info.getDate()).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLogout() {
|
||||
preAuth("refresh_token");
|
||||
|
||||
authenticator.logout();
|
||||
|
||||
assertThat(authenticator.isAuthenticated()).isFalse();
|
||||
assertThat(authenticator.getAuthenticationInfo()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFireLogoutEventAfterLogout() {
|
||||
preAuth("refresh_token");
|
||||
|
||||
authenticator.logout();
|
||||
|
||||
ArgumentCaptor<PluginCenterLogoutEvent> captor = ArgumentCaptor.forClass(PluginCenterLogoutEvent.class);
|
||||
verify(eventBus).post(captor.capture());
|
||||
|
||||
AuthenticationInfo info = captor.getValue().getPriorAuthenticationInfo();
|
||||
assertThat(info.getPluginCenterSubject()).isEqualTo("tricia.mcmillan@hitchhiker.com");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void preAuth(String refreshToken) {
|
||||
Authentication authentication = new Authentication();
|
||||
authentication.setPluginCenterSubject("tricia.mcmillan@hitchhiker.com");
|
||||
authentication.setPrincipal("trillian");
|
||||
authentication.setRefreshToken(refreshToken);
|
||||
authentication.setDate(Instant.now());
|
||||
factory.get(STORE_NAME, null).set(authentication);
|
||||
}
|
||||
|
||||
@CanIgnoreReturnValue
|
||||
private void mockAuthProtocol(String url, String accessToken, String refreshToken) throws IOException {
|
||||
when(advancedHttpClient.post(url)).thenReturn(request);
|
||||
|
||||
AdvancedHttpResponse response = mock(AdvancedHttpResponse.class);
|
||||
when(request.request()).thenReturn(response);
|
||||
|
||||
RefreshResponse refreshResponse = new RefreshResponse();
|
||||
refreshResponse.setAccessToken(accessToken);
|
||||
refreshResponse.setRefreshToken(refreshToken);
|
||||
when(response.contentFromJson(RefreshResponse.class)).thenReturn(refreshResponse);
|
||||
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
import sonia.scm.net.ahc.AdvancedHttpRequest;
|
||||
import sonia.scm.net.ahc.AdvancedHttpResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -40,8 +41,7 @@ import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static sonia.scm.plugin.Tracing.SPAN_KIND;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -49,7 +49,7 @@ class PluginCenterLoaderTest {
|
||||
|
||||
private static final String PLUGIN_URL = "https://plugins.hitchhiker.com";
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
@Mock
|
||||
private AdvancedHttpClient client;
|
||||
|
||||
@Mock
|
||||
@@ -58,9 +58,15 @@ class PluginCenterLoaderTest {
|
||||
@Mock
|
||||
private ScmEventBus eventBus;
|
||||
|
||||
@Mock
|
||||
private PluginCenterAuthenticator authenticator;
|
||||
|
||||
@InjectMocks
|
||||
private PluginCenterLoader loader;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_SELF)
|
||||
private AdvancedHttpRequest request;
|
||||
|
||||
@Test
|
||||
void shouldFetch() throws IOException {
|
||||
Set<AvailablePlugin> plugins = Collections.emptySet();
|
||||
@@ -73,12 +79,16 @@ class PluginCenterLoaderTest {
|
||||
}
|
||||
|
||||
private AdvancedHttpResponse request() throws IOException {
|
||||
return client.get(PLUGIN_URL).spanKind(SPAN_KIND).request();
|
||||
when(client.get(PLUGIN_URL)).thenReturn(request);
|
||||
AdvancedHttpResponse response = mock(AdvancedHttpResponse.class);
|
||||
when(request.request()).thenReturn(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptySetIfPluginCenterNotBeReached() throws IOException {
|
||||
when(request()).thenThrow(new IOException("failed to fetch"));
|
||||
when(client.get(PLUGIN_URL)).thenReturn(request);
|
||||
when(request.request()).thenThrow(new IOException("failed to fetch"));
|
||||
|
||||
Set<AvailablePlugin> fetch = loader.load(PLUGIN_URL);
|
||||
assertThat(fetch).isEmpty();
|
||||
@@ -86,10 +96,31 @@ class PluginCenterLoaderTest {
|
||||
|
||||
@Test
|
||||
void shouldFirePluginCenterErrorEvent() throws IOException {
|
||||
when(request()).thenThrow(new IOException("failed to fetch"));
|
||||
when(client.get(PLUGIN_URL)).thenReturn(request);
|
||||
when(request.request()).thenThrow(new IOException("failed to fetch"));
|
||||
|
||||
loader.load(PLUGIN_URL);
|
||||
|
||||
verify(eventBus).post(any(PluginCenterErrorEvent.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendAccessToken() throws IOException {
|
||||
when(authenticator.isAuthenticated()).thenReturn(true);
|
||||
when(authenticator.fetchAccessToken()).thenReturn("mega-cool-at");
|
||||
|
||||
mockResponse();
|
||||
loader.load(PLUGIN_URL);
|
||||
|
||||
verify(request).bearerAuth("mega-cool-at");
|
||||
}
|
||||
|
||||
private Set<AvailablePlugin> mockResponse() throws IOException {
|
||||
PluginCenterDto dto = new PluginCenterDto();
|
||||
Set<AvailablePlugin> plugins = Collections.emptySet();
|
||||
when(request().contentFromJson(PluginCenterDto.class)).thenReturn(dto);
|
||||
when(mapper.map(dto)).thenReturn(plugins);
|
||||
return plugins;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -86,6 +87,7 @@ class PluginCenterTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void shouldCache() {
|
||||
Set<AvailablePlugin> first = new HashSet<>();
|
||||
when(loader.load(anyString())).thenReturn(first, new HashSet<>());
|
||||
@@ -94,4 +96,25 @@ class PluginCenterTest {
|
||||
assertThat(pluginCenter.getAvailable()).isSameAs(first);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void shouldClearCache() {
|
||||
Set<AvailablePlugin> first = new HashSet<>();
|
||||
when(loader.load(anyString())).thenReturn(first, new HashSet<>());
|
||||
|
||||
assertThat(pluginCenter.getAvailable()).isSameAs(first);
|
||||
pluginCenter.handle(new PluginCenterLoginEvent(null));
|
||||
assertThat(pluginCenter.getAvailable()).isNotSameAs(first);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldLoadOnRefresh() {
|
||||
Set<AvailablePlugin> plugins = new HashSet<>();
|
||||
when(loader.load(PLUGIN_URL_BASE + "2.0.0")).thenReturn(plugins);
|
||||
|
||||
pluginCenter.refresh();
|
||||
|
||||
verify(loader).load(PLUGIN_URL_BASE + "2.0.0");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
import sonia.scm.net.ahc.AdvancedHttpRequest;
|
||||
import sonia.scm.net.ahc.AdvancedHttpResponse;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -46,28 +47,30 @@ import java.util.Collections;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static sonia.scm.plugin.Tracing.SPAN_KIND;
|
||||
|
||||
@ExtendWith({MockitoExtension.class})
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PluginInstallerTest {
|
||||
|
||||
@Mock
|
||||
private SCMContextProvider context;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||
@Mock
|
||||
private AdvancedHttpClient client;
|
||||
|
||||
@Mock
|
||||
private SmpDescriptorExtractor extractor;
|
||||
|
||||
@Mock
|
||||
private PluginCenterAuthenticator authenticator;
|
||||
|
||||
@InjectMocks
|
||||
private PluginInstaller installer;
|
||||
|
||||
@Mock(answer = Answers.RETURNS_SELF)
|
||||
private AdvancedHttpRequest request;
|
||||
|
||||
private Path directory;
|
||||
|
||||
@BeforeEach
|
||||
@@ -108,7 +111,10 @@ class PluginInstallerTest {
|
||||
}
|
||||
|
||||
private AdvancedHttpResponse request(String url) throws IOException {
|
||||
return client.get(url).spanKind(SPAN_KIND).request();
|
||||
AdvancedHttpResponse response = mock(AdvancedHttpResponse.class);
|
||||
when(client.get(url)).thenReturn(request);
|
||||
when(request.request()).thenReturn(response);
|
||||
return response;
|
||||
}
|
||||
|
||||
private AvailablePlugin createGitPlugin() {
|
||||
@@ -121,7 +127,8 @@ class PluginInstallerTest {
|
||||
|
||||
@Test
|
||||
void shouldThrowPluginDownloadException() throws IOException {
|
||||
when(request("https://download.hitchhiker.com")).thenThrow(new IOException("failed to download"));
|
||||
when(client.get("https://download.hitchhiker.com")).thenReturn(request);
|
||||
when(request.request()).thenThrow(new IOException("failed to download"));
|
||||
|
||||
PluginInstallationContext context = PluginInstallationContext.empty();
|
||||
AvailablePlugin gitPlugin = createGitPlugin();
|
||||
@@ -190,6 +197,17 @@ class PluginInstallerTest {
|
||||
assertThat(exception.getDownloaded().getVersion()).isEqualTo("1.1.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAppendBearerAuth() throws IOException {
|
||||
when(authenticator.isAuthenticated()).thenReturn(true);
|
||||
when(authenticator.fetchAccessToken()).thenReturn("atat");
|
||||
mockContent("42");
|
||||
|
||||
installer.install(PluginInstallationContext.empty(), createGitPlugin());
|
||||
|
||||
verify(request).bearerAuth("atat");
|
||||
}
|
||||
|
||||
private AvailablePlugin createPlugin(String name, String url, String checksum) {
|
||||
PluginInformation information = new PluginInformation();
|
||||
information.setName(name);
|
||||
|
||||
@@ -28,9 +28,7 @@ import org.mockito.Answers;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class PluginTestHelper {
|
||||
public static AvailablePlugin createAvailable(String name) {
|
||||
@@ -62,9 +60,14 @@ public class PluginTestHelper {
|
||||
}
|
||||
|
||||
public static AvailablePlugin createAvailable(PluginInformation information) {
|
||||
return createAvailable(information, "https://scm-manager.org/download");
|
||||
}
|
||||
|
||||
public static AvailablePlugin createAvailable(PluginInformation information, String url) {
|
||||
AvailablePluginDescriptor descriptor = mock(AvailablePluginDescriptor.class);
|
||||
lenient().when(descriptor.getInformation()).thenReturn(information);
|
||||
lenient().when(descriptor.getInstallLink()).thenReturn(Optional.of("mycloudogu.com/install/my_plugin"));
|
||||
lenient().when(descriptor.getUrl()).thenReturn(url);
|
||||
return new AvailablePlugin(descriptor);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class SecureParameterSerializerTest {
|
||||
|
||||
private final SecureParameterSerializer serializer = new SecureParameterSerializer(new ObjectMapper());
|
||||
|
||||
@Test
|
||||
void shouldSerializeAndDeserialize() throws IOException {
|
||||
TestObject object = new TestObject("1", 2);
|
||||
String serialized = serializer.serialize(object);
|
||||
|
||||
assertThat(serialized).isNotEmpty();
|
||||
|
||||
object = serializer.deserialize(serialized, TestObject.class);
|
||||
assertThat(object).isNotNull();
|
||||
assertThat(object.getOne()).isEqualTo("1");
|
||||
assertThat(object.getTwo()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public static class TestObject {
|
||||
|
||||
private String one;
|
||||
private int two;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -30,17 +30,14 @@ import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.ws.rs.GET;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -58,6 +55,8 @@ class XsrfAccessTokenValidatorTest {
|
||||
@Mock
|
||||
private AccessToken accessToken;
|
||||
|
||||
private final XsrfExcludes excludes = new XsrfExcludes();
|
||||
|
||||
private XsrfAccessTokenValidator validator;
|
||||
|
||||
/**
|
||||
@@ -65,7 +64,7 @@ class XsrfAccessTokenValidatorTest {
|
||||
*/
|
||||
@BeforeEach
|
||||
void prepareObjectUnderTest() {
|
||||
validator = new XsrfAccessTokenValidator(() -> request);
|
||||
validator = new XsrfAccessTokenValidator(() -> request, excludes);
|
||||
}
|
||||
|
||||
@Nested
|
||||
@@ -86,7 +85,7 @@ class XsrfAccessTokenValidatorTest {
|
||||
when(request.getHeader(Xsrf.HEADER_KEY)).thenReturn("abc");
|
||||
|
||||
// execute and assert
|
||||
assertTrue(validator.validate(accessToken));
|
||||
assertThat(validator.validate(accessToken)).isTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +98,7 @@ class XsrfAccessTokenValidatorTest {
|
||||
when(request.getHeader(Xsrf.HEADER_KEY)).thenReturn("123");
|
||||
|
||||
// execute and assert
|
||||
assertFalse(validator.validate(accessToken));
|
||||
assertThat(validator.validate(accessToken)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +110,7 @@ class XsrfAccessTokenValidatorTest {
|
||||
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.of("abc"));
|
||||
|
||||
// execute and assert
|
||||
assertFalse(validator.validate(accessToken));
|
||||
assertThat(validator.validate(accessToken)).isFalse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,30 +122,43 @@ class XsrfAccessTokenValidatorTest {
|
||||
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.empty());
|
||||
|
||||
// execute and assert
|
||||
assertTrue(validator.validate(accessToken));
|
||||
assertThat(validator.validate(accessToken)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotValidateExcludedRequest() {
|
||||
excludes.add("/excluded");
|
||||
|
||||
// prepare
|
||||
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.of("abc"));
|
||||
when(request.getRequestURI()).thenReturn("/excluded");
|
||||
|
||||
// execute and assert
|
||||
assertThat(validator.validate(accessToken)).isTrue();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"GET", "HEAD", "OPTIONS"})
|
||||
@ValueSource(strings = {"GET", "HEAD", "OPTIONS"})
|
||||
void shouldNotValidateReadRequests(String method) {
|
||||
// prepare
|
||||
when(request.getMethod()).thenReturn(method);
|
||||
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.of("abc"));
|
||||
|
||||
// execute and assert
|
||||
assertTrue(validator.validate(accessToken));
|
||||
assertThat(validator.validate(accessToken)).isTrue();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"POST", "PUT", "DELETE", "PATCH"})
|
||||
@ValueSource(strings = {"GET", "HEAD", "OPTIONS"})
|
||||
void shouldFailValidationOfWriteRequests(String method) {
|
||||
// prepare
|
||||
when(request.getMethod()).thenReturn(method);
|
||||
when(accessToken.getCustom(Xsrf.TOKEN_KEY)).thenReturn(Optional.of("abc"));
|
||||
|
||||
// execute and assert
|
||||
assertFalse(validator.validate(accessToken));
|
||||
assertThat(validator.validate(accessToken)).isTrue();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user