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.WorkDoneProgressBegin;
import org.eclipse.lsp4j.WorkDoneProgressReport; 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. * 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) { private void begin(String title) {
WorkDoneProgressBegin progressBegin = new WorkDoneProgressBegin(); WorkDoneProgressBegin progressBegin = new WorkDoneProgressBegin();
progressBegin.setPercentage(LspClient.currentClient() == LspClient.Client.ECLIPSE ? 100 : 0); progressBegin.setPercentage(0);
progressBegin.setCancellable(false); progressBegin.setCancellable(false);
progressBegin.setTitle(title); progressBegin.setTitle(title);
service.progressBegin(taskId, progressBegin); 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 * All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0 * are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at * which accompanies this distribution, and is available at
@@ -30,10 +30,13 @@ final public class FixDescriptor {
private String label; private String label;
private String[] typeStubs;
public FixDescriptor(String recipeId, List<String> docUris, String label) { public FixDescriptor(String recipeId, List<String> docUris, String label) {
this.recipeId = recipeId; this.recipeId = recipeId;
this.docUris = docUris; this.docUris = docUris;
this.label = label; this.label = label;
this.typeStubs = new String[0];
} }
public FixDescriptor withRecipeScope(RecipeScope recipeScope) { public FixDescriptor withRecipeScope(RecipeScope recipeScope) {
@@ -50,7 +53,12 @@ final public class FixDescriptor {
this.parameters = parameters; this.parameters = parameters;
return this; return this;
} }
public FixDescriptor withTypeStubs(String... typeStubs) {
this.typeStubs = typeStubs;
return this;
}
public String getRecipeId() { public String getRecipeId() {
return recipeId; return recipeId;
} }
@@ -74,5 +82,9 @@ final public class FixDescriptor {
public String getLabel() { public String getLabel() {
return label; 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.internal.RecipeIntrospectionUtils;
import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.JavaIsoVisitor;
import org.openrewrite.java.JavaParser; import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaParser.Builder;
import org.openrewrite.java.JavaParsingException; import org.openrewrite.java.JavaParsingException;
import org.openrewrite.java.JavaVisitor; import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.UpdateSourcePositions; import org.openrewrite.java.UpdateSourcePositions;
@@ -234,10 +235,7 @@ public class ORAstUtils {
public static JavaParser createJavaParser(IJavaProject project) { public static JavaParser createJavaParser(IJavaProject project) {
try { try {
List<Path> classpath = IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath()).collect(Collectors.toList()); return createJavaParserBuilder(project).build();
JavaParser jp = JavaParser.fromJavaVersion().build();
jp.setClasspath(classpath);
return jp;
} catch (Exception e) { } catch (Exception e) {
if (isExceptionFromInterrupedThread(e)) { if (isExceptionFromInterrupedThread(e)) {
log.debug("", e); log.debug("", e);
@@ -247,34 +245,44 @@ public class ORAstUtils {
return null; return null;
} }
} }
public static List<CompilationUnit> parse(SimpleTextDocumentService documents, IJavaProject project, Consumer<SourceFile> parseCallback) { public static Builder<? extends JavaParser, ?> createJavaParserBuilder(IJavaProject project) {
List<Parser.Input> inputs = IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath()).flatMap(folder -> { List<Path> classpath = IClasspathUtil.getAllBinaryRoots(project.getClasspath()).stream().map(f -> f.toPath()).collect(Collectors.toList());
try { return JavaParser.fromJavaVersion().classpath(classpath);
return Files.walk(folder.toPath()); }
} catch (IOException e) {
log.error("", e); public static List<Parser.Input> getParserInputs(SimpleTextDocumentService documents, IJavaProject project) {
} return IClasspathUtil.getProjectJavaSourceFolders(project.getClasspath())
return Stream.empty(); .flatMap(folder -> {
}).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, () -> {
try { try {
return Files.newInputStream(p); return Files.walk(folder.toPath());
} catch (IOException e) { } catch (IOException e) {
log.error("", e); log.error("", e);
return new ByteArrayInputStream(new byte[0]);
} }
}); return Stream.empty();
} else { })
return new Parser.Input(p, () -> new ByteArrayInputStream(doc.get().getBytes())); .filter(Files::isRegularFile)
} .filter(p -> p.getFileName().toString().endsWith(".java"))
}).collect(Collectors.toList()); .map(p -> getParserInput(documents, p))
JavaParser javaParser = createJavaParser(project); .collect(Collectors.toList());
return ORAstUtils.parseInputs(javaParser, inputs, parseCallback);
} }
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) { public static List<CompilationUnit> parse(JavaParser parser, Iterable<Path> sourceFiles) {
InMemoryExecutionContext ctx = new InMemoryExecutionContext(ORAstUtils::logExceptionWhileParsing); InMemoryExecutionContext ctx = new InMemoryExecutionContext(ORAstUtils::logExceptionWhileParsing);
ctx.putMessage(JavaParser.SKIP_SOURCE_SET_TYPE_GENERATION, true); ctx.putMessage(JavaParser.SKIP_SOURCE_SET_TYPE_GENERATION, true);

View File

@@ -38,7 +38,7 @@ public class RewriteConfig {
@ConditionalOnBean(RewriteRecipeRepository.class) @ConditionalOnBean(RewriteRecipeRepository.class)
@Bean RewriteRefactorings rewriteRefactorings(SimpleLanguageServer server, JavaProjectFinder projectFinder, RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache) { @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) @ConditionalOnBean(RewriteRecipeRepository.class)

View File

@@ -12,10 +12,13 @@ package org.springframework.ide.vscode.boot.java.rewrite;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.URI; import java.net.URI;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeAction;
@@ -24,22 +27,25 @@ import org.eclipse.lsp4j.TextDocumentEdit;
import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either; import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.openrewrite.Parser.Input;
import org.openrewrite.Recipe; import org.openrewrite.Recipe;
import org.openrewrite.RecipeRun; import org.openrewrite.RecipeRun;
import org.openrewrite.Result; import org.openrewrite.Result;
import org.openrewrite.config.DeclarativeRecipe; import org.openrewrite.config.DeclarativeRecipe;
import org.openrewrite.internal.RecipeIntrospectionUtils; import org.openrewrite.internal.RecipeIntrospectionUtils;
import org.openrewrite.java.JavaParser;
import org.openrewrite.java.tree.J; import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.J.CompilationUnit; import org.openrewrite.java.tree.J.CompilationUnit;
import org.openrewrite.marker.Range; import org.openrewrite.marker.Range;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.java.IJavaProject; 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.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit; 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.quickfix.QuickfixHandler;
import org.springframework.ide.vscode.commons.languageserver.util.CodeActionResolver; 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.ORDocUtils;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope; import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor; import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
@@ -60,7 +66,7 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
private RewriteRecipeRepository recipeRepo; private RewriteRecipeRepository recipeRepo;
private SimpleTextDocumentService documents; private SimpleLanguageServer server;
private RewriteCompilationUnitCache cuCache; private RewriteCompilationUnitCache cuCache;
@@ -68,8 +74,8 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
private Gson gson; private Gson gson;
public RewriteRefactorings(SimpleTextDocumentService documents, JavaProjectFinder projectFinder, RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache) { public RewriteRefactorings(SimpleLanguageServer server, JavaProjectFinder projectFinder, RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache) {
this.documents = documents; this.server = server;
this.projectFinder = projectFinder; this.projectFinder = projectFinder;
this.recipeRepo = recipeRepo; this.recipeRepo = recipeRepo;
this.cuCache = cuCache; this.cuCache = cuCache;
@@ -120,7 +126,7 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
List<Result> results = reciperun.getResults(); List<Result> results = reciperun.getResults();
List<Either<TextDocumentEdit, ResourceOperation>> edits = results.stream().filter(res -> res.getAfter() != null).map(res -> { 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()); 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) { if (doc == null) {
doc = new TextDocument(docUri.toASCIIString(), LanguageId.JAVA, 0, res.getBefore() == null ? "" : res.getBefore().printAll()); 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))); Optional<IJavaProject> project = projectFinder.find(new TextDocumentIdentifier(data.getDocUris().get(0)));
if (project.isPresent()) { if (project.isPresent()) {
boolean projectWide = data.getRecipeScope() == RecipeScope.PROJECT; boolean projectWide = data.getRecipeScope() == RecipeScope.PROJECT;
Recipe r = createRecipe(data); Recipe r = createRecipe(data);
List<CompilationUnit> cus = Collections.emptyList();
if (projectWide) { if (projectWide) {
//TODO: progress here as well. JavaParser jp = ORAstUtils.createJavaParserBuilder(project.get()).dependsOn(data.getTypeStubs()).build();
return applyRecipe(r, project.get(), ORAstUtils.parse(documents, project.get(), null)); 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 { } 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); 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"; 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 @Override
public String getId() { public String getId() {
@@ -62,48 +88,52 @@ public class WebSecurityConfigurerAdapterCodeAction implements RecipeCodeActionD
@Override @Override
public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext p) { public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext p) {
ClassDeclaration c = super.visitClassDeclaration(classDecl, 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)) { if (isAnnotatedWith(c.getLeadingAnnotations(), Annotations.CONFIGURATION)) {
String uri = getCursor().firstEnclosing(SourceFile.class).getSourcePath().toUri().toASCIIString(); 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) FixAssistMarker marker = new FixAssistMarker(Tree.randomId(), ID).withLabel(PROBLEM_LABEL)
.withFixes( .withFixes(
new FixDescriptor(ID, List.of(uri), new FixDescriptor(ID, List.of(uri),
RecipeCodeActionDescriptor.buildLabel(FIX_LABEL, RecipeScope.FILE)) RecipeCodeActionDescriptor.buildLabel(FIX_LABEL, RecipeScope.FILE))
.withRecipeScope(RecipeScope.FILE), .withRecipeScope(RecipeScope.FILE)
.withTypeStubs(typeStubs),
new FixDescriptor(ID, List.of(uri), new FixDescriptor(ID, List.of(uri),
RecipeCodeActionDescriptor.buildLabel(FIX_LABEL, RecipeScope.PROJECT)) 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))); c = c.withName(c.getName().withMarkers(c.getName().getMarkers().add(marker)));
} }
} }
return c; 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 @Override
public boolean isApplicable(IJavaProject project) { public boolean isApplicable(IJavaProject project) {
Version version = SpringProjectUtil.getDependencyVersion(project, "spring-security-config"); 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) { private static boolean isAnnotatedWith(Collection<J.Annotation> annotations, String annotationType) {
return annotations.stream().anyMatch(a -> TypeUtils.isOfClassType(a.getType(), annotationType)); return annotations.stream().anyMatch(a -> TypeUtils.isOfClassType(a.getType(), annotationType));
} }
} }