WebSecurityConfigurerAdapter quick fix for Security 6.0.x code

This commit is contained in:
aboyko
2023-03-02 20:53:03 -05:00
parent 64fabdfdd0
commit a19217ab7d
6 changed files with 134 additions and 64 deletions

View File

@@ -12,7 +12,6 @@ package org.springframework.ide.vscode.commons.languageserver;
import org.eclipse.lsp4j.WorkDoneProgressBegin;
import org.eclipse.lsp4j.WorkDoneProgressReport;
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
/**
* Eclipse progress requires sending total work units number with the begin progress message. The number is any intger.
@@ -38,7 +37,7 @@ public class PercentageProgressTask extends AbstractProgressTask {
private void begin(String title) {
WorkDoneProgressBegin progressBegin = new WorkDoneProgressBegin();
progressBegin.setPercentage(LspClient.currentClient() == LspClient.Client.ECLIPSE ? 100 : 0);
progressBegin.setPercentage(0);
progressBegin.setCancellable(false);
progressBegin.setTitle(title);
service.progressBegin(taskId, progressBegin);

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022 VMware, Inc.
* Copyright (c) 2022, 2023 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -30,10 +30,13 @@ final public class FixDescriptor {
private String label;
private String[] typeStubs;
public FixDescriptor(String recipeId, List<String> docUris, String label) {
this.recipeId = recipeId;
this.docUris = docUris;
this.label = label;
this.typeStubs = new String[0];
}
public FixDescriptor withRecipeScope(RecipeScope recipeScope) {
@@ -50,7 +53,12 @@ final public class FixDescriptor {
this.parameters = parameters;
return this;
}
public FixDescriptor withTypeStubs(String... typeStubs) {
this.typeStubs = typeStubs;
return this;
}
public String getRecipeId() {
return recipeId;
}
@@ -74,5 +82,9 @@ final public class FixDescriptor {
public String getLabel() {
return label;
}
public String[] getTypeStubs() {
return typeStubs;
}
}

View File

@@ -36,6 +36,7 @@ import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.RecipeIntrospectionUtils;
import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaParser.Builder;
import org.openrewrite.java.JavaParsingException;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.UpdateSourcePositions;
@@ -234,10 +235,7 @@ public class ORAstUtils {
public static JavaParser createJavaParser(IJavaProject project) {
try {
List<Path> classpath = IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath()).collect(Collectors.toList());
JavaParser jp = JavaParser.fromJavaVersion().build();
jp.setClasspath(classpath);
return jp;
return createJavaParserBuilder(project).build();
} catch (Exception e) {
if (isExceptionFromInterrupedThread(e)) {
log.debug("", e);
@@ -247,34 +245,44 @@ public class ORAstUtils {
return null;
}
}
public static List<CompilationUnit> parse(SimpleTextDocumentService documents, IJavaProject project, Consumer<SourceFile> parseCallback) {
List<Parser.Input> inputs = IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath()).flatMap(folder -> {
try {
return Files.walk(folder.toPath());
} catch (IOException e) {
log.error("", e);
}
return Stream.empty();
}).filter(Files::isRegularFile).filter(p -> p.getFileName().toString().endsWith(".java")).map(p -> {
TextDocument doc = documents.getLatestSnapshot(p.toUri().toASCIIString());
if (doc == null) {
return new Parser.Input(p, () -> {
public static Builder<? extends JavaParser, ?> createJavaParserBuilder(IJavaProject project) {
List<Path> classpath = IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath()).collect(Collectors.toList());
return JavaParser.fromJavaVersion().classpath(classpath);
}
public static List<Parser.Input> getParserInputs(SimpleTextDocumentService documents, IJavaProject project) {
return IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath())
.flatMap(folder -> {
try {
return Files.newInputStream(p);
return Files.walk(folder.toPath());
} catch (IOException e) {
log.error("", e);
return new ByteArrayInputStream(new byte[0]);
}
});
} else {
return new Parser.Input(p, () -> new ByteArrayInputStream(doc.get().getBytes()));
}
}).collect(Collectors.toList());
JavaParser javaParser = createJavaParser(project);
return ORAstUtils.parseInputs(javaParser, inputs, parseCallback);
return Stream.empty();
})
.filter(Files::isRegularFile)
.filter(p -> p.getFileName().toString().endsWith(".java"))
.map(p -> getParserInput(documents, p))
.collect(Collectors.toList());
}
public static Parser.Input getParserInput(SimpleTextDocumentService documents, Path p) {
TextDocument doc = documents.getLatestSnapshot(p.toUri().toASCIIString());
if (doc == null) {
return new Parser.Input(p, () -> {
try {
return Files.newInputStream(p);
} catch (IOException e) {
log.error("", e);
return new ByteArrayInputStream(new byte[0]);
}
});
} else {
return new Parser.Input(p, () -> new ByteArrayInputStream(doc.get().getBytes()));
}
}
public static List<CompilationUnit> parse(JavaParser parser, Iterable<Path> sourceFiles) {
InMemoryExecutionContext ctx = new InMemoryExecutionContext(ORAstUtils::logExceptionWhileParsing);
ctx.putMessage(JavaParser.SKIP_SOURCE_SET_TYPE_GENERATION, true);

View File

@@ -38,7 +38,7 @@ public class RewriteConfig {
@ConditionalOnBean(RewriteRecipeRepository.class)
@Bean RewriteRefactorings rewriteRefactorings(SimpleLanguageServer server, JavaProjectFinder projectFinder, RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache) {
return new RewriteRefactorings(server.getTextDocumentService(), projectFinder, recipeRepo, cuCache);
return new RewriteRefactorings(server, projectFinder, recipeRepo, cuCache);
}
@ConditionalOnBean(RewriteRecipeRepository.class)

View File

@@ -12,10 +12,13 @@ package org.springframework.ide.vscode.boot.java.rewrite;
import java.lang.reflect.Field;
import java.net.URI;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.CodeAction;
@@ -24,22 +27,25 @@ import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.openrewrite.Parser.Input;
import org.openrewrite.Recipe;
import org.openrewrite.RecipeRun;
import org.openrewrite.Result;
import org.openrewrite.config.DeclarativeRecipe;
import org.openrewrite.internal.RecipeIntrospectionUtils;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.J.CompilationUnit;
import org.openrewrite.marker.Range;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.PercentageProgressTask;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixHandler;
import org.springframework.ide.vscode.commons.languageserver.util.CodeActionResolver;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.rewrite.ORDocUtils;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
@@ -60,7 +66,7 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
private RewriteRecipeRepository recipeRepo;
private SimpleTextDocumentService documents;
private SimpleLanguageServer server;
private RewriteCompilationUnitCache cuCache;
@@ -68,8 +74,8 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
private Gson gson;
public RewriteRefactorings(SimpleTextDocumentService documents, JavaProjectFinder projectFinder, RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache) {
this.documents = documents;
public RewriteRefactorings(SimpleLanguageServer server, JavaProjectFinder projectFinder, RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache) {
this.server = server;
this.projectFinder = projectFinder;
this.recipeRepo = recipeRepo;
this.cuCache = cuCache;
@@ -120,7 +126,7 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
List<Result> results = reciperun.getResults();
List<Either<TextDocumentEdit, ResourceOperation>> edits = results.stream().filter(res -> res.getAfter() != null).map(res -> {
URI docUri = res.getAfter().getSourcePath().isAbsolute() ? res.getAfter().getSourcePath().toUri() : project.getLocationUri().resolve(res.getAfter().getSourcePath().toString());
TextDocument doc = documents.getLatestSnapshot(docUri.toASCIIString());
TextDocument doc = server.getTextDocumentService().getLatestSnapshot(docUri.toASCIIString());
if (doc == null) {
doc = new TextDocument(docUri.toASCIIString(), LanguageId.JAVA, 0, res.getBefore() == null ? "" : res.getBefore().printAll());
}
@@ -138,12 +144,27 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(data.getDocUris().get(0)));
if (project.isPresent()) {
boolean projectWide = data.getRecipeScope() == RecipeScope.PROJECT;
Recipe r = createRecipe(data);
Recipe r = createRecipe(data);
List<CompilationUnit> cus = Collections.emptyList();
if (projectWide) {
//TODO: progress here as well.
return applyRecipe(r, project.get(), ORAstUtils.parse(documents, project.get(), null));
JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build();
List<Input> inputs = ORAstUtils.getParserInputs(server.getTextDocumentService(), project.get());
PercentageProgressTask progress = server.getProgressService().createPercentageProgressTask(UUID.randomUUID().toString(), inputs.size() + 1, data.getLabel());
try {
cus = ORAstUtils.parseInputs(jp, inputs, s -> progress.increment());
return applyRecipe(r, project.get(), cus);
} finally {
progress.setCurrent(progress.getTotal());
progress.done();
}
} else {
List<CompilationUnit> cus = data.getDocUris().stream().map(docUri -> cuCache.getCU(project.get(), URI.create(docUri))).filter(Objects::nonNull).collect(Collectors.toList());
if (data.getTypeStubs().length == 0) {
cus = data.getDocUris().stream().map(docUri -> cuCache.getCU(project.get(), URI.create(docUri))).filter(Objects::nonNull).collect(Collectors.toList());
} else {
JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build();
List<Input> inputs = data.getDocUris().stream().map(URI::create).map(Paths::get).map(p -> ORAstUtils.getParserInput(server.getTextDocumentService(), p)).collect(Collectors.toList());
cus = ORAstUtils.parseInputs(jp, inputs, null);
}
return applyRecipe(r, project.get(), cus);
}
}

View File

@@ -43,7 +43,33 @@ public class WebSecurityConfigurerAdapterCodeAction implements RecipeCodeActionD
private static final String PROBLEM_LABEL = "Class extends 'WebSecurityConfigurerAdapter' which is removed in Spring-Security 6.x";
protected static final String FIX_LABEL = "Refactor class into a Configuration bean not extending 'WebSecurityConfigurerAdapter'";
private static final String FIX_LABEL = "Refactor class into a Configuration bean not extending 'WebSecurityConfigurerAdapter'";
private static final String STUB_WEB_SECURITY_CONFIG_ADAPTER = """
package org.springframework.security.config.annotation.web.configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
public abstract class WebSecurityConfigurerAdapter {
public void init(WebSecurity web) throws Exception {}
public AuthenticationManager authenticationManagerBean() throws Exception { return null; }
public UserDetailsService userDetailsServiceBean() throws Exception { return null; }
protected void configure(HttpSecurity http) throws Exception {}
public void configure(WebSecurity web) throws Exception {}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {}
}
""";
@Override
public String getId() {
@@ -62,48 +88,52 @@ public class WebSecurityConfigurerAdapterCodeAction implements RecipeCodeActionD
@Override
public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext p) {
ClassDeclaration c = super.visitClassDeclaration(classDecl, p);
if (isExtendingWebSecurityConfigurerAdapter(c)) {
TypeTree superClass = c.getExtends();
boolean isExtendingWebSecurityConfigurerAdapter = false;
boolean isUnresolved = false;
if (superClass != null) {
if (superClass.getType() instanceof JavaType.Unknown) {
String strType = superClass.printTrimmed(getCursor());
isExtendingWebSecurityConfigurerAdapter = "WebSecurityConfigurerAdapter".equals(strType) || FQN_WEB_SECURITY_CONFIGURER_ADAPTER.equals(strType);
isUnresolved = true;
} else if (superClass.getType() instanceof JavaType.FullyQualified) {
isExtendingWebSecurityConfigurerAdapter = FQN_WEB_SECURITY_CONFIGURER_ADAPTER.equals( ((JavaType.FullyQualified)superClass.getType()).getFullyQualifiedName());
}
}
if (isExtendingWebSecurityConfigurerAdapter) {
if (isAnnotatedWith(c.getLeadingAnnotations(), Annotations.CONFIGURATION)) {
String uri = getCursor().firstEnclosing(SourceFile.class).getSourcePath().toUri().toASCIIString();
String[] typeStubs = new String[0];
if (isUnresolved) {
typeStubs = new String[] { STUB_WEB_SECURITY_CONFIG_ADAPTER };
}
FixAssistMarker marker = new FixAssistMarker(Tree.randomId(), ID).withLabel(PROBLEM_LABEL)
.withFixes(
new FixDescriptor(ID, List.of(uri),
RecipeCodeActionDescriptor.buildLabel(FIX_LABEL, RecipeScope.FILE))
.withRecipeScope(RecipeScope.FILE),
.withRecipeScope(RecipeScope.FILE)
.withTypeStubs(typeStubs),
new FixDescriptor(ID, List.of(uri),
RecipeCodeActionDescriptor.buildLabel(FIX_LABEL, RecipeScope.PROJECT))
.withRecipeScope(RecipeScope.PROJECT));
.withRecipeScope(RecipeScope.PROJECT)
.withTypeStubs(typeStubs));
c = c.withName(c.getName().withMarkers(c.getName().getMarkers().add(marker)));
}
}
return c;
}
private boolean isExtendingWebSecurityConfigurerAdapter(J.ClassDeclaration c) {
TypeTree superClass = c.getExtends();
if (superClass != null) {
if (superClass.getType() instanceof JavaType.FullyQualified) {
return FQN_WEB_SECURITY_CONFIGURER_ADAPTER.equals( ((JavaType.FullyQualified)superClass.getType()).getFullyQualifiedName());
} else if (superClass.getType() instanceof JavaType.Unknown) {
String strType = superClass.printTrimmed(getCursor());
return "WebSecurityConfigurerAdapter".equals(strType) || FQN_WEB_SECURITY_CONFIGURER_ADAPTER.equals(strType);
}
}
return false;
}
};
}
@Override
public boolean isApplicable(IJavaProject project) {
Version version = SpringProjectUtil.getDependencyVersion(project, "spring-security-config");
return version != null && version.compareTo(new Version(5, 7, 0, null)) >= 0 && version.compareTo(new Version(6, 0, 0, null)) < 0;
return version != null && version.compareTo(new Version(5, 7, 0, null)) >= 0 && version.compareTo(new Version(6, 1, 0, null)) < 0;
}
private static boolean isAnnotatedWith(Collection<J.Annotation> annotations, String annotationType) {
return annotations.stream().anyMatch(a -> TypeUtils.isOfClassType(a.getType(), annotationType));
}
private static boolean isAnnotatedWith(Collection<J.Annotation> annotations, String annotationType) {
return annotations.stream().anyMatch(a -> TypeUtils.isOfClassType(a.getType(), annotationType));
}
}