Переглянути джерело

Merge branch '6.0.x' into 6.1.x

Closes gh-14049
Marcus Da Coregio 1 рік тому
батько
коміт
e38fee2b40

+ 1 - 0
build.gradle

@@ -24,6 +24,7 @@ apply plugin: 'org.springframework.security.sagan'
 apply plugin: 'org.springframework.github.milestone'
 apply plugin: 'org.springframework.github.changelog'
 apply plugin: 'org.springframework.github.release'
+apply plugin: 'org.springframework.security.versions.verify-dependencies-versions'
 
 group = 'org.springframework.security'
 description = 'Spring Security'

+ 4 - 0
buildSrc/build.gradle

@@ -64,6 +64,10 @@ gradlePlugin {
 			id = "s101"
 			implementationClass = "s101.S101Plugin"
 		}
+		verifyDependenciesVersions {
+			id = "org.springframework.security.versions.verify-dependencies-versions"
+			implementationClass = "org.springframework.security.convention.versions.VerifyDependenciesVersionsPlugin"
+		}
 	}
 }
 

+ 70 - 0
buildSrc/src/main/java/org/springframework/security/convention/versions/TransitiveDependencyLookupUtils.java

@@ -0,0 +1,70 @@
+/*
+ * Copyright 2002-2023 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.convention.versions;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import java.io.IOException;
+import java.io.InputStream;
+
+class TransitiveDependencyLookupUtils {
+	static String OIDC_SDK_NAME = "oauth2-oidc-sdk";
+	static String NIMBUS_JOSE_JWT_NAME = "nimbus-jose-jwt";
+
+	private static OkHttpClient client = new OkHttpClient();
+
+	static String lookupJwtVersion(String oauthSdcVersion) {
+		Request request = new Request.Builder()
+				.get()
+				.url("https://repo.maven.apache.org/maven2/com/nimbusds/" + OIDC_SDK_NAME + "/" + oauthSdcVersion + "/" + OIDC_SDK_NAME + "-" + oauthSdcVersion + ".pom")
+				.build();
+		try (Response response = client.newCall(request).execute()) {
+			if (!response.isSuccessful()) {
+				throw new IOException("Unexpected code " + response);
+			}
+			InputStream inputStream = response.body().byteStream();
+			return getVersion(inputStream);
+
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static String getVersion(InputStream inputStream) throws ParserConfigurationException, IOException, SAXException, XPathExpressionException {
+		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+		dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+		DocumentBuilder db = dbf.newDocumentBuilder();
+
+		Document doc = db.parse(inputStream);
+
+		doc.getDocumentElement().normalize();
+
+		XPath xPath = XPathFactory.newInstance().newXPath();
+		return xPath.evaluate("/project/dependencies/dependency/version[../artifactId/text() = \"" + NIMBUS_JOSE_JWT_NAME + "\"]", doc);
+	}
+}

+ 118 - 0
buildSrc/src/main/java/org/springframework/security/convention/versions/VerifyDependenciesVersionsPlugin.java

@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2023 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.convention.versions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ModuleVersionIdentifier;
+import org.gradle.api.tasks.TaskProvider;
+
+public class VerifyDependenciesVersionsPlugin implements Plugin<Project> {
+
+	@Override
+	public void apply(Project project) {
+		TaskProvider<Task> provider = project.getTasks().register("verifyDependenciesVersions", (verifyDependenciesVersionsTask) -> {
+			verifyDependenciesVersionsTask.setGroup("Verification");
+			verifyDependenciesVersionsTask.setDescription("Verify that specific dependencies are using the same version");
+			List<Configuration> allConfigurations = new ArrayList<>();
+			allConfigurations.addAll(getConfigurations(project));
+			allConfigurations.addAll(getSubprojectsConfigurations(project.getSubprojects()));
+			verifyDependenciesVersionsTask.getInputs().property("dependenciesVersions", new DependencySupplier(allConfigurations));
+			verifyDependenciesVersionsTask.doLast((task) -> {
+				DependencySupplier dependencies = (DependencySupplier) task.getInputs().getProperties().get("dependenciesVersions");
+				Map<String, List<Artifact>> artifacts = dependencies.get();
+				List<Artifact> oauth2OidcSdk = artifacts.get("oauth2-oidc-sdk");
+				List<Artifact> nimbusJoseJwt = artifacts.get("nimbus-jose-jwt");
+				if (oauth2OidcSdk.size() > 1) {
+					throw new IllegalStateException("Found multiple versions of oauth2-oidc-sdk: " + oauth2OidcSdk);
+				}
+				Artifact oauth2OidcSdkArtifact = oauth2OidcSdk.get(0);
+				String nimbusJoseJwtVersion = TransitiveDependencyLookupUtils.lookupJwtVersion(oauth2OidcSdkArtifact.version());
+				List<Artifact> differentVersions = nimbusJoseJwt.stream()
+						.filter((artifact) -> !artifact.version().equals(nimbusJoseJwtVersion))
+						.filter((artifact -> !artifact.configurationName().contains("spring-security-cas"))) // CAS uses a different version
+						.toList();
+				if (!differentVersions.isEmpty()) {
+					String message = "Found transitive nimbus-jose-jwt version [" + nimbusJoseJwtVersion + "] in oauth2-oidc-sdk " + oauth2OidcSdkArtifact
+							+ ", but the project contains a different version of nimbus-jose-jwt " + differentVersions
+							+ ". Please align the versions of nimbus-jose-jwt.";
+					throw new IllegalStateException(message);
+				}
+			});
+		});
+		project.getTasks().getByName("build").dependsOn(provider);
+	}
+
+	private List<Configuration> getConfigurations(Project project) {
+		return project.getConfigurations().stream()
+				.filter(Configuration::isCanBeResolved)
+				.filter((config) -> config.getName().equals("runtimeClasspath"))
+				.toList();
+	}
+
+	private List<Configuration> getSubprojectsConfigurations(Set<Project> subprojects) {
+		if (subprojects.isEmpty()) {
+			return Collections.emptyList();
+		}
+		List<Configuration> subprojectConfigurations = new ArrayList<>();
+		for (Project subproject : subprojects) {
+			subprojectConfigurations.addAll(getConfigurations(subproject));
+			subprojectConfigurations.addAll(getSubprojectsConfigurations(subproject.getSubprojects()));
+		}
+		return subprojectConfigurations;
+	}
+
+	private record Artifact(String name, String version, String configurationName) {
+	}
+
+	private static final class DependencySupplier implements Supplier<Map<String, List<Artifact>>> {
+
+		private final List<Configuration> configurations;
+
+		private DependencySupplier(List<Configuration> configurations) {
+			this.configurations = configurations;
+		}
+
+		@Override
+		public Map<String, List<Artifact>> get() {
+			return getDependencies(this.configurations);
+		}
+
+		private Map<String, List<Artifact>> getDependencies(List<Configuration> configurations) {
+			return configurations.stream().flatMap((configuration) -> {
+						return configuration.getResolvedConfiguration().getResolvedArtifacts().stream()
+								.map((dep) -> {
+									ModuleVersionIdentifier id = dep.getModuleVersion().getId();
+									return new Artifact(id.getName(), id.getVersion(), configuration.toString());
+								});
+					})
+					.distinct()
+					.collect(Collectors.groupingBy(Artifact::name));
+		}
+	}
+
+}