Skip to content

Commit

Permalink
Merge branch '6.2.x'
Browse files Browse the repository at this point in the history
Closes gh-14408
  • Loading branch information
marcusdacoregio committed Jan 5, 2024
2 parents 9135cb4 + 4fb6a33 commit 85177c0
Show file tree
Hide file tree
Showing 53 changed files with 331 additions and 6 deletions.
7 changes: 7 additions & 0 deletions config/spring-security-config.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ dependencies {
testImplementation ('org.apache.maven.resolver:maven-resolver-transport-http') {
exclude group: "org.slf4j", module: "jcl-over-slf4j"
}
testImplementation libs.org.instancio.instancio.junit

testRuntimeOnly 'org.hsqldb:hsqldb'
}
Expand Down Expand Up @@ -153,3 +154,9 @@ tasks.withType(KotlinCompile).configureEach {
jvmTarget = "17"
}
}

configure(project.tasks.withType(Test)) {
doFirst {
systemProperties['springSecurityVersion'] = version
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
/*
* Copyright 2002-2024 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.security;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

import org.apereo.cas.client.validation.AssertionImpl;
import org.instancio.Instancio;
import org.instancio.InstancioApi;
import org.instancio.Select;
import org.instancio.generator.Generator;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.security.access.intercept.RunAsUserToken;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.jaas.JaasAuthenticationToken;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.cas.authentication.CasServiceTicketAuthenticationToken;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthenticationTokens;
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthorizationCodeAuthenticationTokens;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
import org.springframework.security.oauth2.core.TestOAuth2AuthenticatedPrincipals;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.security.oauth2.core.user.TestOAuth2Users;
import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

/**
* Tests that Spring Security classes that implements {@link Serializable} and have the
* same serial version as {@link SpringSecurityCoreVersion#SERIAL_VERSION_UID} can be
* deserialized from a previous minor version.
* <p>
* For example, all classes from version 6.2.x that matches the previous requirement
* should be serialized and saved to a folder, and then later on, in 6.3.x, it is verified
* if they can be deserialized
*
* @author Marcus da Coregio
* @since 6.2.2
* @see <a href="https://github.com/spring-projects/spring-security/issues/3737">GitHub
* Issue #3737</a>
*/
class SpringSecurityCoreVersionSerializableTests {

private static final Map<Class<?>, Generator<?>> generatorByClassName = new HashMap<>();

static final long securitySerialVersionUid = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

static Path currentVersionFolder = Paths.get("src/test/resources/serialized/" + getCurrentVersion());

static Path previousVersionFolder = Paths.get("src/test/resources/serialized/" + getPreviousVersion());

static {
ClientRegistration.Builder clientRegistrationBuilder = TestClientRegistrations.clientRegistration();
ClientRegistration clientRegistration = clientRegistrationBuilder.build();
UserDetails user = TestAuthentication.user();
WebAuthenticationDetails details = new WebAuthenticationDetails("remote", "sessionId");
generatorByClassName.put(DefaultOAuth2User.class, (r) -> TestOAuth2Users.create());
generatorByClassName.put(ClientRegistration.class, (r) -> clientRegistration);
generatorByClassName.put(ClientRegistration.ProviderDetails.class,
(r) -> clientRegistration.getProviderDetails());
generatorByClassName.put(ClientRegistration.ProviderDetails.UserInfoEndpoint.class,
(r) -> clientRegistration.getProviderDetails().getUserInfoEndpoint());
generatorByClassName.put(ClientRegistration.Builder.class, (r) -> clientRegistrationBuilder);
generatorByClassName.put(OAuth2AuthorizationRequest.class,
(r) -> TestOAuth2AuthorizationRequests.request().build());
generatorByClassName.put(OAuth2AuthorizationResponse.class,
(r) -> TestOAuth2AuthorizationResponses.success().build());
generatorByClassName.put(OAuth2AuthorizedClient.class,
(r) -> new OAuth2AuthorizedClient(clientRegistration, "principal", TestOAuth2AccessTokens.noScopes()));
generatorByClassName.put(OAuth2UserAuthority.class, (r) -> new OAuth2UserAuthority(Map.of("username", "user")));
generatorByClassName.put(OAuth2AuthorizationExchange.class, (r) -> TestOAuth2AuthorizationExchanges.success());
generatorByClassName.put(OidcUserInfo.class, (r) -> OidcUserInfo.builder().email("[email protected]").build());
generatorByClassName.put(SessionInformation.class,
(r) -> new SessionInformation(user, r.alphanumeric(4), new Date(1704378933936L)));
generatorByClassName.put(OAuth2LoginAuthenticationToken.class, (r) -> {
var token = new OAuth2LoginAuthenticationToken(clientRegistration,
TestOAuth2AuthorizationExchanges.success());
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2AuthorizationCodeAuthenticationToken.class, (r) -> {
var token = TestOAuth2AuthorizationCodeAuthenticationTokens.authenticated();
token.setDetails(details);
return token;
});
generatorByClassName
.put(org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken.class, (r) -> {
var token = new org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken(
"token");
token.setDetails(details);
return token;
});
generatorByClassName.put(BearerTokenAuthenticationToken.class, (r) -> {
var token = new BearerTokenAuthenticationToken("token");
token.setDetails(details);
return token;
});
generatorByClassName.put(BearerTokenAuthentication.class, (r) -> {
var token = new BearerTokenAuthentication(TestOAuth2AuthenticatedPrincipals.active(),
TestOAuth2AccessTokens.noScopes(), user.getAuthorities());
token.setDetails(details);
return token;
});
generatorByClassName.put(OAuth2AuthenticationToken.class, (r) -> {
var token = TestOAuth2AuthenticationTokens.authenticated();
token.setDetails(details);
return token;
});
generatorByClassName.put(JwtAuthenticationToken.class, (r) -> {
var token = new JwtAuthenticationToken(TestJwts.user());
token.setDetails(details);
return token;
});
generatorByClassName.put(RunAsUserToken.class, (r) -> {
RunAsUserToken token = new RunAsUserToken("key", user, "creds", user.getAuthorities(),
AnonymousAuthenticationToken.class);
token.setDetails(details);
return token;
});
generatorByClassName.put(CasServiceTicketAuthenticationToken.class, (r) -> {
CasServiceTicketAuthenticationToken token = CasServiceTicketAuthenticationToken.stateless("creds");
token.setDetails(details);
return token;
});
generatorByClassName.put(CasAuthenticationToken.class, (r) -> {
var token = new CasAuthenticationToken("key", user, "Password", user.getAuthorities(), user,
new AssertionImpl("test"));
token.setDetails(details);
return token;
});
generatorByClassName.put(CasAssertionAuthenticationToken.class, (r) -> {
var token = new CasAssertionAuthenticationToken(new AssertionImpl("test"), "ticket");
token.setDetails(details);
return token;
});
generatorByClassName.put(RememberMeAuthenticationToken.class, (r) -> {
RememberMeAuthenticationToken token = new RememberMeAuthenticationToken("key", user, user.getAuthorities());
token.setDetails(details);
return token;
});
generatorByClassName.put(PreAuthenticatedAuthenticationToken.class, (r) -> {
PreAuthenticatedAuthenticationToken token = new PreAuthenticatedAuthenticationToken(user, "creds",
user.getAuthorities());
token.setDetails(details);
return token;
});
generatorByClassName.put(UsernamePasswordAuthenticationToken.class, (r) -> {
var token = UsernamePasswordAuthenticationToken.unauthenticated(user, "creds");
token.setDetails(details);
return token;
});
generatorByClassName.put(JaasAuthenticationToken.class, (r) -> {
var token = new JaasAuthenticationToken(user, "creds", null);
token.setDetails(details);
return token;
});
}

@ParameterizedTest
@MethodSource("getClassesToSerialize")
@Disabled("This method should only be used to serialize the classes once")
void serializeCurrentVersionClasses(Class<?> clazz) throws Exception {
Files.createDirectories(currentVersionFolder);
Path filePath = Paths.get(currentVersionFolder.toAbsolutePath() + "/" + clazz.getName());
File file = filePath.toFile();
if (file.exists()) {
return;
}
Files.createFile(filePath);
Object instance = instancioWithDefaults(clazz).create();
assertThat(instance).isInstanceOf(clazz);
try (FileOutputStream fileOutputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)) {
objectOutputStream.writeObject(instance);
objectOutputStream.flush();
}
catch (NotSerializableException ex) {
Files.delete(filePath);
fail("Could not serialize " + clazz.getName(), ex);
}
}

@ParameterizedTest
@MethodSource("getFilesToDeserialize")
void shouldBeAbleToDeserializeClassFromPreviousVersion(Path filePath) {
try (FileInputStream fileInputStream = new FileInputStream(filePath.toFile());
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)) {
Object obj = objectInputStream.readObject();
Class<?> clazz = Class.forName(filePath.getFileName().toString());
assertThat(obj).isInstanceOf(clazz);
}
catch (IOException | ClassNotFoundException ex) {
fail("Could not deserialize " + filePath, ex);
}
}

static Stream<Path> getFilesToDeserialize() throws IOException {
assertThat(previousVersionFolder.toFile().exists())
.as("Make sure that the " + previousVersionFolder + " exists and is not empty")
.isTrue();
try (Stream<Path> files = Files.list(previousVersionFolder)) {
if (files.findFirst().isEmpty()) {
fail("Please make sure to run SpringSecurityCoreVersionSerializableTests#serializeCurrentVersionClasses for the "
+ getPreviousVersion() + " version");
}
}
return Files.list(previousVersionFolder);
}

static Stream<Class<?>> getClassesToSerialize() throws Exception {
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
provider.addIncludeFilter(new AssignableTypeFilter(Serializable.class));
List<Class<?>> classes = new ArrayList<>();

Set<BeanDefinition> components = provider.findCandidateComponents("org/springframework/security");
for (BeanDefinition component : components) {
Class<?> clazz = Class.forName(component.getBeanClassName());
boolean isAbstract = Modifier.isAbstract(clazz.getModifiers());
boolean matchesExpectedSerialVersion = ObjectStreamClass.lookup(clazz)
.getSerialVersionUID() == securitySerialVersionUid;
if (!isAbstract && matchesExpectedSerialVersion) {
classes.add(clazz);
}
}
return classes.stream();
}

private static InstancioApi<?> instancioWithDefaults(Class<?> clazz) {
InstancioApi<?> instancio = Instancio.of(clazz);
if (generatorByClassName.containsKey(clazz)) {
instancio.supply(Select.all(clazz), generatorByClassName.get(clazz));
}
return instancio;
}

private static String getCurrentVersion() {
String version = System.getProperty("springSecurityVersion");
String[] parts = version.split("\\.");
parts[2] = "x";
return String.join(".", parts);
}

private static String getPreviousVersion() {
String version = System.getProperty("springSecurityVersion");
String[] parts = version.split("\\.");
parts[1] = String.valueOf(Integer.parseInt(parts[1]) - 1);
parts[2] = "x";
return String.join(".", parts);
}

}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2024 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.
Expand Down Expand Up @@ -39,11 +39,8 @@ public final class SpringSecurityCoreVersion {

/**
* Global Serialization value for Spring Security classes.
*
* N.B. Classes are not intended to be serializable between different versions. See
* SEC-1709 for why we still need a serial version.
*/
public static final long SERIAL_VERSION_UID = 630L;
public static final long SERIAL_VERSION_UID = 620L;

static final String MIN_SPRING_VERSION = getSpringVersion();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2024 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.
Expand All @@ -23,6 +23,7 @@
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Answers;
Expand Down Expand Up @@ -79,6 +80,7 @@ public void springVersionIsUpToDate() {
}

@Test
@Disabled("Since 6.3. See gh-3737")
public void serialVersionMajorAndMinorVersionMatchBuildVersion() {
String version = System.getProperty("springSecurityVersion");
// Strip patch version
Expand Down
1 change: 1 addition & 0 deletions dependencies/spring-security-dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ dependencies {
api libs.org.apache.maven.resolver.maven.resolver.impl
api libs.org.apache.maven.resolver.maven.resolver.transport.http
api libs.org.apache.maven.maven.resolver.provider
api libs.org.instancio.instancio.junit
}
}

1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ com-github-spullara-mustache-java-compiler = "com.github.spullara.mustache.java:
org-hidetake-gradle-ssh-plugin = "org.hidetake:gradle-ssh-plugin:2.10.1"
org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-info-extractor-gradle:4.29.4"
org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1"
org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1"

[plugins]

Expand Down

0 comments on commit 85177c0

Please sign in to comment.