diff --git a/Jenkinsfile b/Jenkinsfile index f42069f6688b..7d28d339c98c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -43,7 +43,7 @@ for(j = 0; j < jdks.size(); j++) { "MAVEN_OPTS=-Xmx1536m -Xms512m"], buildType, jdk) { // Actually run Maven! // -Dmaven.repo.local=… tells Maven to create a subdir in the temporary directory for the local Maven repository - def mvnCmd = "mvn -Pdebug -U -Dset.changelist help:evaluate -Dexpression=changelist -Doutput=$changelistF clean install ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -ntp -Dmaven.repo.local=$m2repo -e" + def mvnCmd = "mvn -Pdebug -Pjapicmp -U -Dset.changelist help:evaluate -Dexpression=changelist -Doutput=$changelistF clean install ${runTests ? '-Dmaven.test.failure.ignore' : '-DskipTests'} -V -B -ntp -Dmaven.repo.local=$m2repo -e" if(isUnix()) { sh mvnCmd @@ -69,6 +69,7 @@ for(j = 0; j < jdks.size(); j++) { allowEmptyArchive: true, // in case we forgot to reincrementalify fingerprint: true } + publishHTML([allowMissing: true, alwaysLinkToLastBuild: false, includes: 'japicmp.html', keepAll: false, reportDir: 'core/target/japicmp', reportFiles: 'japicmp.html', reportName: 'API compatibility', reportTitles: 'japicmp report']) } } } diff --git a/bom/pom.xml b/bom/pom.xml index fd67d9d3f3f1..8f2fb973dd42 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -42,12 +42,18 @@ THE SOFTWARE. 11.0.1 1.7.30 1.261 - 2.5.6.SEC03 2.4.12 + + org.springframework.security + spring-security-bom + 5.4.1 + pom + import + com.github.spotbugs spotbugs-annotations @@ -418,29 +424,6 @@ THE SOFTWARE. 1.1-beta-11 - - - org.springframework - spring-webmvc - ${spring.version} - - - org.springframework - spring-core - ${spring.version} - - - org.springframework - spring-aop - ${spring.version} - - - org.acegisecurity - acegi-security - 1.0.7 - - - org.jenkins-ci.modules diff --git a/core/pom.xml b/core/pom.xml index 6a16873c1dc6..561ff058a7ac 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -387,16 +387,12 @@ THE SOFTWARE. commons-jexl - org.acegisecurity - acegi-security + org.springframework.security + spring-security-web org.springframework - spring-remoting - - - org.springframework - spring-support + spring-jcl @@ -414,22 +410,6 @@ THE SOFTWARE. org.fusesource.jansi jansi - - - org.springframework - spring-webmvc - - - org.springframework - spring-core - - - org.springframework - spring-aop - org.junit.jupiter junit-jupiter-api @@ -869,5 +849,41 @@ THE SOFTWARE. true + + japicmp + + + + com.github.siom79.japicmp + japicmp-maven-plugin + 0.14.4-20200728.214757-1 + + + + \d+[.]\d+ + + true + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + + + verify + + cmp + + + + + + + diff --git a/core/src/main/java/hudson/DependencyRunner.java b/core/src/main/java/hudson/DependencyRunner.java index 6fd8889caa3c..68e7257bb265 100644 --- a/core/src/main/java/hudson/DependencyRunner.java +++ b/core/src/main/java/hudson/DependencyRunner.java @@ -52,7 +52,7 @@ public DependencyRunner(ProjectRunnable runnable) { } public void run() { - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { Set topLevelProjects = new HashSet<>(); // Get all top-level projects LOGGER.fine("assembling top level projects"); diff --git a/core/src/main/java/hudson/ExpressionFactory2.java b/core/src/main/java/hudson/ExpressionFactory2.java index c8704fccfdb8..e3722182493b 100644 --- a/core/src/main/java/hudson/ExpressionFactory2.java +++ b/core/src/main/java/hudson/ExpressionFactory2.java @@ -1,6 +1,5 @@ package hudson; -import org.acegisecurity.AcegiSecurityException; import org.apache.commons.jelly.JellyContext; import org.apache.commons.jelly.JellyException; import org.apache.commons.jelly.expression.Expression; @@ -15,6 +14,7 @@ import java.util.logging.Logger; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; +import org.springframework.security.access.AccessDeniedException; /** * {@link ExpressionFactory} so that security exception aborts the page rendering. @@ -72,7 +72,7 @@ public Object evaluate(JellyContext context) { CURRENT_CONTEXT.set(context); JexlContext jexlContext = new JellyJexlContext( context ); return expression.evaluate(jexlContext); - } catch (AcegiSecurityException e) { + } catch (AccessDeniedException e) { // let the security exception pass through throw e; } catch (Exception e) { diff --git a/core/src/main/java/hudson/ExtensionFinder.java b/core/src/main/java/hudson/ExtensionFinder.java index ed4e3581b203..4ac88b9d8dee 100644 --- a/core/src/main/java/hudson/ExtensionFinder.java +++ b/core/src/main/java/hudson/ExtensionFinder.java @@ -578,7 +578,7 @@ public void onProvision(ProvisionInvocation provision) { // so that we invoke them before derived class one. This isn't specified in JSR-250 but implemented // this way in Spring and what most developers would expect to happen. - final Set interfaces = ClassUtils.getAllInterfacesAsSet(instance); + final Set> interfaces = ClassUtils.getAllInterfacesAsSet(instance); while (c != Object.class) { Arrays.stream(c.getDeclaredMethods()) @@ -607,7 +607,7 @@ public void onProvision(ProvisionInvocation provision) { * This allows to introspect metadata for a method which is both declared in parent class and in implemented * interface(s). {@code interfaces} typically is obtained by {@link ClassUtils#getAllInterfacesAsSet} */ - Collection getMethodAndInterfaceDeclarations(Method method, Collection interfaces) { + Collection getMethodAndInterfaceDeclarations(Method method, Collection> interfaces) { final List methods = new ArrayList<>(); methods.add(method); diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 3ce3e038833d..926d4cee4d5f 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -145,7 +145,6 @@ import jenkins.model.ModelObjectWithChildren; import jenkins.model.ModelObjectWithContextMenu; -import org.acegisecurity.AccessDeniedException; import org.apache.commons.jelly.JellyContext; import org.apache.commons.jelly.JellyTagException; import org.apache.commons.jelly.Script; @@ -174,6 +173,7 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.accmod.restrictions.DoNotUse; +import org.springframework.security.access.AccessDeniedException; /** * Utility functions used in views. @@ -1770,7 +1770,7 @@ public static String toCCStatus(Item i) { * Checks if the current user is anonymous. */ public static boolean isAnonymous() { - return ACL.isAnonymous(Jenkins.getAuthentication()); + return ACL.isAnonymous2(Jenkins.getAuthentication2()); } /** diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 150754805283..0614c5339e34 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -69,7 +69,6 @@ import jenkins.util.xml.RestrictiveEntityResolver; import net.sf.json.JSONArray; import net.sf.json.JSONObject; -import org.acegisecurity.Authentication; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; @@ -156,6 +155,7 @@ import static hudson.init.InitMilestone.*; import static java.util.logging.Level.*; +import org.springframework.security.core.Authentication; /** * Manages {@link PluginWrapper}s. @@ -884,7 +884,7 @@ public void dynamicLoad(File arc) throws IOException, InterruptedException, Rest */ @Restricted(NoExternalUse.class) public void dynamicLoad(File arc, boolean removeExisting, @CheckForNull List batch) throws IOException, InterruptedException, RestartRequiredException { - try (ACLContext context = ACL.as(ACL.SYSTEM)) { + try (ACLContext context = ACL.as2(ACL.SYSTEM2)) { LOGGER.log(FINE, "Attempting to dynamic load {0}", arc); PluginWrapper p = null; String sn; @@ -951,7 +951,7 @@ public void dynamicLoad(File arc, boolean removeExisting, @CheckForNull List plugins) throws Exception { - try (ACLContext context = ACL.as(ACL.SYSTEM)) { + try (ACLContext context = ACL.as2(ACL.SYSTEM2)) { Map pluginsByName = plugins.stream().collect(Collectors.toMap(p -> p.getShortName(), p -> p)); // recalculate dependencies of plugins optionally depending the newly deployed ones. @@ -1548,7 +1548,7 @@ private List> install(@NonNull Collection> f = item != null ? (QueueTaskFuture)item.getFuture() : null; if (wait || sync || follow) { diff --git a/core/src/main/java/hudson/cli/CLIAction.java b/core/src/main/java/hudson/cli/CLIAction.java index efca422c4437..9564fa675f80 100644 --- a/core/src/main/java/hudson/cli/CLIAction.java +++ b/core/src/main/java/hudson/cli/CLIAction.java @@ -61,9 +61,9 @@ import jenkins.util.FullDuplexHttpService; import jenkins.websocket.WebSocketSession; import jenkins.websocket.WebSockets; -import org.acegisecurity.Authentication; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; +import org.springframework.security.core.Authentication; /** * Shows usage of CLI and commands. @@ -118,7 +118,7 @@ public HttpResponse doWs() { if (!WebSockets.isSupported()) { return HttpResponses.notFound(); } - Authentication authentication = Jenkins.getAuthentication(); + Authentication authentication = Jenkins.getAuthentication2(); return WebSockets.upgrade(new WebSocketSession() { ServerSideImpl connection; class OutputImpl implements PlainCLIProtocol.Output { @@ -269,7 +269,7 @@ void run() throws IOException, InterruptedException { sendExit(2); return; } - command.setTransportAuth(authentication); + command.setTransportAuth2(authentication); command.setClientCharset(encoding); CLICommand orig = CLICommand.setCurrent(command); try { @@ -303,7 +303,7 @@ protected FullDuplexHttpService createService(StaplerRequest req, UUID uuid) thr return new FullDuplexHttpService(uuid) { @Override protected void run(InputStream upload, OutputStream download) throws IOException, InterruptedException { - try (ServerSideImpl connection = new ServerSideImpl(new PlainCLIProtocol.FramedOutput(download), Jenkins.getAuthentication())) { + try (ServerSideImpl connection = new ServerSideImpl(new PlainCLIProtocol.FramedOutput(download), Jenkins.getAuthentication2())) { new PlainCLIProtocol.FramedReader(connection, upload).start(); connection.run(); } diff --git a/core/src/main/java/hudson/cli/CLICommand.java b/core/src/main/java/hudson/cli/CLICommand.java index a1f11658cddb..77440d3e898e 100644 --- a/core/src/main/java/hudson/cli/CLICommand.java +++ b/core/src/main/java/hudson/cli/CLICommand.java @@ -23,35 +23,18 @@ */ package hudson.cli; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.AbortException; import hudson.Extension; import hudson.ExtensionList; import hudson.ExtensionPoint; -import hudson.cli.declarative.CLIMethod; import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson; import hudson.Functions; +import hudson.cli.declarative.CLIMethod; import hudson.cli.declarative.OptionHandlerExtension; -import jenkins.model.Jenkins; import hudson.remoting.Channel; import hudson.security.SecurityRealm; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.Authentication; -import org.acegisecurity.BadCredentialsException; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; -import org.apache.commons.discovery.ResourceClassIterator; -import org.apache.commons.discovery.ResourceNameIterator; -import org.apache.commons.discovery.resource.ClassLoaders; -import org.apache.commons.discovery.resource.classes.DiscoverClasses; -import org.apache.commons.discovery.resource.names.DiscoverServiceNames; -import org.jvnet.hudson.annotation_indexer.Index; -import org.jvnet.tiger_types.Types; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.args4j.CmdLineException; -import org.kohsuke.args4j.CmdLineParser; -import org.kohsuke.args4j.spi.OptionHandler; - import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -64,8 +47,24 @@ import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; +import jenkins.model.Jenkins; +import org.apache.commons.discovery.ResourceClassIterator; +import org.apache.commons.discovery.ResourceNameIterator; +import org.apache.commons.discovery.resource.ClassLoaders; +import org.apache.commons.discovery.resource.classes.DiscoverClasses; +import org.apache.commons.discovery.resource.names.DiscoverServiceNames; +import org.jvnet.hudson.annotation_indexer.Index; +import org.jvnet.tiger_types.Types; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.kohsuke.args4j.spi.OptionHandler; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; /** * Base class for Hudson CLI. @@ -189,7 +188,7 @@ public String getName() { * The default implementation uses args4j to parse command line arguments and call {@link #run()}, * but if that processing is undesirable, subtypes can directly override this method and leave {@link #run()} * to an empty method. - * You would however then have to consider {@link #getTransportAuthentication}, + * You would however then have to consider {@link #getTransportAuthentication2}, * so this is not really recommended. * * @param args @@ -236,10 +235,11 @@ public int main(List args, Locale locale, InputStream stdin, PrintStream Authentication old = null; Authentication auth; try { + // TODO as in CLIRegisterer this may be doing too much work sc = SecurityContextHolder.getContext(); old = sc.getAuthentication(); - sc.setAuthentication(auth = getTransportAuthentication()); + sc.setAuthentication(auth = getTransportAuthentication2()); if (!(this instanceof HelpCommand || this instanceof WhoAmICommand)) Jenkins.get().checkPermission(Jenkins.READ); @@ -285,7 +285,7 @@ public int main(List args, Locale locale, InputStream stdin, PrintStream } private void logFailedCommandAndPrintExceptionErrorMessage(List args, Throwable e) { - Authentication auth = getTransportAuthentication(); + Authentication auth = getTransportAuthentication2(); String logMessage = String.format("Failed call to CLI command %s, with %d arguments, as user %s.", getName(), args.size(), auth != null ? auth.getName() : ""); @@ -316,28 +316,6 @@ public Channel checkChannel() throws AbortException { throw new AbortException("This command is requesting the -remoting mode which is no longer supported. See https://jenkins.io/redirect/cli-command-requires-channel"); } - /** - * Determines if the user authentication is attempted through CLI before running this command. - * - *

- * If your command doesn't require any authentication whatsoever, and if you don't even want to let the user - * authenticate, then override this method to always return false — doing so will result in all the commands - * running as anonymous user credential. - * - *

- * Note that even if this method returns true, the user can still skip aut - * - * @param auth - * Always non-null. - * If the underlying transport had already performed authentication, this object is something other than - * {@link jenkins.model.Jenkins#ANONYMOUS}. - * @deprecated Unused. - */ - @Deprecated - protected boolean shouldPerformAuthentication(Authentication auth) { - return auth== Jenkins.ANONYMOUS; - } - /** * Returns the identity of the client as determined at the CLI transport level. * @@ -352,18 +330,38 @@ protected boolean shouldPerformAuthentication(Authentication auth) { * then this method can return a valid identity of the client. * *

- * If the transport doesn't do authentication, this method returns {@link jenkins.model.Jenkins#ANONYMOUS}. + * If the transport doesn't do authentication, this method returns {@link jenkins.model.Jenkins#ANONYMOUS2}. + * @since TODO */ - public Authentication getTransportAuthentication() { + public Authentication getTransportAuthentication2() { Authentication a = transportAuth; - if (a==null) a = Jenkins.ANONYMOUS; + if (a==null) a = Jenkins.ANONYMOUS2; return a; } - public void setTransportAuth(Authentication transportAuth) { + /** + * @deprecated use {@link #getTransportAuthentication2} + */ + @Deprecated + public org.acegisecurity.Authentication getTransportAuthentication() { + return org.acegisecurity.Authentication.fromSpring(getTransportAuthentication2()); + } + + /** + * @since TODO + */ + public void setTransportAuth2(Authentication transportAuth) { this.transportAuth = transportAuth; } + /** + * @deprecated use {@link #setTransportAuth2} + */ + @Deprecated + public void setTransportAuth(org.acegisecurity.Authentication transportAuth) { + setTransportAuth2(transportAuth.toSpring()); + } + /** * Executes the command, and return the exit code. * diff --git a/core/src/main/java/hudson/cli/HelpCommand.java b/core/src/main/java/hudson/cli/HelpCommand.java index a4623f5b8f8b..fa4c73756f6c 100644 --- a/core/src/main/java/hudson/cli/HelpCommand.java +++ b/core/src/main/java/hudson/cli/HelpCommand.java @@ -30,8 +30,8 @@ import java.util.Map; import java.util.TreeMap; -import org.acegisecurity.AccessDeniedException; import org.kohsuke.args4j.Argument; +import org.springframework.security.access.AccessDeniedException; /** * Show the list of all commands. diff --git a/core/src/main/java/hudson/cli/WhoAmICommand.java b/core/src/main/java/hudson/cli/WhoAmICommand.java index 6ad618d12217..9ffc35861ace 100644 --- a/core/src/main/java/hudson/cli/WhoAmICommand.java +++ b/core/src/main/java/hudson/cli/WhoAmICommand.java @@ -25,8 +25,8 @@ import hudson.Extension; import jenkins.model.Jenkins; -import org.acegisecurity.Authentication; -import org.acegisecurity.GrantedAuthority; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; /** * Report the current granted authorities @@ -41,7 +41,7 @@ public String getShortDescription() { } protected int run() { - Authentication a = Jenkins.getAuthentication(); + Authentication a = Jenkins.getAuthentication2(); stdout.println("Authenticated as: "+a.getName()); stdout.println("Authorities:"); for (GrantedAuthority ga : a.getAuthorities()) { diff --git a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java index 8f7f01b6490b..168aa49f7bb5 100644 --- a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java +++ b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java @@ -23,6 +23,7 @@ */ package hudson.cli.declarative; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.AbortException; import hudson.Extension; import hudson.ExtensionComponent; @@ -32,20 +33,6 @@ import hudson.cli.CLICommand; import hudson.cli.CloneableCLICommand; import hudson.model.Hudson; -import jenkins.ExtensionComponentSet; -import jenkins.ExtensionRefreshException; -import jenkins.model.Jenkins; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.Authentication; -import org.acegisecurity.BadCredentialsException; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; -import org.jvnet.hudson.annotation_indexer.Index; -import org.jvnet.localizer.ResourceBundleHolder; -import org.kohsuke.args4j.CmdLineParser; -import org.kohsuke.args4j.CmdLineException; - -import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; @@ -59,11 +46,22 @@ import java.util.Locale; import java.util.MissingResourceException; import java.util.Stack; -import static java.util.logging.Level.SEVERE; - import java.util.UUID; import java.util.logging.Level; +import static java.util.logging.Level.SEVERE; import java.util.logging.Logger; +import jenkins.ExtensionComponentSet; +import jenkins.ExtensionRefreshException; +import jenkins.model.Jenkins; +import org.jvnet.hudson.annotation_indexer.Index; +import org.jvnet.localizer.ResourceBundleHolder; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; /** * Discover {@link CLIMethod}s and register them as {@link CLICommand} implementations. @@ -202,13 +200,14 @@ public int main(List args, Locale locale, InputStream stdin, PrintStream CmdLineParser parser = bindMethod(binders); try { + // TODO this could probably use ACL.as; why is it calling SecurityContext.setAuthentication rather than SecurityContextHolder.setContext? SecurityContext sc = SecurityContextHolder.getContext(); Authentication old = sc.getAuthentication(); try { // fill up all the binders parser.parseArgument(args); - Authentication auth = getTransportAuthentication(); + Authentication auth = getTransportAuthentication2(); sc.setAuthentication(auth); // run the CLI with the right credential jenkins.checkPermission(Jenkins.READ); diff --git a/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java b/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java index 5bd9834b485b..99bae388ca68 100644 --- a/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java +++ b/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java @@ -31,13 +31,13 @@ import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; -import org.acegisecurity.Authentication; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.OptionDef; import org.kohsuke.args4j.spi.OptionHandler; import org.kohsuke.args4j.spi.Parameters; import org.kohsuke.args4j.spi.Setter; +import org.springframework.security.core.Authentication; /** * Refers to an {@link Item} by its name. @@ -61,8 +61,8 @@ protected GenericItemOptionHandler(CmdLineParser parser, OptionDef option, Sette final String src = params.getParameter(0); T s = j.getItemByFullName(src, type()); if (s == null) { - final Authentication who = Jenkins.getAuthentication(); - try (ACLContext acl = ACL.as(ACL.SYSTEM)) { + final Authentication who = Jenkins.getAuthentication2(); + try (ACLContext acl = ACL.as2(ACL.SYSTEM2)) { Item actual = j.getItemByFullName(src); if (actual == null) { LOGGER.log(Level.FINE, "really no item exists named {0}", src); diff --git a/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java b/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java index 3c8a52d07e86..33f787e998b1 100644 --- a/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java +++ b/core/src/main/java/hudson/cli/handlers/ViewOptionHandler.java @@ -30,7 +30,6 @@ import jenkins.model.Jenkins; -import org.acegisecurity.AccessDeniedException; import org.kohsuke.MetaInfServices; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; @@ -40,6 +39,7 @@ import org.kohsuke.args4j.spi.Setter; import edu.umd.cs.findbugs.annotations.CheckForNull; +import org.springframework.security.access.AccessDeniedException; /** * Refers to {@link View} by its name. diff --git a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java index 82dbffcf085a..3cc2520c11c0 100644 --- a/core/src/main/java/hudson/diagnosis/OldDataMonitor.java +++ b/core/src/main/java/hudson/diagnosis/OldDataMonitor.java @@ -118,7 +118,7 @@ public Map getData() { private static void remove(Saveable obj, boolean isDelete) { Jenkins j = Jenkins.get(); OldDataMonitor odm = get(j); - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { odm.data.remove(referTo(obj)); if (isDelete && obj instanceof Job) { for (Run r : ((Job) obj).getBuilds()) { diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index 7e636b2dda8f..6af15f3ad15d 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -75,7 +75,6 @@ import java.util.regex.Pattern; import edu.umd.cs.findbugs.annotations.NonNull; -import org.acegisecurity.AccessDeniedException; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; @@ -102,6 +101,7 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.Ancestor; +import org.springframework.security.access.AccessDeniedException; /** * Partial default implementation of {@link Item}. @@ -313,13 +313,13 @@ private void checkIfNameIsUsed(@NonNull String newName) throws Failure { if (item != null) { throw new Failure(Messages.AbstractItem_NewNameInUse(newName)); } - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { item = getParent().getItem(newName); if (item != null) { if (LOGGER.isLoggable(Level.FINE)) { LOGGER.log(Level.FINE, "Unable to rename the job {0}: name {1} is already in use. " + "User {2} has no {3} permission for existing job with the same name", - new Object[] {this.getFullName(), newName, ctx.getPreviousContext().getAuthentication().getName(), Item.DISCOVER.name} ); + new Object[] {this.getFullName(), newName, ctx.getPreviousContext2().getAuthentication().getName(), Item.DISCOVER.name} ); } // Don't explicitly mention that there is another item with the same name. throw new Failure(Messages.Jenkins_NotAllowedName(newName)); diff --git a/core/src/main/java/hudson/model/AsyncAperiodicWork.java b/core/src/main/java/hudson/model/AsyncAperiodicWork.java index cd9f46b830cf..b9b705d8bc0b 100644 --- a/core/src/main/java/hudson/model/AsyncAperiodicWork.java +++ b/core/src/main/java/hudson/model/AsyncAperiodicWork.java @@ -114,7 +114,7 @@ public final void doAperiodicRun() { long stopTime; StreamTaskListener l = createListener(); - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { l.getLogger().printf("Started at %tc%n", new Date(startTime)); execute(l); } catch (IOException e) { diff --git a/core/src/main/java/hudson/model/AsyncPeriodicWork.java b/core/src/main/java/hudson/model/AsyncPeriodicWork.java index 80031750b764..c0ae6eb5c18f 100644 --- a/core/src/main/java/hudson/model/AsyncPeriodicWork.java +++ b/core/src/main/java/hudson/model/AsyncPeriodicWork.java @@ -96,7 +96,7 @@ public final void doRun() { StreamTaskListener l = createListener(); try { l.getLogger().printf("Started at %tc%n", new Date(startTime)); - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { execute(l); } } catch (IOException e) { diff --git a/core/src/main/java/hudson/model/BuildAuthorizationToken.java b/core/src/main/java/hudson/model/BuildAuthorizationToken.java index dd54ffb30756..5c31e1fadabf 100644 --- a/core/src/main/java/hudson/model/BuildAuthorizationToken.java +++ b/core/src/main/java/hudson/model/BuildAuthorizationToken.java @@ -33,8 +33,8 @@ import java.io.IOException; import javax.servlet.http.HttpServletResponse; import jenkins.security.ApiTokenProperty; -import org.acegisecurity.AccessDeniedException; import org.kohsuke.stapler.HttpResponses; +import org.springframework.security.access.AccessDeniedException; /** * Authorization token to allow projects to trigger themselves under the secured environment. diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java index b5e481f98203..23bc9c8e9042 100644 --- a/core/src/main/java/hudson/model/Cause.java +++ b/core/src/main/java/hudson/model/Cause.java @@ -362,7 +362,7 @@ public static class DeeplyNestedUpstreamCause extends Cause { public static class UserCause extends Cause { private String authenticationName; public UserCause() { - this.authenticationName = Jenkins.getAuthentication().getName(); + this.authenticationName = Jenkins.getAuthentication2().getName(); } /** diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index ad10061530d0..cbf495d3cea3 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -1366,7 +1366,7 @@ public String call() throws IOException { Executors.newCachedThreadPool( new ExceptionCatchingThreadFactory( new NamingThreadFactory( - new DaemonThreadFactory(), "Computer.threadPoolForRemoting"))), ACL.SYSTEM)); + new DaemonThreadFactory(), "Computer.threadPoolForRemoting"))), ACL.SYSTEM2)); // // diff --git a/core/src/main/java/hudson/model/DependencyGraph.java b/core/src/main/java/hudson/model/DependencyGraph.java index f52fb4e1eaca..b5dd097acf06 100644 --- a/core/src/main/java/hudson/model/DependencyGraph.java +++ b/core/src/main/java/hudson/model/DependencyGraph.java @@ -86,7 +86,7 @@ public DependencyGraph() { public void build() { // Set full privileges while computing to avoid missing any projects the current user cannot see. - try (ACLContext ctx = ACL.as(ACL.SYSTEM)){ + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)){ this.computationalData = new HashMap<>(); for( AbstractProject p : Jenkins.get().allItems(AbstractProject.class) ) p.buildDependencyGraph(this); @@ -401,7 +401,7 @@ public AbstractProject getDownstreamProject() { * Decide whether build should be triggered and provide any Actions for the build. * Default implementation always returns true (for backward compatibility), and * adds no Actions. Subclasses may override to control how/if the build is triggered. - *

The authentication in effect ({@link Jenkins#getAuthentication}) will be that of the upstream build. + *

The authentication in effect ({@link Jenkins#getAuthentication2}) will be that of the upstream build. * An implementation is expected to perform any relevant access control checks: * that an upstream project can both see and build a downstream project, * or that a downstream project can see an upstream project. diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java index dfcb32a9ee6f..10d33101a71e 100644 --- a/core/src/main/java/hudson/model/Executor.java +++ b/core/src/main/java/hudson/model/Executor.java @@ -36,8 +36,6 @@ import jenkins.model.CauseOfInterruption.UserInterruption; import jenkins.model.InterruptedBuildAction; import jenkins.model.Jenkins; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.Authentication; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.QueryParameter; @@ -76,6 +74,8 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; /** @@ -196,8 +196,8 @@ public void interrupt(Result result) { } private void interrupt(Result result, boolean forShutdown) { - Authentication a = Jenkins.getAuthentication(); - if (a == ACL.SYSTEM) + Authentication a = Jenkins.getAuthentication2(); + if (a.equals(ACL.SYSTEM2)) interrupt(result, forShutdown, new CauseOfInterruption[0]); else { // worth recording who did it @@ -340,7 +340,7 @@ public void run() { lock.writeLock().unlock(); } - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { SubTask task; // transition from idle to building. // perform this state change as an atomic operation wrt other queue operations @@ -414,9 +414,9 @@ public SubTask call() throws Exception { } setName(getName() + " : executing " + executable.toString()); - Authentication auth = workUnit.context.item.authenticate(); + Authentication auth = workUnit.context.item.authenticate2(); LOGGER.log(FINE, "{0} is now executing {1} as {2}", new Object[] {getName(), executable, auth}); - if (LOGGER.isLoggable(FINE) && auth.equals(ACL.SYSTEM)) { // i.e., unspecified + if (LOGGER.isLoggable(FINE) && auth.equals(ACL.SYSTEM2)) { // i.e., unspecified if (QueueItemAuthenticatorDescriptor.all().isEmpty()) { LOGGER.fine("no QueueItemAuthenticator implementations installed"); } else if (QueueItemAuthenticatorConfiguration.get().getAuthenticators().isEmpty()) { @@ -425,7 +425,7 @@ public SubTask call() throws Exception { LOGGER.log(FINE, "some QueueItemAuthenticator implementations configured but neglected to authenticate {0}", executable); } } - try (ACLContext context = ACL.as(auth)) { + try (ACLContext context = ACL.as2(auth)) { queue.execute(executable, task); } } catch (AsynchronousExecution x) { diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java index 577a50373e80..b3480b70296a 100644 --- a/core/src/main/java/hudson/model/Fingerprint.java +++ b/core/src/main/java/hudson/model/Fingerprint.java @@ -71,8 +71,8 @@ import java.util.logging.Logger; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.Authentication; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; /** * A file being tracked by Jenkins. @@ -125,7 +125,7 @@ public String getName() { */ private boolean hasPermissionToDiscoverBuild() { // We expose the data to Jenkins administrators in order to - // let them manage the data for deleted jobs (also works for SYSTEM) + // let them manage the data for deleted jobs (also works for SYSTEM2) final Jenkins instance = Jenkins.get(); if (instance.hasPermission(Jenkins.ADMINISTER)) { return true; @@ -831,7 +831,7 @@ public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingCont public static final class ProjectRenameListener extends ItemListener { @Override public void onLocationChanged(final Item item, final String oldName, final String newName) { - try (ACLContext acl = ACL.as(ACL.SYSTEM)) { + try (ACLContext acl = ACL.as2(ACL.SYSTEM2)) { locationChanged(item, oldName, newName); } } @@ -1452,9 +1452,9 @@ private static boolean canDiscoverItem(@NonNull final String fullName) { } // Probably it failed due to the missing Item.DISCOVER - // We try to retrieve the job using SYSTEM user and to check permissions manually. - final Authentication userAuth = Jenkins.getAuthentication(); - try (ACLContext acl = ACL.as(ACL.SYSTEM)) { + // We try to retrieve the job using SYSTEM2 user and to check permissions manually. + final Authentication userAuth = Jenkins.getAuthentication2(); + try (ACLContext acl = ACL.as2(ACL.SYSTEM2)) { final Item itemBySystemUser = jenkins.getItemByFullName(fullName); if (itemBySystemUser == null) { return false; @@ -1462,14 +1462,14 @@ private static boolean canDiscoverItem(@NonNull final String fullName) { // To get the item existence fact, a user needs Item.DISCOVER for the item // and Item.READ for all container folders. - boolean canDiscoverTheItem = itemBySystemUser.hasPermission(userAuth, Item.DISCOVER); + boolean canDiscoverTheItem = itemBySystemUser.hasPermission2(userAuth, Item.DISCOVER); if (canDiscoverTheItem) { ItemGroup current = itemBySystemUser.getParent(); do { if (current instanceof Item) { final Item i = (Item) current; current = i.getParent(); - if (!i.hasPermission(userAuth, Item.READ)) { + if (!i.hasPermission2(userAuth, Item.READ)) { canDiscoverTheItem = false; } } else { diff --git a/core/src/main/java/hudson/model/ItemGroup.java b/core/src/main/java/hudson/model/ItemGroup.java index ad56acbc0780..b55609f910dc 100644 --- a/core/src/main/java/hudson/model/ItemGroup.java +++ b/core/src/main/java/hudson/model/ItemGroup.java @@ -32,7 +32,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import edu.umd.cs.findbugs.annotations.CheckForNull; -import org.acegisecurity.AccessDeniedException; +import org.springframework.security.access.AccessDeniedException; /** * Represents a grouping inherent to a kind of {@link Item}s. diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java index 25b2505f15b3..f34f89eb4df6 100644 --- a/core/src/main/java/hudson/model/ItemGroupMixIn.java +++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java @@ -51,7 +51,7 @@ import java.util.logging.Logger; import java.util.regex.Matcher; import jenkins.security.NotReallyRoleSensitiveCallable; -import org.acegisecurity.AccessDeniedException; +import org.springframework.security.access.AccessDeniedException; import org.xml.sax.SAXException; /** @@ -229,7 +229,7 @@ public synchronized T copy(T src, String name) throws I while (matcher.find()) { if (Secret.decrypt(matcher.group(1)) != null) { // AccessDeniedException2 does not permit a custom message, and anyway redirecting the user to the login screen is obviously pointless. - throw new AccessDeniedException(Messages.ItemGroupMixIn_may_not_copy_as_it_contains_secrets_and_(src.getFullName(), Jenkins.getAuthentication().getName(), Item.PERMISSIONS.title, Item.EXTENDED_READ.name, Item.CONFIGURE.name)); + throw new AccessDeniedException(Messages.ItemGroupMixIn_may_not_copy_as_it_contains_secrets_and_(src.getFullName(), Jenkins.getAuthentication2().getName(), Item.PERMISSIONS.title, Item.EXTENDED_READ.name, Item.CONFIGURE.name)); } } } @@ -282,7 +282,7 @@ public synchronized TopLevelItem createProjectFromXML(String name, InputStream x } }); - success = acl.getACL().hasCreatePermission(Jenkins.getAuthentication(), parent, result.getDescriptor()) + success = acl.getACL().hasCreatePermission2(Jenkins.getAuthentication2(), parent, result.getDescriptor()) && result.getDescriptor().isApplicableIn(parent); add(result); diff --git a/core/src/main/java/hudson/model/Items.java b/core/src/main/java/hudson/model/Items.java index 298d30235845..e21efe6d5b05 100644 --- a/core/src/main/java/hudson/model/Items.java +++ b/core/src/main/java/hudson/model/Items.java @@ -52,9 +52,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; import jenkins.model.DirectlyModifiableTopLevelItemGroup; import jenkins.model.Jenkins; -import org.acegisecurity.Authentication; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; +import org.springframework.security.core.Authentication; /** * Convenience methods related to {@link Item}. @@ -163,16 +163,16 @@ public static DescriptorExtensionList all() * @since 1.607 */ public static List all(ItemGroup c) { - return all(Jenkins.getAuthentication(), c); + return all2(Jenkins.getAuthentication2(), c); } /** * Returns all the registered {@link TopLevelItemDescriptor}s that the specified security principal is allowed to * create within the specified item group. * - * @since 1.607 + * @since TODO */ - public static List all(Authentication a, ItemGroup c) { + public static List all2(Authentication a, ItemGroup c) { List result = new ArrayList<>(); ACL acl; if (c instanceof AccessControlled) { @@ -182,13 +182,22 @@ public static List all(Authentication a, ItemGroup c) { acl = Jenkins.get().getACL(); } for (TopLevelItemDescriptor d: all()) { - if (acl.hasCreatePermission(a, c, d) && d.isApplicableIn(c)) { + if (acl.hasCreatePermission2(a, c, d) && d.isApplicableIn(c)) { result.add(d); } } return result; } + /** + * @deprecated use {@link #all2(Authentication, ItemGroup)} + * @since 1.607 + */ + @Deprecated + public static List all(org.acegisecurity.Authentication a, ItemGroup c) { + return all2(a.toSpring(), c); + } + /** * @deprecated Underspecified what the parameter is. {@link Descriptor#getId}? A {@link Describable} class name? */ @@ -438,7 +447,7 @@ private static void getAllItems(final ItemGroup root, Class /** * Gets a read-only view of all the {@link Item}s recursively in the {@link ItemGroup} tree visible to - * {@link Jenkins#getAuthentication()} without concern for the order in which items are returned. Each iteration + * {@link Jenkins#getAuthentication2()} without concern for the order in which items are returned. Each iteration * of the view will be "live" reflecting the items available between the time the iteration was started and the * time the iteration was completed, however if items are moved during an iteration - depending on the move - it * may be possible for such items to escape the entire iteration. @@ -450,13 +459,13 @@ private static void getAllItems(final ItemGroup root, Class * @since 2.37 */ public static Iterable allItems(ItemGroup root, Class type) { - return allItems(Jenkins.getAuthentication(), root, type); + return allItems2(Jenkins.getAuthentication2(), root, type); } /** * Gets a read-only view of all the {@link Item}s recursively matching type and predicate * in the {@link ItemGroup} tree visible to - * {@link Jenkins#getAuthentication()} without concern for the order in which items are returned. Each iteration + * {@link Jenkins#getAuthentication2()} without concern for the order in which items are returned. Each iteration * of the view will be "live" reflecting the items available between the time the iteration was started and the * time the iteration was completed, however if items are moved during an iteration - depending on the move - it * may be possible for such items to escape the entire iteration. @@ -469,7 +478,7 @@ public static Iterable allItems(ItemGroup root, Class typ * @since 2.221 */ public static Iterable allItems(ItemGroup root, Class type, Predicate pred) { - return allItems(Jenkins.getAuthentication(), root, type, pred); + return allItems2(Jenkins.getAuthentication2(), root, type, pred); } /** @@ -483,10 +492,19 @@ public static Iterable allItems(ItemGroup root, Class typ * @param type the type. * @param the type. * @return An {@link Iterable} for all items. + * @since TODO + */ + public static Iterable allItems2(Authentication authentication, ItemGroup root, Class type) { + return allItems2(authentication, root, type, t -> true); + } + + /** + * @deprecated use {@link #allItems2(Authentication, ItemGroup, Class)} * @since 2.37 */ - public static Iterable allItems(Authentication authentication, ItemGroup root, Class type) { - return allItems(authentication, root, type, t -> true); + @Deprecated + public static Iterable allItems(org.acegisecurity.Authentication authentication, ItemGroup root, Class type) { + return allItems2(authentication.toSpring(), root, type); } /** @@ -502,12 +520,21 @@ public static Iterable allItems(Authentication authenticatio * @param the type. * @param pred the predicate. * @return An {@link Iterable} for all items. - * @since 2.221 + * @since TODO */ - public static Iterable allItems(Authentication authentication, ItemGroup root, Class type, Predicate pred) { + public static Iterable allItems2(Authentication authentication, ItemGroup root, Class type, Predicate pred) { return new AllItemsIterable<>(root, authentication, type, pred); } + /** + * @deprecated use {@link #allItems2(Authentication, ItemGroup, Class, Predicate)} + * @since 2.221 + */ + @Deprecated + public static Iterable allItems(org.acegisecurity.Authentication authentication, ItemGroup root, Class type, Predicate pred) { + return allItems2(authentication.toSpring(), root, type, pred); + } + /** * Finds an item whose name (when referenced from the specified context) is closest to the given name. * @param the type of item being considered @@ -630,13 +657,13 @@ public boolean hasNext() { } ItemGroup group = stack.pop(); // group.getItems() is responsible for performing the permission check so we will not repeat it - if (Jenkins.getAuthentication() == authentication) { + if (Jenkins.getAuthentication2().equals(authentication)) { delegate = group.getItems(search).iterator(); } else { // slower path because the caller has switched authentication // we need to keep the original authentication so that allItems() can be used // like getAllItems() without the cost of building the entire list up front - try (ACLContext ctx = ACL.as(authentication)) { + try (ACLContext ctx = ACL.as2(authentication)) { delegate = group.getItems(search).iterator(); } } @@ -679,7 +706,7 @@ public T next() { */ static void verifyItemDoesNotAlreadyExist(@NonNull ItemGroup parent, @NonNull String newName, @CheckForNull Item variant) throws IllegalArgumentException, Failure { Item existing; - try (ACLContext ctxt = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctxt = ACL.as2(ACL.SYSTEM2)) { existing = parent.getItem(newName); } if (existing != null && existing != variant) { diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java index dfb01c973114..d2865ef9dcf6 100644 --- a/core/src/main/java/hudson/model/Label.java +++ b/core/src/main/java/hudson/model/Label.java @@ -410,7 +410,7 @@ public int getTiedJobCount() { // denormalize for performance // we don't need to respect security as much when returning a simple count - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { int result = 0; for (AbstractProject ignored : Jenkins.get().allItems(AbstractProject.class, p -> matches(p.getAssignedLabelString()))) { ++result; diff --git a/core/src/main/java/hudson/model/ListView.java b/core/src/main/java/hudson/model/ListView.java index 5aff6afbb7ae..b40df81887ac 100644 --- a/core/src/main/java/hudson/model/ListView.java +++ b/core/src/main/java/hudson/model/ListView.java @@ -51,10 +51,8 @@ import net.jcip.annotations.GuardedBy; import javax.servlet.ServletException; import jenkins.model.Jenkins; -import jenkins.model.ParameterizedJobMixIn; import net.sf.json.JSONObject; -import org.acegisecurity.AccessDeniedException; import org.jenkinsci.Symbol; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -67,6 +65,7 @@ import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.verb.POST; +import org.springframework.security.access.AccessDeniedException; /** * Displays {@link Job}s in a flat list view. @@ -532,7 +531,7 @@ public static List getDefaultColumns() { public static final class Listener extends ItemListener { @Override public void onLocationChanged(final Item item, final String oldFullName, final String newFullName) { - try (ACLContext acl = ACL.as(ACL.SYSTEM)) { + try (ACLContext acl = ACL.as2(ACL.SYSTEM2)) { locationChanged(oldFullName, newFullName); } } @@ -577,7 +576,7 @@ private void renameViewItem(String oldFullName, String newFullName, ViewGroup vg @Override public void onDeleted(final Item item) { - try (ACLContext acl = ACL.as(ACL.SYSTEM)) { + try (ACLContext acl = ACL.as2(ACL.SYSTEM2)) { deleted(item); } } diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java index d8417f9c8ce3..a0eb7a07499d 100644 --- a/core/src/main/java/hudson/model/Node.java +++ b/core/src/main/java/hudson/model/Node.java @@ -65,7 +65,6 @@ import jenkins.util.SystemProperties; import jenkins.util.io.OnMaster; import net.sf.json.JSONObject; -import org.acegisecurity.Authentication; import org.jvnet.localizer.Localizable; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.ProtectedExternally; @@ -74,6 +73,7 @@ import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; +import org.springframework.security.core.Authentication; /** * Base type of Jenkins agents (although in practice, you probably extend {@link Slave} to define a new agent type). @@ -398,8 +398,8 @@ public CauseOfBlockage canTake(Queue.BuildableItem item) { } } - Authentication identity = item.authenticate(); - if (!(SKIP_BUILD_CHECK_ON_FLYWEIGHTS && item.task instanceof Queue.FlyweightTask) && !hasPermission(identity, Computer.BUILD)) { + Authentication identity = item.authenticate2(); + if (!(SKIP_BUILD_CHECK_ON_FLYWEIGHTS && item.task instanceof Queue.FlyweightTask) && !hasPermission2(identity, Computer.BUILD)) { // doesn't have a permission return CauseOfBlockage.fromMessage(Messages._Node_LackingBuildPermission(identity.getName(), getDisplayName())); } diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index 317c5829ac1f..afdb68a5f11e 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -116,8 +116,6 @@ import jenkins.model.Jenkins; import jenkins.security.QueueItemAuthenticator; import jenkins.util.AtmostOneTaskExecutor; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.Authentication; import org.jenkinsci.bytecode.AdaptField; import org.jenkinsci.remoting.RoleChecker; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -139,6 +137,8 @@ import jenkins.model.queue.CompositeCauseOfBlockage; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.interceptor.RequirePOST; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; /** * Build queue. @@ -2000,13 +2000,26 @@ default Collection getSubTasks() { * When the task execution touches other objects inside Jenkins, the access control is performed * based on whether this {@link Authentication} is allowed to use them. * - * @return by default, {@link ACL#SYSTEM} - * @since 1.520 + * @return by default, {@link ACL#SYSTEM2} + * @since TODO * @see QueueItemAuthenticator * @see Tasks#getDefaultAuthenticationOf(Queue.Task) */ - default @NonNull Authentication getDefaultAuthentication() { - return ACL.SYSTEM; + default @NonNull Authentication getDefaultAuthentication2() { + if (Util.isOverridden(Queue.Task.class, getClass(), "getDefaultAuthentication")) { + return getDefaultAuthentication().toSpring(); + } else { + return ACL.SYSTEM2; + } + } + + /** + * @deprecated use {@link #getDefaultAuthentication2()} + * @since 1.520 + */ + @Deprecated + default @NonNull org.acegisecurity.Authentication getDefaultAuthentication() { + return org.acegisecurity.Authentication.fromSpring(getDefaultAuthentication2()); } /** @@ -2022,13 +2035,27 @@ default Collection getSubTasks() { * older versions of Jenkins may not have this method implemented. Called private method _getDefaultAuthenticationOf(Task) on {@link Tasks} * to avoid {@link AbstractMethodError}. * - * @since 1.592 + * @since TODO * @see QueueItemAuthenticator * @see Tasks#getDefaultAuthenticationOf(Queue.Task, Queue.Item) */ - default @NonNull Authentication getDefaultAuthentication(Queue.Item item) { - return getDefaultAuthentication(); + default @NonNull Authentication getDefaultAuthentication2(Queue.Item item) { + if (Util.isOverridden(Queue.Task.class, getClass(), "getDefaultAuthentication", Queue.Item.class)) { + return getDefaultAuthentication(item).toSpring(); + } else { + return getDefaultAuthentication2(); + } + } + + /** + * @deprecated use {@link #getDefaultAuthentication2(Queue.Item)} + * @since 1.592 + */ + @Deprecated + default @NonNull org.acegisecurity.Authentication getDefaultAuthentication(Queue.Item item) { + return org.acegisecurity.Authentication.fromSpring(getDefaultAuthentication2(item)); } + } /** @@ -2316,19 +2343,28 @@ public HttpResponse doCancelQueue() { * * When the task execution touches other objects inside Jenkins, the access control is performed * based on whether this {@link Authentication} is allowed to use them. Implementers, if you are unsure, - * return the identity of the user who queued the task, or {@link ACL#SYSTEM} to bypass the access control + * return the identity of the user who queued the task, or {@link ACL#SYSTEM2} to bypass the access control * and run as the super user. * - * @since 1.520 + * @since TODO */ @NonNull - public Authentication authenticate() { + public Authentication authenticate2() { for (QueueItemAuthenticator auth : QueueItemAuthenticatorProvider.authenticators()) { - Authentication a = auth.authenticate(this); + Authentication a = auth.authenticate2(this); if (a!=null) return a; } - return task.getDefaultAuthentication(this); + return task.getDefaultAuthentication2(this); + } + + /** + * @deprecated use {@link #authenticate2} + * @since 1.520 + */ + @Deprecated + public org.acegisecurity.Authentication authenticate() { + return org.acegisecurity.Authentication.fromSpring(authenticate2()); } @Restricted(DoNotUse.class) // only for Stapler export diff --git a/core/src/main/java/hudson/model/Run.java b/core/src/main/java/hudson/model/Run.java index d8ee74992094..825640440098 100644 --- a/core/src/main/java/hudson/model/Run.java +++ b/core/src/main/java/hudson/model/Run.java @@ -119,8 +119,6 @@ import jenkins.util.VirtualFile; import jenkins.util.io.OnMaster; import net.sf.json.JSONObject; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.Authentication; import org.apache.commons.io.IOUtils; import org.apache.commons.jelly.XMLOutput; import org.apache.commons.lang.ArrayUtils; @@ -136,6 +134,8 @@ import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.verb.POST; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; /** * A particular execution of {@link Job}. @@ -1888,12 +1888,12 @@ protected final void execute(@NonNull RunExecution job) { listener = createBuildListener(job, logger, charset); listener.started(getCauses()); - Authentication auth = Jenkins.getAuthentication(); - if (auth.equals(ACL.SYSTEM)) { + Authentication auth = Jenkins.getAuthentication2(); + if (auth.equals(ACL.SYSTEM2)) { listener.getLogger().println(Messages.Run_running_as_SYSTEM()); } else { String id = auth.getName(); - if (!auth.equals(Jenkins.ANONYMOUS)) { + if (!auth.equals(Jenkins.ANONYMOUS2)) { final User usr = User.getById(id, false); if (usr != null) { // Encode user hyperlink for existing users id = ModelHyperlinkNote.encodeTo(usr); @@ -2476,8 +2476,9 @@ public EnvVars getEnvironment() throws IOException, InterruptedException { * @param id as produced by {@link #getExternalizableId} * @return the same run, or null if the job or run was not found * @throws IllegalArgumentException if the ID is malformed + * @throws AccessDeniedException as per {@link ItemGroup#getItem} */ - public @CheckForNull static Run fromExternalizableId(String id) throws IllegalArgumentException { + public @CheckForNull static Run fromExternalizableId(String id) throws IllegalArgumentException, AccessDeniedException { int hash = id.lastIndexOf('#'); if (hash <= 0) { throw new IllegalArgumentException("Invalid id"); diff --git a/core/src/main/java/hudson/model/TaskAction.java b/core/src/main/java/hudson/model/TaskAction.java index d9c4a7a748da..76b045845cd4 100644 --- a/core/src/main/java/hudson/model/TaskAction.java +++ b/core/src/main/java/hudson/model/TaskAction.java @@ -62,7 +62,7 @@ public abstract class TaskAction extends AbstractModelObject implements Action { /** * Gets the permission object that represents the permission (against {@link #getACL}) to perform this task. - * Generally your implementation of {@link #getIconFileName} should return null if {@code !getACL().hasPermission(getPermission())}. + * Generally your implementation of {@link #getIconFileName} should return null if {@code !getACL().hasPermission2(getPermission())}. */ protected abstract Permission getPermission(); diff --git a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java index b7942df57991..9dac84b79505 100644 --- a/core/src/main/java/hudson/model/TopLevelItemDescriptor.java +++ b/core/src/main/java/hudson/model/TopLevelItemDescriptor.java @@ -26,7 +26,6 @@ import hudson.ExtensionList; import jenkins.model.Jenkins; import jenkins.model.item_category.ItemCategory; -import org.acegisecurity.AccessDeniedException; import org.apache.commons.jelly.Script; import org.apache.commons.jelly.XMLOutput; import org.apache.commons.lang.StringUtils; @@ -45,6 +44,7 @@ import java.io.StringWriter; import java.util.logging.Level; import java.util.logging.Logger; +import org.springframework.security.access.AccessDeniedException; /** * {@link Descriptor} for {@link TopLevelItem}s. diff --git a/core/src/main/java/hudson/model/UpdateCenter.java b/core/src/main/java/hudson/model/UpdateCenter.java index d217f1decd14..a296ae91575c 100644 --- a/core/src/main/java/hudson/model/UpdateCenter.java +++ b/core/src/main/java/hudson/model/UpdateCenter.java @@ -67,7 +67,6 @@ import jenkins.util.io.OnMaster; import net.sf.json.JSONObject; -import org.acegisecurity.Authentication; import org.apache.commons.io.input.CountingInputStream; import org.apache.commons.io.output.NullOutputStream; import org.jenkinsci.Symbol; @@ -125,6 +124,7 @@ import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.interceptor.RequirePOST; +import org.springframework.security.core.Authentication; /** @@ -702,7 +702,7 @@ private boolean checkMinVersion(@CheckForNull Plugin p, @CheckForNull VersionNum @RequirePOST public void doUpgrade(StaplerResponse rsp) throws IOException, ServletException { Jenkins.get().checkPermission(Jenkins.ADMINISTER); - HudsonUpgradeJob job = new HudsonUpgradeJob(getCoreSource(), Jenkins.getAuthentication()); + HudsonUpgradeJob job = new HudsonUpgradeJob(getCoreSource(), Jenkins.getAuthentication2()); if(!Lifecycle.get().canRewriteHudsonWar()) { sendError("Jenkins upgrade not supported in this running mode"); return; @@ -813,7 +813,7 @@ public void doDowngrade(StaplerResponse rsp) throws IOException, ServletExceptio return; } - HudsonDowngradeJob job = new HudsonDowngradeJob(getCoreSource(), Jenkins.getAuthentication()); + HudsonDowngradeJob job = new HudsonDowngradeJob(getCoreSource(), Jenkins.getAuthentication2()); LOGGER.info("Scheduling the core downgrade"); addJob(job); rsp.sendRedirect2("."); @@ -825,7 +825,7 @@ public void doDowngrade(StaplerResponse rsp) throws IOException, ServletExceptio @RequirePOST public void doRestart(StaplerResponse rsp) throws IOException, ServletException { Jenkins.get().checkPermission(Jenkins.ADMINISTER); - HudsonDowngradeJob job = new HudsonDowngradeJob(getCoreSource(), Jenkins.getAuthentication()); + HudsonDowngradeJob job = new HudsonDowngradeJob(getCoreSource(), Jenkins.getAuthentication2()); LOGGER.info("Scheduling the core downgrade"); addJob(job); @@ -1534,7 +1534,7 @@ public synchronized boolean cancel() { public RestartJenkinsJob(UpdateSite site) { super(site); - this.authentication = Jenkins.getAuthentication().getName(); + this.authentication = Jenkins.getAuthentication2().getName(); } public synchronized void run() { @@ -2155,7 +2155,7 @@ public void _run() throws IOException, InstallationStatus { // if this is a bundled plugin, make sure it won't get overwritten PluginWrapper pw = plugin.getInstalled(); if (pw!=null && pw.isBundled()) { - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { pw.doPin(); } } diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java index 4ba581611be2..3eeaa7867453 100644 --- a/core/src/main/java/hudson/model/UpdateSite.java +++ b/core/src/main/java/hudson/model/UpdateSite.java @@ -262,7 +262,7 @@ public FormValidation doVerifySignature() throws IOException { * @since 2.9 */ protected UpdateCenter.InstallationJob createInstallationJob(Plugin plugin, UpdateCenter uc, boolean dynamicLoad) { - return uc.new InstallationJob(plugin, this, Jenkins.getAuthentication(), dynamicLoad); + return uc.new InstallationJob(plugin, this, Jenkins.getAuthentication2(), dynamicLoad); } /** @@ -1598,7 +1598,7 @@ public Future deploy(boolean dynamicLoad, @CheckForNull UUID co public Future deployBackup() { Jenkins.get().checkPermission(Jenkins.ADMINISTER); UpdateCenter uc = Jenkins.get().getUpdateCenter(); - return uc.addJob(uc.new PluginDowngradeJob(this, UpdateSite.this, Jenkins.getAuthentication())); + return uc.addJob(uc.new PluginDowngradeJob(this, UpdateSite.this, Jenkins.getAuthentication2())); } /** * Making the installation web bound. diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index f9b5d576b447..d50e79c1dca1 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -26,6 +26,9 @@ import com.google.common.base.Predicate; import com.infradna.tool.bridge_method_injector.WithBridgeMethods; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import hudson.BulkChange; import hudson.CopyOnWrite; import hudson.Extension; @@ -40,12 +43,11 @@ import hudson.security.ACL; import hudson.security.AccessControlled; import hudson.security.SecurityRealm; -import hudson.security.UserMayOrMayNotExistException; +import hudson.security.UserMayOrMayNotExistException2; import hudson.util.FormApply; import hudson.util.FormValidation; import hudson.util.RunList; import hudson.util.XStream2; - import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -62,26 +64,16 @@ import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; - import jenkins.model.IdStrategy; import jenkins.model.Jenkins; import jenkins.model.ModelObjectWithContextMenu; -import jenkins.security.ImpersonatingUserDetailsService; +import jenkins.security.ImpersonatingUserDetailsService2; import jenkins.security.LastGrantedAuthoritiesProperty; import jenkins.security.UserDetailsCache; import jenkins.util.SystemProperties; import net.sf.json.JSONObject; -import org.acegisecurity.Authentication; -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; -import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.UsernameNotFoundException; import org.apache.commons.lang.StringUtils; import org.jenkinsci.Symbol; import org.kohsuke.accmod.Restricted; @@ -93,7 +85,13 @@ import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.verb.POST; -import org.springframework.dao.DataAccessException; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * Represents a user. @@ -363,10 +361,23 @@ public T getProperty(Class clazz) { * logged in. * * @throws UsernameNotFoundException If this user is not a valid user in the backend {@link SecurityRealm}. + * @since TODO + */ + public @NonNull Authentication impersonate2() throws UsernameNotFoundException { + return this.impersonate(this.getUserDetailsForImpersonation2()); + } + + /** + * @deprecated use {@link #impersonate2} * @since 1.419 */ - public @NonNull Authentication impersonate() throws UsernameNotFoundException { - return this.impersonate(this.getUserDetailsForImpersonation()); + @Deprecated + public @NonNull org.acegisecurity.Authentication impersonate() throws org.acegisecurity.userdetails.UsernameNotFoundException { + try { + return org.acegisecurity.Authentication.fromSpring(impersonate2()); + } catch (AuthenticationException x) { + throw org.acegisecurity.AuthenticationException.fromSpring(x); + } } /** @@ -377,17 +388,18 @@ public T getProperty(Class clazz) { * * @return userDetails for the user, in case he's not found but seems legitimate, we provide a userDetails with minimum access * @throws UsernameNotFoundException If this user is not a valid user in the backend {@link SecurityRealm}. + * @since TODO */ - public @NonNull UserDetails getUserDetailsForImpersonation() throws UsernameNotFoundException { - ImpersonatingUserDetailsService userDetailsService = new ImpersonatingUserDetailsService( - Jenkins.get().getSecurityRealm().getSecurityComponents().userDetails + public @NonNull UserDetails getUserDetailsForImpersonation2() throws UsernameNotFoundException { + ImpersonatingUserDetailsService2 userDetailsService = new ImpersonatingUserDetailsService2( + Jenkins.get().getSecurityRealm().getSecurityComponents().userDetails2 ); try { UserDetails userDetails = userDetailsService.loadUserByUsername(id); LOGGER.log(Level.FINE, "Impersonation of the user {0} was a success", id); return userDetails; - } catch (UserMayOrMayNotExistException e) { + } catch (UserMayOrMayNotExistException2 e) { LOGGER.log(Level.FINE, "The user {0} may or may not exist in the SecurityRealm, so we provide minimum access", id); } catch (UsernameNotFoundException e) { if (ALLOW_NON_EXISTENT_USER_TO_LOGIN) { @@ -396,23 +408,32 @@ public T getProperty(Class clazz) { LOGGER.log(Level.FINE, "The user {0} was not found in the SecurityRealm", id); throw e; } - } catch (DataAccessException e) { - // seems like it's in the same boat as UserMayOrMayNotExistException - LOGGER.log(Level.FINE, "The user {0} retrieval just threw a DataAccess exception with msg = {1}, so we provide minimum access", new Object[]{id, e.getMessage()}); } return new LegitimateButUnknownUserDetails(id); } + /** + * @deprecated use {@link #getUserDetailsForImpersonation2} + */ + @Deprecated + public @NonNull org.acegisecurity.userdetails.UserDetails getUserDetailsForImpersonation() throws org.acegisecurity.userdetails.UsernameNotFoundException { + try { + return org.acegisecurity.userdetails.UserDetails.fromSpring(getUserDetailsForImpersonation2()); + } catch (AuthenticationException x) { + throw org.acegisecurity.AuthenticationException.fromSpring(x); + } + } + /** * Only used for a legitimate user we have no idea about. We give it only minimum access */ - private static class LegitimateButUnknownUserDetails extends org.acegisecurity.userdetails.User { + private static class LegitimateButUnknownUserDetails extends org.springframework.security.core.userdetails.User { private LegitimateButUnknownUserDetails(String username) throws IllegalArgumentException { super( username, "", true, true, true, true, - new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY} + Collections.singleton(SecurityRealm.AUTHENTICATED_AUTHORITY2) ); } } @@ -420,8 +441,8 @@ private LegitimateButUnknownUserDetails(String username) throws IllegalArgumentE /** * Creates an {@link Authentication} object that represents this user using the given userDetails * - * @param userDetails Provided by {@link #getUserDetailsForImpersonation()}. - * @see #getUserDetailsForImpersonation() + * @param userDetails Provided by {@link #getUserDetailsForImpersonation2()}. + * @see #getUserDetailsForImpersonation2() */ @Restricted(NoExternalUse.class) public @NonNull Authentication impersonate(@NonNull UserDetails userDetails) { @@ -566,7 +587,7 @@ public void doSubmitDescription(StaplerRequest req, StaplerResponse rsp) throws * @since 1.172 */ public static @CheckForNull User current() { - return get(Jenkins.getAuthentication()); + return get2(Jenkins.getAuthentication2()); } /** @@ -575,9 +596,9 @@ public void doSubmitDescription(StaplerRequest req, StaplerResponse rsp) throws * * @param a the supplied {@link Authentication} . * @return a {@link User} object for the supplied {@link Authentication} or {@code null} - * @since 1.609 + * @since TODO */ - public static @CheckForNull User get(@CheckForNull Authentication a) { + public static @CheckForNull User get2(@CheckForNull Authentication a) { if (a == null || a instanceof AnonymousAuthenticationToken) return null; @@ -585,6 +606,15 @@ public void doSubmitDescription(StaplerRequest req, StaplerResponse rsp) throws return getById(a.getName(), true); } + /** + * @deprecated use {@link #get2(Authentication)} + * @since 1.609 + */ + @Deprecated + public static @CheckForNull User get(@CheckForNull org.acegisecurity.Authentication a) { + return get2(a != null ? a.toSpring() : null); + } + /** * Gets the {@link User} object by its {@code id} * @@ -859,7 +889,7 @@ public void doConfigSubmit(StaplerRequest req, StaplerResponse rsp) throws IOExc @RequirePOST public void doDoDelete(StaplerRequest req, StaplerResponse rsp) throws IOException { checkPermission(Jenkins.ADMINISTER); - if (idStrategy().equals(id, Jenkins.getAuthentication().getName())) { + if (idStrategy().equals(id, Jenkins.getAuthentication2().getName())) { rsp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Cannot delete self"); return; } @@ -898,8 +928,8 @@ public void doRssLatest(StaplerRequest req, StaplerResponse rsp) throws IOExcept public ACL getACL() { ACL base = Jenkins.get().getAuthorizationStrategy().getACL(this); // always allow a non-anonymous user full control of himself. - return ACL.lambda((a, permission) -> (idStrategy().equals(a.getName(), id) && !(a instanceof AnonymousAuthenticationToken)) - || base.hasPermission(a, permission)); + return ACL.lambda2((a, permission) -> (idStrategy().equals(a.getName(), id) && !(a instanceof AnonymousAuthenticationToken)) + || base.hasPermission2(a, permission)); } /** @@ -907,14 +937,14 @@ public ACL getACL() { */ public boolean canDelete() { final IdStrategy strategy = idStrategy(); - return hasPermission(Jenkins.ADMINISTER) && !strategy.equals(id, Jenkins.getAuthentication().getName()) + return hasPermission(Jenkins.ADMINISTER) && !strategy.equals(id, Jenkins.getAuthentication2().getName()) && UserIdMapper.getInstance().isMapped(id); } /** * Checks for authorities (groups) associated with this user. * If the caller lacks {@link Jenkins#ADMINISTER}, or any problems arise, returns an empty list. - * {@link SecurityRealm#AUTHENTICATED_AUTHORITY} and the username, if present, are omitted. + * {@link SecurityRealm#AUTHENTICATED_AUTHORITY2} and the username, if present, are omitted. * * @return a possibly empty list * @since 1.498 @@ -926,13 +956,13 @@ public boolean canDelete() { List r = new ArrayList<>(); Authentication authentication; try { - authentication = impersonate(); + authentication = impersonate2(); } catch (UsernameNotFoundException x) { LOGGER.log(Level.FINE, "cannot look up authorities for " + id, x); return Collections.emptyList(); } for (GrantedAuthority a : authentication.getAuthorities()) { - if (a.equals(SecurityRealm.AUTHENTICATED_AUTHORITY)) { + if (a.equals(SecurityRealm.AUTHENTICATED_AUTHORITY2)) { continue; } String n = a.getAuthority(); @@ -1212,7 +1242,7 @@ public String resolveCanonicalId(String idOrFullName, Map context) { return userDetails.getUsername(); } catch (UsernameNotFoundException x) { LOGGER.log(Level.FINE, "not sure whether " + idOrFullName + " is a valid username or not", x); - } catch (DataAccessException | ExecutionException x) { + } catch (ExecutionException x) { LOGGER.log(Level.FINE, "could not look up " + idOrFullName, x); } finally { resolving.set(false); diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index fde5d094c465..5b82acc1725a 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -1110,7 +1110,7 @@ public Categories doItemCategories(StaplerRequest req, StaplerResponse rsp, @Que } else { ctx = null; } - for (TopLevelItemDescriptor descriptor : DescriptorVisibilityFilter.apply(getOwner().getItemGroup(), Items.all(Jenkins.getAuthentication(), getOwner().getItemGroup()))) { + for (TopLevelItemDescriptor descriptor : DescriptorVisibilityFilter.apply(getOwner().getItemGroup(), Items.all2(Jenkins.getAuthentication2(), getOwner().getItemGroup()))) { ItemCategory ic = ItemCategory.getCategory(descriptor); Map metadata = new HashMap<>(); @@ -1291,7 +1291,7 @@ public static List allInstantiable() { } for (ViewDescriptor d : DescriptorVisibilityFilter.apply(owner, all())) { if (d.isApplicableIn(owner) && d.isInstantiable() - && owner.getACL().hasCreatePermission(Jenkins.getAuthentication(), owner, d)) { + && owner.getACL().hasCreatePermission2(Jenkins.getAuthentication2(), owner, d)) { r.add(d); } } diff --git a/core/src/main/java/hudson/model/listeners/ItemListener.java b/core/src/main/java/hudson/model/listeners/ItemListener.java index b87db51150dd..9f6a336cd7de 100644 --- a/core/src/main/java/hudson/model/listeners/ItemListener.java +++ b/core/src/main/java/hudson/model/listeners/ItemListener.java @@ -272,7 +272,7 @@ public static void fireLocationChange(final Item rootItem, final String oldFullN return null; }); if (rootItem instanceof ItemGroup) { - for (final Item child : Items.allItems(ACL.SYSTEM, (ItemGroup)rootItem, Item.class)) { + for (final Item child : Items.allItems2(ACL.SYSTEM2, (ItemGroup)rootItem, Item.class)) { final String childNew = child.getFullName(); assert childNew.startsWith(newFullName); assert childNew.charAt(newFullName.length()) == '/'; diff --git a/core/src/main/java/hudson/model/queue/MappingWorksheet.java b/core/src/main/java/hudson/model/queue/MappingWorksheet.java index d953b48fd1b7..00c42e3bae38 100644 --- a/core/src/main/java/hudson/model/queue/MappingWorksheet.java +++ b/core/src/main/java/hudson/model/queue/MappingWorksheet.java @@ -134,7 +134,7 @@ public boolean canAccept(WorkChunk c) { if (c.assignedLabel!=null && !c.assignedLabel.contains(node)) return false; // label mismatch - if (!(Node.SKIP_BUILD_CHECK_ON_FLYWEIGHTS && item.task instanceof Queue.FlyweightTask) && !nodeAcl.hasPermission(item.authenticate(), Computer.BUILD)) + if (!(Node.SKIP_BUILD_CHECK_ON_FLYWEIGHTS && item.task instanceof Queue.FlyweightTask) && !nodeAcl.hasPermission2(item.authenticate2(), Computer.BUILD)) return false; // tasks don't have a permission to run on this node return true; diff --git a/core/src/main/java/hudson/model/queue/Tasks.java b/core/src/main/java/hudson/model/queue/Tasks.java index c591fe494075..470cfb11d222 100644 --- a/core/src/main/java/hudson/model/queue/Tasks.java +++ b/core/src/main/java/hudson/model/queue/Tasks.java @@ -27,12 +27,11 @@ import hudson.model.Queue.Item; import hudson.model.Queue.Task; import edu.umd.cs.findbugs.annotations.CheckForNull; -import org.acegisecurity.Authentication; - import java.util.Collection; import edu.umd.cs.findbugs.annotations.NonNull; import jenkins.security.QueueItemAuthenticator; import jenkins.security.QueueItemAuthenticatorProvider; +import org.springframework.security.core.Authentication; /** * Convenience methods around {@link Task} and {@link SubTask}. @@ -80,36 +79,46 @@ public static hudson.model.Item getItemOf(@NonNull SubTask t) { return p instanceof hudson.model.Item ? (hudson.model.Item)p : null; } - /** @deprecated call {@link Task#getDefaultAuthentication()} directly */ + /** @deprecated call {@link Queue.Task#getDefaultAuthentication()} directly */ @Deprecated @NonNull - public static Authentication getDefaultAuthenticationOf(Task t) { + public static org.acegisecurity.Authentication getDefaultAuthenticationOf(Task t) { return t.getDefaultAuthentication(); } - /** @deprecated call {@link Task#getDefaultAuthentication(Item)} directly */ + /** @deprecated call {@link Queue.Task#getDefaultAuthentication(Item)} directly */ @Deprecated @NonNull - public static Authentication getDefaultAuthenticationOf(Task t, Item item) { + public static org.acegisecurity.Authentication getDefaultAuthenticationOf(Task t, Item item) { return t.getDefaultAuthentication(item); } /** * Finds what authentication a task is likely to be run under when scheduled. - * The actual authentication after scheduling ({@link hudson.model.Queue.Item#authenticate}) might differ, - * in case some {@link QueueItemAuthenticator#authenticate(hudson.model.Queue.Item)} takes (for example) actions into consideration. + * The actual authentication after scheduling ({@link hudson.model.Queue.Item#authenticate2}) might differ, + * in case some {@link QueueItemAuthenticator#authenticate2(hudson.model.Queue.Item)} takes (for example) actions into consideration. * @param t a task - * @return an authentication as specified by some {@link QueueItemAuthenticator#authenticate(hudson.model.Queue.Task)}; else {@link Task#getDefaultAuthentication()} - * @since 1.560 + * @return an authentication as specified by some {@link QueueItemAuthenticator#authenticate2(hudson.model.Queue.Task)}; else {@link Task#getDefaultAuthentication2()} + * @since TODO */ - public static @NonNull Authentication getAuthenticationOf(@NonNull Task t) { + public static @NonNull Authentication getAuthenticationOf2(@NonNull Task t) { for (QueueItemAuthenticator qia : QueueItemAuthenticatorProvider.authenticators()) { - Authentication a = qia.authenticate(t); + Authentication a = qia.authenticate2(t); if (a != null) { return a; } } - return t.getDefaultAuthentication(); + return t.getDefaultAuthentication2(); + } + + /** + * @deprecated use {@link #getAuthenticationOf2} + * @since 1.560 + */ + @Deprecated + public static @NonNull org.acegisecurity.Authentication getAuthenticationOf(@NonNull Task t) { + return org.acegisecurity.Authentication.fromSpring(getAuthenticationOf2(t)); } + } diff --git a/core/src/main/java/hudson/security/ACL.java b/core/src/main/java/hudson/security/ACL.java index 963fd398b7ee..e1412e5809f6 100644 --- a/core/src/main/java/hudson/security/ACL.java +++ b/core/src/main/java/hudson/security/ACL.java @@ -23,34 +23,34 @@ */ package hudson.security; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Util; +import hudson.model.Item; +import hudson.model.ItemGroup; +import hudson.model.TopLevelItemDescriptor; import hudson.model.User; import hudson.model.View; import hudson.model.ViewDescriptor; import hudson.model.ViewGroup; +import hudson.remoting.Callable; import java.util.LinkedHashSet; import java.util.Set; -import java.util.stream.Collectors; -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; - -import hudson.model.Item; -import hudson.remoting.Callable; -import hudson.model.ItemGroup; -import hudson.model.TopLevelItemDescriptor; import java.util.function.BiFunction; -import jenkins.security.NonSerializableSecurityContext; +import java.util.stream.Collectors; import jenkins.model.Jenkins; +import jenkins.security.NonSerializableSecurityContext; import jenkins.security.NotReallyRoleSensitiveCallable; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.Authentication; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; import org.acegisecurity.acls.sid.PrincipalSid; import org.acegisecurity.acls.sid.Sid; -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; /** * Gate-keeper that controls access to Hudson's model objects. @@ -68,15 +68,15 @@ public abstract class ACL { * if the user doesn't have the permission. */ public final void checkPermission(@NonNull Permission p) { - Authentication a = Jenkins.getAuthentication(); - if (a == SYSTEM) { + Authentication a = Jenkins.getAuthentication2(); + if (a.equals(SYSTEM2)) { return; } - if (!hasPermission(a,p)) { + if (!hasPermission2(a,p)) { while (!p.enabled && p.impliedBy != null) { p = p.impliedBy; } - throw new AccessDeniedException2(a,p); + throw new AccessDeniedException3(a,p); } } @@ -99,7 +99,7 @@ public final void checkAnyPermission(@NonNull Permission... permissions) { boolean failed = !hasAnyPermission(permissions); - Authentication authentication = Jenkins.getAuthentication(); + Authentication authentication = Jenkins.getAuthentication2(); if (failed) { // we know that none of the permissions are granted Set enabledPermissions = new LinkedHashSet<>(); for (Permission p : permissions) { @@ -130,11 +130,11 @@ public final void checkAnyPermission(@NonNull Permission... permissions) { * if the user doesn't have the permission. */ public final boolean hasPermission(@NonNull Permission p) { - Authentication a = Jenkins.getAuthentication(); - if (a == SYSTEM) { + Authentication a = Jenkins.getAuthentication2(); + if (a.equals(SYSTEM2)) { return true; } - return hasPermission(a, p); + return hasPermission2(a, p); } /** @@ -151,8 +151,8 @@ public final boolean hasAnyPermission(@NonNull Permission... permissions) { throw new IllegalArgumentException("At least one permission must be provided"); } - Authentication a = Jenkins.getAuthentication(); - if (a == SYSTEM) { + Authentication a = Jenkins.getAuthentication2(); + if (a.equals(SYSTEM2)) { return true; } @@ -168,21 +168,50 @@ public final boolean hasAnyPermission(@NonNull Permission... permissions) { * Checks if the given principle has the given permission. * *

- * Note that {@link #SYSTEM} can be passed in as the authentication parameter, + * Note that {@link #SYSTEM2} can be passed in as the authentication parameter, * in which case you should probably just assume it has every permission. + * @since TODO */ - public abstract boolean hasPermission(@NonNull Authentication a, @NonNull Permission permission); + public boolean hasPermission2(@NonNull Authentication a, @NonNull Permission permission) { + if (Util.isOverridden(ACL.class, getClass(), "hasPermission", org.acegisecurity.Authentication.class, Permission.class)) { + return hasPermission(org.acegisecurity.Authentication.fromSpring(a), permission); + } else { + throw new AbstractMethodError("implement hasPermission2"); + } + } + + /** + * @deprecated use {@link #hasPermission2} + */ + @Deprecated + public boolean hasPermission(@NonNull org.acegisecurity.Authentication a, @NonNull Permission permission) { + return hasPermission2(a.toSpring(), permission); + } /** * Creates a simple {@link ACL} implementation based on a “single-abstract-method” easily implemented via lambda syntax. - * @param impl the implementation of {@link ACL#hasPermission(Authentication, Permission)} + * @param impl the implementation of {@link ACL#hasPermission2(Authentication, Permission)} * @return an adapter to that lambda + * @since TODO + */ + public static ACL lambda2(final BiFunction impl) { + return new ACL() { + @Override + public boolean hasPermission2(Authentication a, Permission permission) { + return impl.apply(a, permission); + } + }; + } + + /** + * @deprecated use {@link #lambda2} * @since 2.105 */ - public static ACL lambda(final BiFunction impl) { + @Deprecated + public static ACL lambda(final BiFunction impl) { return new ACL() { @Override - public boolean hasPermission(Authentication a, Permission permission) { + public boolean hasPermission(org.acegisecurity.Authentication a, Permission permission) { return impl.apply(a, permission); } }; @@ -201,11 +230,11 @@ public boolean hasPermission(Authentication a, Permission permission) { */ public final void checkCreatePermission(@NonNull ItemGroup c, @NonNull TopLevelItemDescriptor d) { - Authentication a = Jenkins.getAuthentication(); - if (a == SYSTEM) { + Authentication a = Jenkins.getAuthentication2(); + if (a.equals(SYSTEM2)) { return; } - if (!hasCreatePermission(a, c, d)) { + if (!hasCreatePermission2(a, c, d)) { throw new AccessDeniedException(Messages.AccessDeniedException2_MissingPermission(a.getName(), Item.CREATE.group.title+"/"+Item.CREATE.name + Item.CREATE + "/" + d.getDisplayName())); } @@ -213,18 +242,32 @@ public final void checkCreatePermission(@NonNull ItemGroup c, /** * Checks if the given principal has the permission to create top level items within the specified item group. *

- * Note that {@link #SYSTEM} can be passed in as the authentication parameter, + * Note that {@link #SYSTEM2} can be passed in as the authentication parameter, * in which case you should probably just assume it can create anything anywhere. * @param a the principal. * @param c the container of the item. * @param d the descriptor of the item to be created. * @return false * if the user doesn't have the permission. + * @since TODO + */ + public boolean hasCreatePermission2(@NonNull Authentication a, @NonNull ItemGroup c, + @NonNull TopLevelItemDescriptor d) { + if (Util.isOverridden(ACL.class, getClass(), "hasCreatePermission", org.acegisecurity.Authentication.class, ItemGroup.class, TopLevelItemDescriptor.class)) { + return hasCreatePermission(org.acegisecurity.Authentication.fromSpring(a), c, d); + } else { + return true; + } + } + + /** + * @deprecated use {@link #hasCreatePermission2(Authentication, ItemGroup, TopLevelItemDescriptor)} * @since 1.607 */ - public boolean hasCreatePermission(@NonNull Authentication a, @NonNull ItemGroup c, + @Deprecated + public boolean hasCreatePermission(@NonNull org.acegisecurity.Authentication a, @NonNull ItemGroup c, @NonNull TopLevelItemDescriptor d) { - return true; + return hasCreatePermission2(a.toSpring(), c, d); } /** @@ -239,11 +282,11 @@ public boolean hasCreatePermission(@NonNull Authentication a, @NonNull ItemGroup */ public final void checkCreatePermission(@NonNull ViewGroup c, @NonNull ViewDescriptor d) { - Authentication a = Jenkins.getAuthentication(); - if (a == SYSTEM) { + Authentication a = Jenkins.getAuthentication2(); + if (a.equals(SYSTEM2)) { return; } - if (!hasCreatePermission(a, c, d)) { + if (!hasCreatePermission2(a, c, d)) { throw new AccessDeniedException(Messages.AccessDeniedException2_MissingPermission(a.getName(), View.CREATE.group.title + "/" + View.CREATE.name + View.CREATE + "/" + d.getDisplayName())); } @@ -252,18 +295,32 @@ public final void checkCreatePermission(@NonNull ViewGroup c, /** * Checks if the given principal has the permission to create views within the specified view group. *

- * Note that {@link #SYSTEM} can be passed in as the authentication parameter, + * Note that {@link #SYSTEM2} can be passed in as the authentication parameter, * in which case you should probably just assume it can create anything anywhere. * @param a the principal. * @param c the container of the view. * @param d the descriptor of the view to be created. * @return false * if the user doesn't have the permission. + * @since TODO + */ + public boolean hasCreatePermission2(@NonNull Authentication a, @NonNull ViewGroup c, + @NonNull ViewDescriptor d) { + if (Util.isOverridden(ACL.class, getClass(), "hasCreatePermission", org.acegisecurity.Authentication.class, ViewGroup.class, ViewDescriptor.class)) { + return hasCreatePermission(org.acegisecurity.Authentication.fromSpring(a), c, d); + } else { + return true; + } + } + + /** + * @deprecated use {@link #hasCreatePermission2(Authentication, ItemGroup, TopLevelItemDescriptor)} * @since 2.37 */ - public boolean hasCreatePermission(@NonNull Authentication a, @NonNull ViewGroup c, + @Deprecated + public boolean hasCreatePermission(@NonNull org.acegisecurity.Authentication a, @NonNull ViewGroup c, @NonNull ViewDescriptor d) { - return true; + return hasCreatePermission2(a.toSpring(), c, d); } // @@ -311,8 +368,15 @@ public String toString() { *

* This is used when Hudson is performing computation for itself, instead * of acting on behalf of an user, such as doing builds. + * @since TODO */ - public static final Authentication SYSTEM = new UsernamePasswordAuthenticationToken(SYSTEM_USERNAME,"SYSTEM"); + public static final Authentication SYSTEM2 = new UsernamePasswordAuthenticationToken(SYSTEM_USERNAME,"SYSTEM"); + + /** + * @deprecated use {@link #SYSTEM2} + */ + @Deprecated + public static final org.acegisecurity.Authentication SYSTEM = new org.acegisecurity.providers.UsernamePasswordAuthenticationToken((UsernamePasswordAuthenticationToken) SYSTEM2); /** * Changes the {@link Authentication} associated with the current thread @@ -321,31 +385,40 @@ public String toString() { *

* When the impersonation is over, be sure to restore the previous authentication * via {@code SecurityContextHolder.setContext(returnValueFromThisMethod)}; - * or just use {@link #impersonate(Authentication,Runnable)}. + * or just use {@link #impersonate2(Authentication, Runnable)}. * *

* We need to create a new {@link SecurityContext} instead of {@link SecurityContext#setAuthentication(Authentication)} * because the same {@link SecurityContext} object is reused for all the concurrent requests from the same session. - * @since 1.462 - * @deprecated use try with resources and {@link #as(Authentication)} + * @since TODO + * @deprecated use try with resources and {@link #as2(Authentication)} */ @Deprecated - public static @NonNull SecurityContext impersonate(@NonNull Authentication auth) { + public static @NonNull SecurityContext impersonate2(@NonNull Authentication auth) { SecurityContext old = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(new NonSerializableSecurityContext(auth)); return old; } /** - * Safer variant of {@link #impersonate(Authentication)} that does not require a finally-block. - * @param auth authentication, such as {@link #SYSTEM} + * @deprecated use {@link #impersonate2(Authentication)} + * @since 1.462 + */ + @Deprecated + public static @NonNull org.acegisecurity.context.SecurityContext impersonate(@NonNull org.acegisecurity.Authentication auth) { + return org.acegisecurity.context.SecurityContext.fromSpring(impersonate2(auth.toSpring())); + } + + /** + * Safer variant of {@link #impersonate2(Authentication)} that does not require a finally-block. + * @param auth authentication, such as {@link #SYSTEM2} * @param body an action to run with this alternate authentication in effect - * @since 1.509 - * @deprecated use try with resources and {@link #as(Authentication)} + * @since TODO + * @deprecated use try with resources and {@link #as2(Authentication)} */ @Deprecated - public static void impersonate(@NonNull Authentication auth, @NonNull Runnable body) { - SecurityContext old = impersonate(auth); + public static void impersonate2(@NonNull Authentication auth, @NonNull Runnable body) { + SecurityContext old = impersonate2(auth); try { body.run(); } finally { @@ -354,15 +427,24 @@ public static void impersonate(@NonNull Authentication auth, @NonNull Runnable b } /** - * Safer variant of {@link #impersonate(Authentication)} that does not require a finally-block. - * @param auth authentication, such as {@link #SYSTEM} + * @deprecated use {@link #impersonate2(Authentication, Runnable)} + * @since 1.509 + */ + @Deprecated + public static void impersonate(@NonNull org.acegisecurity.Authentication auth, @NonNull Runnable body) { + impersonate2(auth.toSpring(), body); + } + + /** + * Safer variant of {@link #impersonate2(Authentication)} that does not require a finally-block. + * @param auth authentication, such as {@link #SYSTEM2} * @param body an action to run with this alternate authentication in effect (try {@link NotReallyRoleSensitiveCallable}) - * @since 1.587 - * @deprecated use try with resources and {@link #as(Authentication)} + * @since TODO + * @deprecated use try with resources and {@link #as2(Authentication)} */ @Deprecated - public static V impersonate(Authentication auth, Callable body) throws T { - SecurityContext old = impersonate(auth); + public static V impersonate2(Authentication auth, Callable body) throws T { + SecurityContext old = impersonate2(auth); try { return body.call(); } finally { @@ -370,6 +452,15 @@ public static V impersonate(Authentication auth, Callabl } } + /** + * @deprecated use {@link #impersonate2(Authentication, Callable)} + * @since 1.587 + */ + @Deprecated + public static V impersonate(org.acegisecurity.Authentication auth, Callable body) throws T { + return impersonate2(auth.toSpring(), body); + } + /** * Changes the {@link Authentication} associated with the current thread to the specified one and returns an * {@link AutoCloseable} that restores the previous security context. @@ -377,21 +468,31 @@ public static V impersonate(Authentication auth, Callabl *

* This makes impersonation much easier within code as it can now be used using the try with resources construct: *

-     *     try (ACLContext ctx = ACL.as(auth)) {
+     *     try (ACLContext ctx = ACL.as2(auth)) {
      *        ...
      *     }
      * 
* @param auth the new authentication. * @return the previous authentication context - * @since 2.14 + * @since TODO */ @NonNull - public static ACLContext as(@NonNull Authentication auth) { + public static ACLContext as2(@NonNull Authentication auth) { final ACLContext context = new ACLContext(SecurityContextHolder.getContext()); SecurityContextHolder.setContext(new NonSerializableSecurityContext(auth)); return context; } + /** + * @deprecated use {@link #as2(Authentication)} + * @since 2.14 + */ + @Deprecated + @NonNull + public static ACLContext as(@NonNull org.acegisecurity.Authentication auth) { + return as2(auth.toSpring()); + } + /** * Changes the {@link Authentication} associated with the current thread to the specified one and returns an * {@link AutoCloseable} that restores the previous security context. @@ -399,7 +500,7 @@ public static ACLContext as(@NonNull Authentication auth) { *

* This makes impersonation much easier within code as it can now be used using the try with resources construct: *

-     *     try (ACLContext ctx = ACL.as(auth)) {
+     *     try (ACLContext ctx = ACL.as2(auth)) {
      *        ...
      *     }
      * 
@@ -410,16 +511,26 @@ public static ACLContext as(@NonNull Authentication auth) { */ @NonNull public static ACLContext as(@CheckForNull User user) { - return as(user == null ? Jenkins.ANONYMOUS : user.impersonate()); + return as2(user == null ? Jenkins.ANONYMOUS2 : user.impersonate2()); } /** * Checks if the given authentication is anonymous by checking its class. - * @see Jenkins#ANONYMOUS + * @see Jenkins#ANONYMOUS2 * @see AnonymousAuthenticationToken + * @since TODO */ - public static boolean isAnonymous(@NonNull Authentication authentication) { + public static boolean isAnonymous2(@NonNull Authentication authentication) { //TODO use AuthenticationTrustResolver instead to be consistent through the application return authentication instanceof AnonymousAuthenticationToken; } + + /** + * @deprecated use {@link #isAnonymous2} + */ + @Deprecated + public static boolean isAnonymous(@NonNull org.acegisecurity.Authentication authentication) { + return isAnonymous2(authentication.toSpring()); + } + } diff --git a/core/src/main/java/hudson/security/ACLContext.java b/core/src/main/java/hudson/security/ACLContext.java index 2f1b4e207d6b..f68bde46104f 100644 --- a/core/src/main/java/hudson/security/ACLContext.java +++ b/core/src/main/java/hudson/security/ACLContext.java @@ -24,9 +24,9 @@ package hudson.security; import edu.umd.cs.findbugs.annotations.NonNull; -import org.acegisecurity.Authentication; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; /** * A {@link AutoCloseable} that captures the previous {@link SecurityContext} and restores it on {@link #close()} @@ -52,12 +52,21 @@ public class ACLContext implements AutoCloseable { /** * Accessor for the previous context. * @return the previous context. + * @since TODO */ @NonNull - public SecurityContext getPreviousContext() { + public SecurityContext getPreviousContext2() { return previousContext; } + /** + * @deprecated use {@link #getPreviousContext2} + */ + @Deprecated + public org.acegisecurity.context.SecurityContext getPreviousContext() { + return org.acegisecurity.context.SecurityContext.fromSpring(getPreviousContext2()); + } + @Override public void close() { SecurityContextHolder.setContext(previousContext); diff --git a/core/src/main/java/hudson/security/AbstractPasswordBasedSecurityRealm.java b/core/src/main/java/hudson/security/AbstractPasswordBasedSecurityRealm.java index fa727202729a..13049d8424ae 100644 --- a/core/src/main/java/hudson/security/AbstractPasswordBasedSecurityRealm.java +++ b/core/src/main/java/hudson/security/AbstractPasswordBasedSecurityRealm.java @@ -1,25 +1,24 @@ package hudson.security; -import groovy.lang.Binding; -import hudson.util.spring.BeanBuilder; +import hudson.Util; import jenkins.model.Jenkins; -import jenkins.security.ImpersonatingUserDetailsService; +import jenkins.security.ImpersonatingUserDetailsService2; import jenkins.security.SecurityListener; -import org.acegisecurity.AuthenticationException; -import org.acegisecurity.AuthenticationManager; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider; -import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.UserDetailsService; -import org.acegisecurity.userdetails.UsernameNotFoundException; -import org.springframework.dao.DataAccessException; -import org.springframework.web.context.WebApplicationContext; +import org.springframework.security.authentication.AnonymousAuthenticationProvider; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.RememberMeAuthenticationProvider; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * Partial implementation of {@link SecurityRealm} for username/password based authentication. * This is a convenience base class if all you are trying to do is to check the given username * and password with the information stored in somewhere else, and you don't want to do anything - * with Acegi. + * with Spring Security. * *

* This {@link SecurityRealm} uses the standard login form (and a few other optional mechanisms like BASIC auth) @@ -28,18 +27,21 @@ * @author Kohsuke Kawaguchi * @since 1.317 */ -public abstract class AbstractPasswordBasedSecurityRealm extends SecurityRealm implements UserDetailsService { +public abstract class AbstractPasswordBasedSecurityRealm extends SecurityRealm { @Override public SecurityComponents createSecurityComponents() { - Binding binding = new Binding(); - binding.setVariable("authenticator", new Authenticator()); - - BeanBuilder builder = new BeanBuilder(); - builder.parse(Jenkins.get().servletContext.getResourceAsStream("/WEB-INF/security/AbstractPasswordBasedSecurityRealm.groovy"),binding); - WebApplicationContext context = builder.createApplicationContext(); + // this does all the hard work. + Authenticator authenticator = new Authenticator(); + // these providers apply everywhere + RememberMeAuthenticationProvider rmap = new RememberMeAuthenticationProvider(Jenkins.get().getSecretKey()); + // this doesn't mean we allow anonymous access. + // we just authenticate2 anonymous users as such, + // so that later authorization can reject them if so configured + AnonymousAuthenticationProvider aap = new AnonymousAuthenticationProvider("anonymous"); + AuthenticationManager authenticationManager = new ProviderManager(authenticator, rmap, aap); return new SecurityComponents( - findBean(AuthenticationManager.class, context), - new ImpersonatingUserDetailsService(this)); + authenticationManager, + new ImpersonatingUserDetailsService2(this::loadUserByUsername2)); } /** @@ -48,7 +50,7 @@ public SecurityComponents createSecurityComponents() { * *

* If the user name and the password pair matches, retrieve the information about this user and - * return it as a {@link UserDetails} object. {@link org.acegisecurity.userdetails.User} is a convenient + * return it as a {@link UserDetails} object. {@link org.springframework.security.core.userdetails.User} is a convenient * implementation to use, but if your backend offers additional data, you may want to use your own subtype * so that the rest of Hudson can use those additional information (such as e-mail address --- see * MailAddressResolver.) @@ -64,13 +66,36 @@ public SecurityComponents createSecurityComponents() { *

* If the user name and the password pair doesn't match, throw {@link AuthenticationException} to reject the login * attempt. + * @since TODO */ - protected abstract UserDetails authenticate(String username, String password) throws AuthenticationException; + protected UserDetails authenticate2(String username, String password) throws AuthenticationException { + if (Util.isOverridden(AbstractPasswordBasedSecurityRealm.class, getClass(), "authenticate", String.class, String.class)) { + try { + return authenticate(username, password).toSpring(); + } catch (org.acegisecurity.AcegiSecurityException x) { + throw x.toSpring(); + } + } else { + throw new AbstractMethodError("Implement authenticate2"); + } + } + + /** + * @deprecated use {@link #authenticate2} + */ + @Deprecated + protected org.acegisecurity.userdetails.UserDetails authenticate(String username, String password) throws org.acegisecurity.AuthenticationException { + try { + return org.acegisecurity.userdetails.UserDetails.fromSpring(authenticate2(username, password)); + } catch (AuthenticationException x) { + throw org.acegisecurity.AuthenticationException.fromSpring(x); + } + } private UserDetails doAuthenticate(String username, String password) throws AuthenticationException { try { - UserDetails user = authenticate(username, password); - SecurityListener.fireAuthenticated(user); + UserDetails user = authenticate2(username, password); + SecurityListener.fireAuthenticated2(user); return user; } catch (AuthenticationException x) { SecurityListener.fireFailedToAuthenticate(username); @@ -87,15 +112,65 @@ private UserDetails doAuthenticate(String username, String password) throws Auth * a query like this, just always throw {@link UsernameNotFoundException}. */ @Override - public abstract UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException; + public UserDetails loadUserByUsername2(String username) throws UsernameNotFoundException { + if (Util.isOverridden(AbstractPasswordBasedSecurityRealm.class, getClass(), "loadUserByUsername", String.class)) { + try { + return loadUserByUsername(username).toSpring(); + } catch (org.acegisecurity.AcegiSecurityException x) { + throw x.toSpring(); + } catch (org.springframework.dao.DataAccessException x) { + throw x.toSpring(); + } + } else { + throw new AbstractMethodError("Implement loadUserByUsername2"); + } + } + + /** + * @deprecated use {@link #loadUserByUsername2} + */ + @Deprecated + @Override + public org.acegisecurity.userdetails.UserDetails loadUserByUsername(String username) throws org.acegisecurity.userdetails.UsernameNotFoundException, org.springframework.dao.DataAccessException { + try { + return org.acegisecurity.userdetails.UserDetails.fromSpring(loadUserByUsername2(username)); + } catch (AuthenticationException x) { + throw org.acegisecurity.AuthenticationException.fromSpring(x); + } + } /** * Retrieves information about a group by its name. * - * This method is the group version of the {@link #loadUserByUsername(String)}. + * This method is the group version of the {@link #loadUserByUsername2(String)}. */ @Override - public abstract GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException; + public GroupDetails loadGroupByGroupname2(String groupname, boolean fetchMembers) throws UsernameNotFoundException { + if (Util.isOverridden(AbstractPasswordBasedSecurityRealm.class, getClass(), "loadGroupByGroupname", String.class)) { + try { + return loadGroupByGroupname(groupname); + } catch (org.acegisecurity.AcegiSecurityException x) { + throw x.toSpring(); + } catch (org.springframework.dao.DataAccessException x) { + throw x.toSpring(); + } + } else { + throw new AbstractMethodError("Implement loadGroupByGroupname2"); + } + } + + /** + * @deprecated use {@link #loadGroupByGroupname2} + */ + @Deprecated + @Override + public GroupDetails loadGroupByGroupname(String groupname) throws org.acegisecurity.userdetails.UsernameNotFoundException, org.springframework.dao.DataAccessException { + try { + return loadGroupByGroupname2(groupname, false); + } catch (AuthenticationException x) { + throw org.acegisecurity.AuthenticationException.fromSpring(x); + } + } class Authenticator extends AbstractUserDetailsAuthenticationProvider { protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { diff --git a/core/src/main/java/hudson/security/AccessControlled.java b/core/src/main/java/hudson/security/AccessControlled.java index 5e12debb3c3a..694744996be2 100644 --- a/core/src/main/java/hudson/security/AccessControlled.java +++ b/core/src/main/java/hudson/security/AccessControlled.java @@ -24,8 +24,8 @@ package hudson.security; import edu.umd.cs.findbugs.annotations.NonNull; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.Authentication; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; /** * Object that has an {@link ACL} @@ -75,14 +75,23 @@ default boolean hasAnyPermission(@NonNull Permission... permission) { } /** - * Convenient short-cut for {@code getACL().hasPermission(a, permission)} - * @since 2.92 + * Convenient short-cut for {@code getACL().hasPermission2(a, permission)} + * @since TODO */ - default boolean hasPermission(@NonNull Authentication a, @NonNull Permission permission) { - if (a == ACL.SYSTEM) { + default boolean hasPermission2(@NonNull Authentication a, @NonNull Permission permission) { + if (a.equals(ACL.SYSTEM2)) { return true; } - return getACL().hasPermission(a, permission); + return getACL().hasPermission2(a, permission); + } + + /** + * @deprecated use {@link #hasPermission2} + * @since 2.92 + */ + @Deprecated + default boolean hasPermission(@NonNull org.acegisecurity.Authentication a, @NonNull Permission permission) { + return hasPermission2(a.toSpring(), permission); } } diff --git a/core/src/main/java/hudson/security/AccessDeniedException2.java b/core/src/main/java/hudson/security/AccessDeniedException2.java index 7b39396bf667..ed6e5f49769e 100644 --- a/core/src/main/java/hudson/security/AccessDeniedException2.java +++ b/core/src/main/java/hudson/security/AccessDeniedException2.java @@ -2,7 +2,6 @@ import org.acegisecurity.AccessDeniedException; import org.acegisecurity.Authentication; -import org.acegisecurity.GrantedAuthority; import javax.servlet.http.HttpServletResponse; import java.io.PrintWriter; @@ -11,7 +10,9 @@ /** * {@link AccessDeniedException} with more information. * @author Kohsuke Kawaguchi + * @deprecated use {@link AccessDeniedException3} */ +@Deprecated public class AccessDeniedException2 extends AccessDeniedException { /** If true, report {@code X-You-Are-In-Group} headers. Disabled due to JENKINS-39402; use {@code /whoAmI} etc. to diagnose permission issues. */ @@ -42,18 +43,7 @@ public AccessDeniedException2(Throwable t, Authentication authentication, Permis * Reports the details of the access failure in HTTP headers to assist diagnosis. */ public void reportAsHeaders(HttpServletResponse rsp) { - rsp.addHeader("X-You-Are-Authenticated-As",authentication.getName()); - if (REPORT_GROUP_HEADERS) { - for (GrantedAuthority auth : authentication.getAuthorities()) { - rsp.addHeader("X-You-Are-In-Group",auth.getAuthority()); - } - } else { - rsp.addHeader("X-You-Are-In-Group-Disabled", "JENKINS-39402: use -Dhudson.security.AccessDeniedException2.REPORT_GROUP_HEADERS=true or use /whoAmI to diagnose"); - } - rsp.addHeader("X-Required-Permission", permission.getId()); - for (Permission p=permission.impliedBy; p!=null; p=p.impliedBy) { - rsp.addHeader("X-Permission-Implied-By", p.getId()); - } + toSpring().reportAsHeaders(rsp); } /** @@ -62,15 +52,12 @@ public void reportAsHeaders(HttpServletResponse rsp) { * but instead of using HTTP headers, this version is meant to go inside the payload. */ public void report(PrintWriter w) { - w.println("You are authenticated as: "+authentication.getName()); - w.println("Groups that you are in:"); - for (GrantedAuthority auth : authentication.getAuthorities()) { - w.println(" "+auth.getAuthority()); - } + toSpring().report(w); + } - w.println("Permission you need to have (but didn't): "+permission.getId()); - for (Permission p=permission.impliedBy; p!=null; p=p.impliedBy) { - w.println(" ... which is implied by: "+p.getId()); - } + @Override + public AccessDeniedException3 toSpring() { + Throwable t = getCause(); + return t != null ? new AccessDeniedException3(t, authentication.toSpring(), permission) : new AccessDeniedException3(authentication.toSpring(), permission); } } diff --git a/core/src/main/java/hudson/security/AccessDeniedException3.java b/core/src/main/java/hudson/security/AccessDeniedException3.java new file mode 100644 index 000000000000..64323bf5bc1e --- /dev/null +++ b/core/src/main/java/hudson/security/AccessDeniedException3.java @@ -0,0 +1,77 @@ +package hudson.security; + +import javax.servlet.http.HttpServletResponse; +import java.io.PrintWriter; +import jenkins.util.SystemProperties; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +/** + * {@link AccessDeniedException} with more information. + * @author Kohsuke Kawaguchi + * @since TODO + */ +public class AccessDeniedException3 extends AccessDeniedException { + + /** If true, report {@code X-You-Are-In-Group} headers. Disabled due to JENKINS-39402; use {@code /whoAmI} etc. to diagnose permission issues. */ + @SuppressWarnings("deprecation") + private static /* not final */ boolean REPORT_GROUP_HEADERS = SystemProperties.getBoolean(AccessDeniedException2.class.getName() + ".REPORT_GROUP_HEADERS"); + + /** + * This object represents the user being authenticated. + */ + public final Authentication authentication; + + /** + * This object represents the permission that the user needed. + */ + public final Permission permission; + + public AccessDeniedException3(Authentication authentication, Permission permission) { + this(null,authentication,permission); + } + + public AccessDeniedException3(Throwable t, Authentication authentication, Permission permission) { + super(Messages.AccessDeniedException2_MissingPermission(authentication.getName(), + permission.group.title+"/"+permission.name), t); + this.authentication = authentication; + this.permission = permission; + } + + /** + * Reports the details of the access failure in HTTP headers to assist diagnosis. + */ + public void reportAsHeaders(HttpServletResponse rsp) { + rsp.addHeader("X-You-Are-Authenticated-As",authentication.getName()); + if (REPORT_GROUP_HEADERS) { + for (GrantedAuthority auth : authentication.getAuthorities()) { + rsp.addHeader("X-You-Are-In-Group",auth.getAuthority()); + } + } else { + rsp.addHeader("X-You-Are-In-Group-Disabled", "JENKINS-39402: use -Dhudson.security.AccessDeniedException2.REPORT_GROUP_HEADERS=true or use /whoAmI to diagnose"); + } + rsp.addHeader("X-Required-Permission", permission.getId()); + for (Permission p=permission.impliedBy; p!=null; p=p.impliedBy) { + rsp.addHeader("X-Permission-Implied-By", p.getId()); + } + } + + /** + * Reports the details of the access failure. + * This method is similar to {@link #reportAsHeaders(HttpServletResponse)} for the intention + * but instead of using HTTP headers, this version is meant to go inside the payload. + */ + public void report(PrintWriter w) { + w.println("You are authenticated as: "+authentication.getName()); + w.println("Groups that you are in:"); + for (GrantedAuthority auth : authentication.getAuthorities()) { + w.println(" "+auth.getAuthority()); + } + + w.println("Permission you need to have (but didn't): "+permission.getId()); + for (Permission p=permission.impliedBy; p!=null; p=p.impliedBy) { + w.println(" ... which is implied by: "+p.getId()); + } + } +} diff --git a/core/src/main/java/hudson/security/AccessDeniedHandlerImpl.java b/core/src/main/java/hudson/security/AccessDeniedHandlerImpl.java index b784a3d37d01..92c45b0f9fe9 100644 --- a/core/src/main/java/hudson/security/AccessDeniedHandlerImpl.java +++ b/core/src/main/java/hudson/security/AccessDeniedHandlerImpl.java @@ -23,17 +23,16 @@ */ package hudson.security; -import jenkins.model.Jenkins; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.ui.AccessDeniedHandler; -import org.kohsuke.stapler.WebApp; - +import java.io.IOException; import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; +import jenkins.model.Jenkins; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.WebApp; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; /** * Handles {@link AccessDeniedException} happened during request processing. @@ -41,16 +40,16 @@ * * @author Kohsuke Kawaguchi */ +@Restricted(NoExternalUse.class) public class AccessDeniedHandlerImpl implements AccessDeniedHandler { - public void handle(ServletRequest request, ServletResponse response, AccessDeniedException cause) throws IOException, ServletException { - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse rsp = (HttpServletResponse) response; + @Override + public void handle(HttpServletRequest req, HttpServletResponse rsp, AccessDeniedException cause) throws IOException, ServletException { rsp.setStatus(HttpServletResponse.SC_FORBIDDEN); req.setAttribute("exception",cause); - if (cause instanceof AccessDeniedException2) { - ((AccessDeniedException2)cause).reportAsHeaders(rsp); + if (cause instanceof AccessDeniedException3) { + ((AccessDeniedException3)cause).reportAsHeaders(rsp); } WebApp.get(Jenkins.get().servletContext).getSomeStapler() diff --git a/core/src/main/java/hudson/security/AuthenticationManagerProxy.java b/core/src/main/java/hudson/security/AuthenticationManagerProxy.java index 717f07dc77c2..2b5b4de7c83d 100644 --- a/core/src/main/java/hudson/security/AuthenticationManagerProxy.java +++ b/core/src/main/java/hudson/security/AuthenticationManagerProxy.java @@ -23,10 +23,12 @@ */ package hudson.security; -import org.acegisecurity.AuthenticationManager; -import org.acegisecurity.Authentication; -import org.acegisecurity.AuthenticationException; -import org.acegisecurity.DisabledException; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; /** * {@link AuthenticationManager} proxy that delegates to another instance. @@ -38,6 +40,7 @@ * * @author Kohsuke Kawaguchi */ +@Restricted(NoExternalUse.class) public class AuthenticationManagerProxy implements AuthenticationManager { private volatile AuthenticationManager delegate; diff --git a/core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java b/core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java index e1a281b48214..71be533d7750 100644 --- a/core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java +++ b/core/src/main/java/hudson/security/AuthenticationProcessingFilter2.java @@ -23,33 +23,46 @@ */ package hudson.security; -import java.util.Properties; -import java.util.logging.Logger; -import java.util.logging.Level; +import hudson.model.User; import java.io.IOException; - +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; - -import hudson.Util; -import hudson.model.User; import jenkins.security.SecurityListener; import jenkins.security.seed.UserSeedProperty; -import org.acegisecurity.Authentication; -import org.acegisecurity.AuthenticationException; -import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; /** - * {@link AuthenticationProcessingFilter} with a change for Jenkins so that + * Login filter with a change for Jenkins so that * we can pick up the hidden "from" form field defined in {@code login.jelly} * to send the user back to where he came from, after a successful authentication. * * @author Kohsuke Kawaguchi */ -public class AuthenticationProcessingFilter2 extends AuthenticationProcessingFilter { +@Restricted(NoExternalUse.class) +public final class AuthenticationProcessingFilter2 extends UsernamePasswordAuthenticationFilter { + + public AuthenticationProcessingFilter2(String authenticationGatewayUrl) { + setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/" + authenticationGatewayUrl, "POST")); + // Jenkins/login.jelly & SetupWizard/authenticate-security-token.jelly + setUsernameParameter("j_username"); + setPasswordParameter("j_password"); + } + + /* TODO none of this compiles against Spring Security; rewrite (try InteractiveAuthenticationSuccessEvent & SimpleUrlAuthenticationFailureHandler): + @Override protected String determineTargetUrl(HttpServletRequest request) { + AbstractAuthenticationProcessingFilter f = new UsernamePasswordAuthenticationFilter(); String targetUrl = request.getParameter("from"); request.getSession().setAttribute("from", targetUrl); @@ -70,8 +83,8 @@ protected String determineTargetUrl(HttpServletRequest request) { } /** - * @see org.acegisecurity.ui.AbstractProcessingFilter#determineFailureUrl(javax.servlet.http.HttpServletRequest, org.acegisecurity.AuthenticationException) - */ + * @see AbstractProcessingFilter#determineFailureUrl(HttpServletRequest, AuthenticationException) + * / @Override protected String determineFailureUrl(HttpServletRequest request, AuthenticationException failed) { Properties excMap = getExceptionMappings(); @@ -80,16 +93,19 @@ protected String determineFailureUrl(HttpServletRequest request, AuthenticationE request.getSession().setAttribute("from", whereFrom); return excMap.getProperty(failedClassName, getAuthenticationFailureUrl()); } + */ @Override - protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException { - super.onSuccessfulAuthentication(request,response,authResult); + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { + super.successfulAuthentication(request, response, chain, authResult); // make sure we have a session to store this successful authentication, given that we no longer // let HttpSessionContextIntegrationFilter2 to create sessions. - // HttpSessionContextIntegrationFilter stores the updated SecurityContext object into this session later + // SecurityContextPersistenceFilter stores the updated SecurityContext object into this session later // (either when a redirect is issued, via its HttpResponseWrapper, or when the execution returns to its // doFilter method. + /* TODO causes an ISE on the next line: request.getSession().invalidate(); + */ HttpSession newSession = request.getSession(); if (!UserSeedProperty.DISABLE_USER_SEED) { @@ -100,7 +116,7 @@ protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServle newSession.setAttribute(UserSeedProperty.USER_SESSION_SEED, sessionSeed); } - // as the request comes from Acegi redirect, that's not a Stapler one + // as the request comes from Spring Security redirect, that's not a Stapler one // thus it's not possible to retrieve it in the SecurityListener in that case // for that reason we need to keep the above code that apply quite the same logic as UserSeedSecurityListener SecurityListener.fireLoggedIn(authResult.getName()); @@ -108,18 +124,17 @@ protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServle /** * Leave the information about login failure. - * - *

- * Otherwise it seems like Acegi doesn't really leave the detail of the failure anywhere. */ @Override - protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException { - super.onUnsuccessfulAuthentication(request, response, failed); + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { + super.unsuccessfulAuthentication(request, response, failed); LOGGER.log(Level.FINE, "Login attempt failed", failed); + /* TODO this information appears to have been deliberately removed from Spring Security: Authentication auth = failed.getAuthentication(); if (auth != null) { SecurityListener.fireFailedToLogIn(auth.getName()); } + */ } private static final Logger LOGGER = Logger.getLogger(AuthenticationProcessingFilter2.class.getName()); diff --git a/core/src/main/java/hudson/security/AuthorizationStrategy.java b/core/src/main/java/hudson/security/AuthorizationStrategy.java index 3d63272d0f07..66cab7d0b716 100644 --- a/core/src/main/java/hudson/security/AuthorizationStrategy.java +++ b/core/src/main/java/hudson/security/AuthorizationStrategy.java @@ -94,12 +94,12 @@ public abstract class AuthorizationStrategy extends AbstractDescribableImpl { + return ACL.lambda2((a, permission) -> { ACL base = item.getOwner().getACL(); - boolean hasPermission = base.hasPermission(a, permission); + boolean hasPermission = base.hasPermission2(a, permission); if (!hasPermission && permission == View.READ) { - return base.hasPermission(a,View.CONFIGURE) || !item.getItems().isEmpty(); + return base.hasPermission2(a,View.CONFIGURE) || !item.getItems().isEmpty(); } return hasPermission; @@ -221,7 +221,7 @@ private Object readResolve() { return Collections.emptySet(); } - private static final ACL UNSECURED_ACL = ACL.lambda((a, p) -> true); + private static final ACL UNSECURED_ACL = ACL.lambda2((a, p) -> true); @Extension @Symbol("unsecured") public static final class DescriptorImpl extends Descriptor { diff --git a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java index 2c1c379d7d6f..c7eee8cd5db3 100644 --- a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java +++ b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java @@ -25,16 +25,9 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.User; -import jenkins.model.Jenkins; import hudson.util.Scrambler; -import jenkins.security.SecurityListener; -import org.acegisecurity.Authentication; -import jenkins.security.BasicApiTokenHelper; -import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.userdetails.UserDetails; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; - +import java.io.IOException; +import java.net.URLEncoder; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -45,8 +38,14 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.net.URLEncoder; +import jenkins.model.Jenkins; +import jenkins.security.BasicApiTokenHelper; +import jenkins.security.SecurityListener; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; /** * Implements the dual authentication mechanism. @@ -109,7 +108,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha // normal requests, or security not enabled if(req.getUserPrincipal()!=null) { // before we route this request, integrate the container authentication - // to Acegi. For anonymous users that doesn't have user principal, + // to Spring Security. For anonymous users that doesn't have user principal, // AnonymousProcessingFilter that follows this should create // an Authentication object. SecurityContextHolder.getContext().setAuthentication(new ContainerAuthentication(req)); @@ -141,10 +140,10 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha { User u = BasicApiTokenHelper.isConnectingUsingApiToken(username, password); if(u != null){ - UserDetails userDetails = u.getUserDetailsForImpersonation(); + UserDetails userDetails = u.getUserDetailsForImpersonation2(); Authentication auth = u.impersonate(userDetails); - SecurityListener.fireAuthenticated(userDetails); + SecurityListener.fireAuthenticated2(userDetails); SecurityContextHolder.getContext().setAuthentication(auth); try { diff --git a/core/src/main/java/hudson/security/ChainedServletFilter.java b/core/src/main/java/hudson/security/ChainedServletFilter.java index a9a0e129074f..4c8805f9c83e 100644 --- a/core/src/main/java/hudson/security/ChainedServletFilter.java +++ b/core/src/main/java/hudson/security/ChainedServletFilter.java @@ -28,6 +28,7 @@ import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -35,6 +36,8 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; /** * Servlet {@link Filter} that chains multiple {@link Filter}s. @@ -70,24 +73,41 @@ public void init(FilterConfig filterConfig) throws ServletException { f.init(filterConfig); } + private static final Pattern UNINTERESTING_URIS = Pattern.compile("/(images|jsbundles|css|scripts|adjuncts)/|/favicon[.]ico|/ajax"); + @Override public void doFilter(ServletRequest request, ServletResponse response, final FilterChain chain) throws IOException, ServletException { - LOGGER.entering(ChainedServletFilter.class.getName(), "doFilter"); + String uri = request instanceof HttpServletRequest ? ((HttpServletRequest) request).getRequestURI() : "?"; + Level level = UNINTERESTING_URIS.matcher(uri).find() ? Level.FINER : Level.FINE; + LOGGER.log(level, () -> "starting filter on " + uri); new FilterChain() { private int position=0; // capture the array for thread-safety private final Filter[] filters = ChainedServletFilter.this.filters; + @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if(position==filters.length) { - // reached to the end + LOGGER.log(level, () -> uri + " end: " + status()); chain.doFilter(request,response); } else { - // call next - filters[position++].doFilter(request,response,this); + Filter next = filters[position++]; + try { + LOGGER.log(level, () -> uri + " @" + position + " " + next + " »"); + next.doFilter(request,response,this); + LOGGER.log(level, () -> uri + " @" + position + " " + next + " « success: " + status()); + } catch (IOException | ServletException | RuntimeException x) { + LOGGER.log(level, () -> uri + " @" + position + " " + next + " « " + x + ": " + status()); + throw x; + } } } + + private int status() { + return response instanceof HttpServletResponse ? ((HttpServletResponse) response).getStatus() : 0; + } }.doFilter(request,response); + } public void destroy() { diff --git a/core/src/main/java/hudson/security/ContainerAuthentication.java b/core/src/main/java/hudson/security/ContainerAuthentication.java index ea6f404bdeb4..16b6952b61a6 100644 --- a/core/src/main/java/hudson/security/ContainerAuthentication.java +++ b/core/src/main/java/hudson/security/ContainerAuthentication.java @@ -24,29 +24,33 @@ package hudson.security; import jenkins.model.Jenkins; -import org.acegisecurity.Authentication; -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.GrantedAuthorityImpl; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import java.security.Principal; import java.util.List; import java.util.ArrayList; +import java.util.Collection; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; /** * {@link Authentication} implementation for {@link Principal} * given through {@link HttpServletRequest}. * *

- * This is used to plug the container authentication to Acegi, + * This is used to plug the container authentication to Spring Security, * for backward compatibility with Hudson < 1.160. * * @author Kohsuke Kawaguchi */ +@Restricted(NoExternalUse.class) public final class ContainerAuthentication implements Authentication { private final Principal principal; - private GrantedAuthority[] authorities; + private Collection authorities; /** * Servlet container can tie a {@link ServletRequest} to the request handling thread, @@ -63,13 +67,13 @@ public ContainerAuthentication(HttpServletRequest request) { List l = new ArrayList<>(); for( String g : Jenkins.get().getAuthorizationStrategy().getGroups()) { if(request.isUserInRole(g)) - l.add(new GrantedAuthorityImpl(g)); + l.add(new SimpleGrantedAuthority(g)); } - l.add(SecurityRealm.AUTHENTICATED_AUTHORITY); - authorities = l.toArray(new GrantedAuthority[0]); + l.add(SecurityRealm.AUTHENTICATED_AUTHORITY2); + authorities = l; } - public GrantedAuthority[] getAuthorities() { + public Collection getAuthorities() { return authorities; } diff --git a/core/src/main/java/hudson/security/DeferredCreationLdapAuthoritiesPopulator.java b/core/src/main/java/hudson/security/DeferredCreationLdapAuthoritiesPopulator.java deleted file mode 100644 index ac3a228b57fd..000000000000 --- a/core/src/main/java/hudson/security/DeferredCreationLdapAuthoritiesPopulator.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi - * - * 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 hudson.security; - -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.ldap.InitialDirContextFactory; -import org.acegisecurity.ldap.LdapDataAccessException; -import org.acegisecurity.providers.ldap.LdapAuthoritiesPopulator; -import org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator; -import org.acegisecurity.userdetails.ldap.LdapUserDetails; -import hudson.security.SecurityRealm.SecurityComponents; - -/** - * Implementation of {@link LdapAuthoritiesPopulator} that defers creation of a - * {@link DefaultLdapAuthoritiesPopulator} until one is needed. This is done to - * ensure that the groupSearchBase property can be set. - * - * @author justinedelson - * @deprecated as of 1.280 - * {@link SecurityComponents} are now created after {@link SecurityRealm} is created, so - * the initialization order issue that this code was trying to address no longer exists. - */ -@Deprecated -public class DeferredCreationLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator { - - /** - * A default role which will be assigned to all authenticated users if set. - */ - private String defaultRole = null; - - /** - * An initial context factory is only required if searching for groups is - * required. - */ - private InitialDirContextFactory initialDirContextFactory = null; - - /** - * Controls used to determine whether group searches should be performed - * over the full sub-tree from the base DN. - */ - private boolean searchSubtree = false; - - /** - * The ID of the attribute which contains the role name for a group - */ - private String groupRoleAttribute = "cn"; - - /** - * The base DN from which the search for group membership should be - * performed - */ - private String groupSearchBase = null; - - /** - * The pattern to be used for the user search. {0} is the user's DN - */ - private String groupSearchFilter = "(| (member={0}) (uniqueMember={0}) (memberUid={0}))"; - - private String rolePrefix = "ROLE_"; - - private boolean convertToUpperCase = true; - - /** - * Constructor. - * - * @param initialDirContextFactory - * supplies the contexts used to search for user roles. - * @param groupSearchBase - * if this is an empty string the search will be performed from - * the root DN of the context factory. - */ - public DeferredCreationLdapAuthoritiesPopulator( - InitialDirContextFactory initialDirContextFactory, String groupSearchBase) { - this.setInitialDirContextFactory(initialDirContextFactory); - this.setGroupSearchBase(groupSearchBase); - } - - public GrantedAuthority[] getGrantedAuthorities(LdapUserDetails userDetails) - throws LdapDataAccessException { - return create().getGrantedAuthorities(userDetails); - } - - public void setConvertToUpperCase(boolean convertToUpperCase) { - this.convertToUpperCase = convertToUpperCase; - } - - public void setDefaultRole(String defaultRole) { - this.defaultRole = defaultRole; - } - - public void setGroupRoleAttribute(String groupRoleAttribute) { - this.groupRoleAttribute = groupRoleAttribute; - } - - public void setGroupSearchBase(String groupSearchBase) { - this.groupSearchBase = groupSearchBase; - } - - public void setGroupSearchFilter(String groupSearchFilter) { - this.groupSearchFilter = groupSearchFilter; - } - - public void setInitialDirContextFactory(InitialDirContextFactory initialDirContextFactory) { - this.initialDirContextFactory = initialDirContextFactory; - } - - public void setRolePrefix(String rolePrefix) { - this.rolePrefix = rolePrefix; - } - - public void setSearchSubtree(boolean searchSubtree) { - this.searchSubtree = searchSubtree; - } - - /** - * Create a new DefaultLdapAuthoritiesPopulator object. - * - * @return a DefaultLdapAuthoritiesPopulator. - */ - private DefaultLdapAuthoritiesPopulator create() { - DefaultLdapAuthoritiesPopulator populator = new DefaultLdapAuthoritiesPopulator( - initialDirContextFactory, groupSearchBase); - populator.setConvertToUpperCase(convertToUpperCase); - if (defaultRole != null) { - populator.setDefaultRole(defaultRole); - } - populator.setGroupRoleAttribute(groupRoleAttribute); - populator.setGroupSearchFilter(groupSearchFilter); - populator.setRolePrefix(rolePrefix); - populator.setSearchSubtree(searchSubtree); - return populator; - } - -} diff --git a/core/src/main/java/hudson/security/FederatedLoginService.java b/core/src/main/java/hudson/security/FederatedLoginService.java index e87b30af68ed..0d7567668a5c 100644 --- a/core/src/main/java/hudson/security/FederatedLoginService.java +++ b/core/src/main/java/hudson/security/FederatedLoginService.java @@ -23,23 +23,22 @@ */ package hudson.security; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.ExtensionList; import hudson.ExtensionPoint; -import jenkins.model.Jenkins; import hudson.model.User; import hudson.model.UserProperty; -import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.userdetails.UserDetails; +import java.io.IOException; +import java.io.Serializable; +import javax.servlet.ServletException; +import jenkins.model.Jenkins; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; - -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; -import javax.servlet.ServletException; -import java.io.IOException; -import java.io.Serializable; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; /** * Abstraction for a login mechanism through external authenticator/identity provider @@ -189,7 +188,7 @@ public User signin() throws UnclaimedIdentityException { User u = locateUser(); if (u!=null) { // login as this user - UserDetails d = Jenkins.get().getSecurityRealm().loadUserByUsername(u.getId()); + UserDetails d = Jenkins.get().getSecurityRealm().loadUserByUsername2(u.getId()); UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(d,"",d.getAuthorities()); token.setDetails(d); diff --git a/core/src/main/java/hudson/security/GroupDetails.java b/core/src/main/java/hudson/security/GroupDetails.java index e83aaa531dc5..543074ef0e3a 100644 --- a/core/src/main/java/hudson/security/GroupDetails.java +++ b/core/src/main/java/hudson/security/GroupDetails.java @@ -23,9 +23,8 @@ */ package hudson.security; -import org.acegisecurity.userdetails.UserDetails; - import java.util.Set; +import org.springframework.security.core.userdetails.UserDetails; /** * Represents the details of a group. diff --git a/core/src/main/java/hudson/security/HttpSessionContextIntegrationFilter2.java b/core/src/main/java/hudson/security/HttpSessionContextIntegrationFilter2.java index aafc93af3bfa..35baa6659f03 100644 --- a/core/src/main/java/hudson/security/HttpSessionContextIntegrationFilter2.java +++ b/core/src/main/java/hudson/security/HttpSessionContextIntegrationFilter2.java @@ -24,13 +24,7 @@ package hudson.security; import hudson.model.User; -import jenkins.security.NonSerializableSecurityContext; import jenkins.security.seed.UserSeedProperty; -import org.acegisecurity.context.HttpSessionContextIntegrationFilter; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.Authentication; -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; - import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -38,27 +32,28 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.io.IOException; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.context.SecurityContextPersistenceFilter; +import org.springframework.security.web.context.SecurityContextRepository; -/** - * Erases the {@link SecurityContext} persisted in {@link HttpSession} - * if {@link InvalidatableUserDetails#isInvalid()} returns true. - * - * @see InvalidatableUserDetails - */ -public class HttpSessionContextIntegrationFilter2 extends HttpSessionContextIntegrationFilter { - public HttpSessionContextIntegrationFilter2() throws ServletException { - setContext(NonSerializableSecurityContext.class); +public class HttpSessionContextIntegrationFilter2 extends SecurityContextPersistenceFilter { + public HttpSessionContextIntegrationFilter2(SecurityContextRepository securityContextRepository) { + super(securityContextRepository); } + @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpSession session = ((HttpServletRequest) req).getSession(false); if (session != null) { - SecurityContext o = (SecurityContext) session.getAttribute(ACEGI_SECURITY_CONTEXT_KEY); + SecurityContext o = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); if (o != null) { Authentication a = o.getAuthentication(); if (a != null) { - if (isAuthInvalidated(a) || hasInvalidSessionSeed(a, session)) { - session.setAttribute(ACEGI_SECURITY_CONTEXT_KEY, null); + if (hasInvalidSessionSeed(a, session)) { + session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, null); } } } @@ -67,18 +62,6 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) super.doFilter(req, res, chain); } - private boolean isAuthInvalidated(Authentication authentication) { - if (authentication.getPrincipal() instanceof InvalidatableUserDetails) { - InvalidatableUserDetails ud = (InvalidatableUserDetails) authentication.getPrincipal(); - if (ud.isInvalid()) { - // don't let Acegi see invalid security context - return true; - } - } - - return false; - } - private boolean hasInvalidSessionSeed(Authentication authentication, HttpSession session) { if (UserSeedProperty.DISABLE_USER_SEED || authentication instanceof AnonymousAuthenticationToken) { return false; diff --git a/core/src/main/java/hudson/security/HudsonAuthenticationEntryPoint.java b/core/src/main/java/hudson/security/HudsonAuthenticationEntryPoint.java index 43798635ad74..c31be3b62fec 100644 --- a/core/src/main/java/hudson/security/HudsonAuthenticationEntryPoint.java +++ b/core/src/main/java/hudson/security/HudsonAuthenticationEntryPoint.java @@ -23,26 +23,23 @@ */ package hudson.security; +import com.google.common.base.Strings; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Functions; - -import com.google.common.base.Strings; -import org.acegisecurity.AuthenticationException; -import org.acegisecurity.InsufficientAuthenticationException; -import org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint; - -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; - import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.URLEncoder; import java.text.MessageFormat; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.authentication.InsufficientAuthenticationException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; /** * For anonymous requests to pages that require authentication, @@ -59,12 +56,17 @@ * * @author Kohsuke Kawaguchi */ -public class HudsonAuthenticationEntryPoint extends AuthenticationProcessingFilterEntryPoint { - @Override - public void commence(ServletRequest request, ServletResponse response, AuthenticationException reason) throws IOException, ServletException { - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse rsp = (HttpServletResponse) response; +@Restricted(NoExternalUse.class) +public class HudsonAuthenticationEntryPoint implements AuthenticationEntryPoint { + private final String loginFormUrl; + + public HudsonAuthenticationEntryPoint(String loginFormUrl) { + this.loginFormUrl = loginFormUrl; + } + + @Override + public void commence(HttpServletRequest req, HttpServletResponse rsp, AuthenticationException reason) throws IOException, ServletException { String requestedWith = req.getHeader("X-Requested-With"); if("XMLHttpRequest".equals(requestedWith)) { // container authentication normally relies on session attribute to @@ -77,7 +79,7 @@ public void commence(ServletRequest request, ServletResponse response, Authentic // give the opportunity to include the target URL String uriFrom = req.getRequestURI(); if(!Strings.isNullOrEmpty(req.getQueryString())) uriFrom += "?" + req.getQueryString(); - String loginForm = req.getContextPath()+getLoginFormUrl(); + String loginForm = req.getContextPath() + loginFormUrl; loginForm = MessageFormat.format(loginForm, URLEncoder.encode(uriFrom,"UTF-8")); req.setAttribute("loginForm", loginForm); @@ -86,11 +88,11 @@ public void commence(ServletRequest request, ServletResponse response, Authentic Functions.advertiseHeaders(rsp); - AccessDeniedException2 cause = null; + AccessDeniedException3 cause = null; // report the diagnosis information if possible if (reason instanceof InsufficientAuthenticationException) { - if (reason.getCause() instanceof AccessDeniedException2) { - cause = (AccessDeniedException2) reason.getCause(); + if (reason.getCause() instanceof AccessDeniedException3) { + cause = (AccessDeniedException3) reason.getCause(); cause.reportAsHeaders(rsp); } } diff --git a/core/src/main/java/hudson/security/HudsonFilter.java b/core/src/main/java/hudson/security/HudsonFilter.java index b37b2ea4ea4e..a291a795e870 100644 --- a/core/src/main/java/hudson/security/HudsonFilter.java +++ b/core/src/main/java/hudson/security/HudsonFilter.java @@ -23,12 +23,9 @@ */ package hudson.security; -import jenkins.model.Jenkins; - import java.io.IOException; -import java.util.logging.Logger; import static java.util.logging.Level.SEVERE; - +import java.util.logging.Logger; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -37,10 +34,10 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; - -import org.acegisecurity.AuthenticationManager; -import org.acegisecurity.ui.rememberme.RememberMeServices; -import org.acegisecurity.userdetails.UserDetailsService; +import jenkins.model.Jenkins; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.RememberMeServices; /** * {@link Filter} that Jenkins uses to implement security support. @@ -67,7 +64,7 @@ public class HudsonFilter implements Filter { private FilterConfig filterConfig; /** - * {@link AuthenticationManager} proxy so that the acegi filter chain can stay the same + * {@link AuthenticationManager} proxy so that the Spring Security filter chain can stay the same * even when security setting is reconfigured. * * @deprecated in 1.271. @@ -78,7 +75,7 @@ public class HudsonFilter implements Filter { public static final AuthenticationManagerProxy AUTHENTICATION_MANAGER = new AuthenticationManagerProxy(); /** - * {@link UserDetailsService} proxy so that the acegi filter chain can stay the same + * {@link UserDetailsService} proxy so that the Spring Security filter chain can stay the same * even when security setting is reconfigured. * * @deprecated in 1.271. @@ -89,7 +86,7 @@ public class HudsonFilter implements Filter { public static final UserDetailsServiceProxy USER_DETAILS_SERVICE_PROXY = new UserDetailsServiceProxy(); /** - * {@link RememberMeServices} proxy so that the acegi filter chain can stay the same + * {@link RememberMeServices} proxy so that the Spring Security filter chain can stay the same * even when security setting is reconfigured. * * @deprecated in 1.271. @@ -136,9 +133,9 @@ public static HudsonFilter get(ServletContext context) { public void reset(SecurityRealm securityRealm) throws ServletException { if (securityRealm != null) { SecurityRealm.SecurityComponents sc = securityRealm.getSecurityComponents(); - AUTHENTICATION_MANAGER.setDelegate(sc.manager); - USER_DETAILS_SERVICE_PROXY.setDelegate(sc.userDetails); - REMEMBER_ME_SERVICES_PROXY.setDelegate(sc.rememberMe); + AUTHENTICATION_MANAGER.setDelegate(sc.manager2); + USER_DETAILS_SERVICE_PROXY.setDelegate(sc.userDetails2); + REMEMBER_ME_SERVICES_PROXY.setDelegate(sc.rememberMe2); // make sure this.filter is always a valid filter. Filter oldf = this.filter; Filter newf = securityRealm.createFilter(this.filterConfig); diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java index fcdfcf03cddf..463e20f051ac 100644 --- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java +++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java @@ -47,16 +47,6 @@ import jenkins.util.SystemProperties; import jenkins.security.seed.UserSeedProperty; import net.sf.json.JSONObject; -import org.acegisecurity.Authentication; -import org.acegisecurity.AuthenticationException; -import org.acegisecurity.BadCredentialsException; -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.providers.encoding.PasswordEncoder; -import org.acegisecurity.providers.encoding.ShaPasswordEncoder; -import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.UsernameNotFoundException; import org.jenkinsci.Symbol; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.ForwardToView; @@ -68,7 +58,6 @@ import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.interceptor.RequirePOST; import org.mindrot.jbcrypt.BCrypt; -import org.springframework.dao.DataAccessException; import edu.umd.cs.findbugs.annotations.NonNull; import javax.servlet.Filter; @@ -86,7 +75,6 @@ import java.lang.reflect.Constructor; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; -import java.security.SecureRandom; import java.util.*; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -94,6 +82,15 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; /** * {@link SecurityRealm} that performs authentication by looking up {@link User}. @@ -183,12 +180,17 @@ private static boolean hasSomeUser() { * This implementation doesn't support groups. */ @Override - public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException { + public GroupDetails loadGroupByGroupname2(String groupname, boolean fetchMembers) throws UsernameNotFoundException { throw new UsernameNotFoundException(groupname); } @Override - public Details loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { + public UserDetails loadUserByUsername2(String username) throws UsernameNotFoundException { + return load(username).asUserDetails(); + } + + @Restricted(NoExternalUse.class) + public Details load(String username) throws UsernameNotFoundException { User u = User.getById(username, false); Details p = u!=null ? u.getProperty(Details.class) : null; if(p==null) @@ -199,18 +201,12 @@ public Details loadUserByUsername(String username) throws UsernameNotFoundExcept } @Override - protected Details authenticate(String username, String password) throws AuthenticationException { - Details u = loadUserByUsername(username); + protected UserDetails authenticate2(String username, String password) throws AuthenticationException { + Details u = load(username); if (!u.isPasswordCorrect(password)) { - String message; - try { - message = ResourceBundle.getBundle("org.acegisecurity.messages").getString("AbstractUserDetailsAuthenticationProvider.badCredentials"); - } catch (MissingResourceException x) { - message = "Bad credentials"; - } - throw new BadCredentialsException(message); + throw new BadCredentialsException("Bad credentials"); } - return u; + return u.asUserDetails(); } /** @@ -281,7 +277,7 @@ private void loginAndTakeBack(StaplerRequest req, StaplerResponse rsp, User u) t // ... and let him login Authentication a = new UsernamePasswordAuthenticationToken(u.getId(),req.getParameter("password1")); - a = this.getSecurityComponents().manager.authenticate(a); + a = this.getSecurityComponents().manager2.authenticate(a); SecurityContextHolder.getContext().setAuthentication(a); SecurityListener.fireLoggedIn(u.getId()); @@ -575,7 +571,7 @@ public User getUser(String id) { } // TODO - private static final GrantedAuthority[] TEST_AUTHORITY = {AUTHENTICATED_AUTHORITY}; + private static final Collection TEST_AUTHORITY = Collections.singleton(AUTHENTICATED_AUTHORITY2); public static final class SignupInfo { public String username,password1,password2,fullname,email,captcha; @@ -619,7 +615,7 @@ public SignupInfo(FederatedIdentity i) { * is sent to the hidden input field by using {@link Protector}, so that * the same password can be retained but without leaking information to the browser. */ - public static final class Details extends UserProperty implements InvalidatableUserDetails { + public static final class Details extends UserProperty { /** * Hashed password. */ @@ -642,20 +638,31 @@ static Details fromHashedPassword(String hashed) { } static Details fromPlainPassword(String rawPassword) { - return new Details(PASSWORD_ENCODER.encodePassword(rawPassword,null)); + return new Details(PASSWORD_ENCODER.encode(rawPassword)); } - public GrantedAuthority[] getAuthorities() { + /** + * @since TODO + */ + public Collection getAuthorities2() { // TODO return TEST_AUTHORITY; } + /** + * @deprecated use {@link #getAuthorities2} + */ + @Deprecated + public org.acegisecurity.GrantedAuthority[] getAuthorities() { + return org.acegisecurity.GrantedAuthority.fromSpring(getAuthorities2()); + } + public String getPassword() { return passwordHash; } public boolean isPasswordCorrect(String candidate) { - return PASSWORD_ENCODER.isPasswordValid(getPassword(),candidate,null); + return PASSWORD_ENCODER.matches(candidate, getPassword()); } public String getProtectedPassword() { @@ -687,8 +694,57 @@ public boolean isEnabled() { return true; } - public boolean isInvalid() { - return user==null; + UserDetails asUserDetails() { + return new UserDetailsImpl(); + } + + private final class UserDetailsImpl implements UserDetails { + + @Override + public Collection getAuthorities() { + return Details.this.getAuthorities2(); + } + + @Override + public String getPassword() { + return Details.this.getPassword(); + } + + @Override + public String getUsername() { + return Details.this.getUsername(); + } + + @Override + public boolean isAccountNonExpired() { + return Details.this.isAccountNonExpired(); + } + + @Override + public boolean isAccountNonLocked() { + return Details.this.isAccountNonLocked(); + } + + @Override + public boolean isCredentialsNonExpired() { + return Details.this.isCredentialsNonExpired(); + } + + @Override + public boolean isEnabled() { + return Details.this.isEnabled(); + } + + @Override + public boolean equals(Object o) { + return o instanceof UserDetailsImpl && ((UserDetailsImpl) o).getUsername().equals(getUsername()); + } + + @Override + public int hashCode() { + return getUsername().hashCode(); + } + } public static class ConverterImpl extends XStream2.PassthruConverter

{ @@ -696,7 +752,7 @@ public static class ConverterImpl extends XStream2.PassthruConverter
{ @Override protected void callback(Details d, UnmarshallingContext context) { // Convert to hashed password and report to monitor if we load old data if (d.password!=null && d.passwordHash==null) { - d.passwordHash = PASSWORD_ENCODER.encodePassword(Scrambler.descramble(d.password),null); + d.passwordHash = PASSWORD_ENCODER.encode(Scrambler.descramble(d.password)); OldDataMonitor.report(context, "1.283"); } } @@ -804,56 +860,7 @@ public Category getCategory() { } } - /** - * {@link PasswordEncoder} based on SHA-256 and random salt generation. - * - *

- * The salt is prepended to the hashed password and returned. So the encoded password is of the form - * {@code SALT ':' hash(PASSWORD,SALT)}. - * - *

- * This abbreviates the need to store the salt separately, which in turn allows us to hide the salt handling - * in this little class. The rest of the Acegi thinks that we are not using salt. - */ - /*package*/ static final PasswordEncoder CLASSIC = new PasswordEncoder() { - private final PasswordEncoder passwordEncoder = new ShaPasswordEncoder(256); - - public String encodePassword(String rawPass, Object obj) throws DataAccessException { - return hash(rawPass); - } - - public boolean isPasswordValid(String encPass, String rawPass, Object obj) throws DataAccessException { - // pull out the sale from the encoded password - int i = encPass.indexOf(':'); - if(i<0) return false; - String salt = encPass.substring(0,i); - return encPass.substring(i+1).equals(passwordEncoder.encodePassword(rawPass,salt)); - } - - /** - * Creates a hashed password by generating a random salt. - */ - private String hash(String password) { - String salt = generateSalt(); - return salt+':'+passwordEncoder.encodePassword(password,salt); - } - - /** - * Generates random salt. - */ - private String generateSalt() { - StringBuilder buf = new StringBuilder(); - SecureRandom sr = new SecureRandom(); - for( int i=0; i<6; i++ ) {// log2(52^6)=34.20... so, this is about 32bit strong. - boolean upper = sr.nextBoolean(); - char ch = (char)(sr.nextInt(26) + 'a'); - if(upper) ch=Character.toUpperCase(ch); - buf.append(ch); - } - return buf.toString(); - } - }; - + // TODO can we instead use BCryptPasswordEncoder from Spring Security, which has its own copy of BCrypt so we could drop the special library? /** * {@link PasswordEncoder} that uses jBCrypt. */ @@ -866,12 +873,14 @@ private static class JBCryptEncoder implements PasswordEncoder { private static final Pattern BCRYPT_PATTERN = Pattern.compile("^\\$2a\\$([0-9]{2})\\$.{53}$"); - public String encodePassword(String rawPass, Object obj) throws DataAccessException { - return BCrypt.hashpw(rawPass,BCrypt.gensalt()); + @Override + public String encode(CharSequence rawPassword) { + return BCrypt.hashpw(rawPassword.toString(), BCrypt.gensalt()); } - public boolean isPasswordValid(String encPass, String rawPass, Object obj) throws DataAccessException { - return BCrypt.checkpw(rawPass,encPass); + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + return BCrypt.checkpw(rawPassword.toString(), encodedPassword); } /** @@ -895,9 +904,10 @@ public boolean isHashValid(String hash) { /* package */ static final JBCryptEncoder JBCRYPT_ENCODER = new JBCryptEncoder(); + // TODO check if DelegatingPasswordEncoder can be used /** - * Combines {@link #JBCRYPT_ENCODER} and {@link #CLASSIC} into one so that we can continue - * to accept {@link #CLASSIC} format but new encoding will always done via {@link #JBCRYPT_ENCODER}. + * Wraps {@link #JBCRYPT_ENCODER}. + * There used to be a SHA-256-based encoder but this is long deprecated, and insecure anyway. */ /* package */ static class MultiPasswordEncoder implements PasswordEncoder { /** @@ -911,15 +921,17 @@ public boolean isHashValid(String hash) { '#' is neither in base64 nor hex, which makes it a good choice. */ - public String encodePassword(String rawPass, Object salt) throws DataAccessException { - return JBCRYPT_HEADER+JBCRYPT_ENCODER.encodePassword(rawPass,salt); + @Override + public String encode(CharSequence rawPassword) { + return JBCRYPT_HEADER+JBCRYPT_ENCODER.encode(rawPassword); } - public boolean isPasswordValid(String encPass, String rawPass, Object salt) throws DataAccessException { + @Override + public boolean matches(CharSequence rawPassword, String encPass) { if (isPasswordHashed(encPass)) { - return JBCRYPT_ENCODER.isPasswordValid(encPass.substring(JBCRYPT_HEADER.length()), rawPass, salt); + return JBCRYPT_ENCODER.matches(rawPassword, encPass.substring(JBCRYPT_HEADER.length())); } else { - return CLASSIC.isPasswordValid(encPass, rawPass, salt); + return false; } } diff --git a/core/src/main/java/hudson/security/InvalidatableUserDetails.java b/core/src/main/java/hudson/security/InvalidatableUserDetails.java deleted file mode 100644 index 8f59170c01bf..000000000000 --- a/core/src/main/java/hudson/security/InvalidatableUserDetails.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi - * - * 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 hudson.security; - -import org.acegisecurity.Authentication; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.userdetails.UserDetails; - -import javax.servlet.http.HttpSession; -import jenkins.security.NonSerializableSecurityContext; - -/** - * {@link UserDetails} that can mark {@link Authentication} invalid. - * - *

- * Tomcat persists sessions by using Java serialization (and - * that includes the security token created by Acegi, which includes this object) - * and when that happens, the next time the server comes back - * it will try to deserialize {@link SecurityContext} that Acegi - * puts into {@link HttpSession} (which transitively includes {@link UserDetails} - * that can be implemented by Hudson. - * - *

- * Such {@link UserDetails} implementation can override the {@link #isInvalid()} - * method and return false, so that such {@link SecurityContext} will be - * dropped before the rest of Acegi sees it. - * - *

- * See JENKINS-1482 - * - * @author Kohsuke Kawaguchi - * @deprecated - * Starting 1.285, Hudson stops persisting {@link Authentication} altogether - * (see {@link NonSerializableSecurityContext}), so there's no need to use this mechanism. - */ -@Deprecated -public interface InvalidatableUserDetails extends UserDetails { - boolean isInvalid(); -} diff --git a/core/src/main/java/hudson/security/LegacySecurityRealm.java b/core/src/main/java/hudson/security/LegacySecurityRealm.java index 3db5a4b92248..807e2f324af4 100644 --- a/core/src/main/java/hudson/security/LegacySecurityRealm.java +++ b/core/src/main/java/hudson/security/LegacySecurityRealm.java @@ -23,21 +23,19 @@ */ package hudson.security; -import org.acegisecurity.AuthenticationManager; -import org.acegisecurity.Authentication; -import org.acegisecurity.AuthenticationException; +import hudson.Extension; +import hudson.model.Descriptor; +import java.util.ArrayList; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterConfig; import org.jenkinsci.Symbol; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.DataBoundConstructor; -import org.springframework.web.context.WebApplicationContext; -import groovy.lang.Binding; -import hudson.model.Descriptor; -import hudson.util.spring.BeanBuilder; -import hudson.Extension; - -import javax.servlet.Filter; -import javax.servlet.FilterConfig; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; /** * {@link SecurityRealm} that accepts {@link ContainerAuthentication} object @@ -76,22 +74,17 @@ public String getLoginUrl() { return "loginEntry"; } - /** - * Filter to run for the LegacySecurityRealm is the - * ChainServletFilter legacy from /WEB-INF/security/SecurityFilters.groovy. - */ @Override public Filter createFilter(FilterConfig filterConfig) { - Binding binding = new Binding(); - SecurityComponents sc = this.createSecurityComponents(); - binding.setVariable("securityComponents", sc); - binding.setVariable("securityRealm",this); - BeanBuilder builder = new BeanBuilder(); - builder.parse(filterConfig.getServletContext().getResourceAsStream("/WEB-INF/security/SecurityFilters.groovy"),binding); - - WebApplicationContext context = builder.createApplicationContext(); - - return (Filter) context.getBean("legacy"); + // this filter set up is used to emulate the legacy Hudson behavior + // of container authentication before 1.160 + List filters = new ArrayList<>(); + filters.add(new BasicAuthenticationFilter()); + filters.addAll(commonFilters()); + // when using container-authentication we can't hit /login directly. + // we first have to hit protected /loginEntry, then let the container + // trap that into /login. + return new ChainedServletFilter(filters); } /** diff --git a/core/src/main/java/hudson/security/RememberMeServicesProxy.java b/core/src/main/java/hudson/security/RememberMeServicesProxy.java index 6b4ba001a0b7..065d2fc8b242 100644 --- a/core/src/main/java/hudson/security/RememberMeServicesProxy.java +++ b/core/src/main/java/hudson/security/RememberMeServicesProxy.java @@ -23,13 +23,14 @@ */ package hudson.security; -import jenkins.model.Jenkins; -import jenkins.security.ConfidentialStore; -import org.acegisecurity.ui.rememberme.RememberMeServices; -import org.acegisecurity.Authentication; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import jenkins.model.Jenkins; +import jenkins.security.ConfidentialStore; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.RememberMeServices; /** * {@link RememberMeServices} proxy. @@ -45,6 +46,7 @@ * * @author Kohsuke Kawaguchi */ +@Restricted(NoExternalUse.class) public class RememberMeServicesProxy implements RememberMeServices { private volatile RememberMeServices delegate; diff --git a/core/src/main/java/hudson/security/SecurityMode.java b/core/src/main/java/hudson/security/SecurityMode.java index a2a1922ad375..6119cd04e3ef 100644 --- a/core/src/main/java/hudson/security/SecurityMode.java +++ b/core/src/main/java/hudson/security/SecurityMode.java @@ -45,7 +45,7 @@ public enum SecurityMode { */ LEGACY, /** - * Security-enabled mode implemented through Acegi. + * Security-enabled mode implemented through Spring Security. */ SECURED } diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java index 4ee1202b920a..949f8cb6c2ea 100644 --- a/core/src/main/java/hudson/security/SecurityRealm.java +++ b/core/src/main/java/hudson/security/SecurityRealm.java @@ -23,55 +23,61 @@ */ package hudson.security; -import groovy.lang.Binding; -import hudson.ExtensionPoint; import hudson.DescriptorExtensionList; import hudson.Extension; +import hudson.ExtensionPoint; +import hudson.Util; import hudson.cli.CLICommand; import hudson.model.AbstractDescribableImpl; import hudson.model.Descriptor; -import jenkins.model.IdStrategy; -import jenkins.model.Jenkins; import hudson.security.FederatedLoginService.FederatedIdentity; import hudson.security.captcha.CaptchaSupport; import hudson.util.DescriptorList; import hudson.util.PluginServletFilter; -import hudson.util.spring.BeanBuilder; -import org.acegisecurity.Authentication; -import org.acegisecurity.AuthenticationManager; -import org.acegisecurity.GrantedAuthorityImpl; -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.ui.rememberme.RememberMeServices; -import static org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY; -import org.acegisecurity.userdetails.UserDetailsService; -import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.UsernameNotFoundException; -import org.apache.commons.lang.StringUtils; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.DoNotUse; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.Stapler; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import org.springframework.context.ApplicationContext; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.dao.DataAccessException; - -import javax.servlet.Filter; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.http.HttpSession; -import javax.servlet.http.Cookie; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import javax.servlet.Filter; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpSession; +import jenkins.model.IdStrategy; +import jenkins.model.Jenkins; +import jenkins.security.AcegiSecurityExceptionFilter; +import jenkins.security.BasicHeaderProcessor; import net.sf.json.JSONObject; +import org.apache.commons.lang.StringUtils; import org.jenkinsci.Symbol; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.Stapler; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.access.ExceptionTranslationFilter; +import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; +import org.springframework.security.web.authentication.RememberMeServices; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; +import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter; +import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; +import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; /** * Pluggable security realm that connects external user database to Hudson. @@ -87,7 +93,7 @@ * For compatibility reasons, there are two somewhat different ways to implement a custom SecurityRealm. * *

- * One is to override the {@link #createSecurityComponents()} and create key Acegi components + * One is to override the {@link #createSecurityComponents()} and create key Spring Security components * that control the authentication process. * The default {@link SecurityRealm#createFilter(FilterConfig)} implementation then assembles them * into a chain of {@link Filter}s. All the incoming requests to Hudson go through this filter chain, @@ -106,7 +112,7 @@ * The other way of doing this is to ignore {@link #createSecurityComponents()} completely (by returning * {@link SecurityComponents} created by the default constructor) and just concentrate on {@link #createFilter(FilterConfig)}. * As long as the resulting filter chain properly sets up {@link Authentication} object at the end of the processing, - * Hudson doesn't really need you to fit the standard Acegi models like {@link AuthenticationManager} and + * Jenkins doesn't really need you to fit the standard Spring Security models like {@link AuthenticationManager} and * {@link UserDetailsService}. * *

@@ -157,12 +163,12 @@ public abstract class SecurityRealm extends AbstractDescribableImpl getDescriptor() { /** * Returns the URL to submit a form for the authentication. * There's no need to override this, except for {@link LegacySecurityRealm}. + * @see AuthenticationProcessingFilter2 */ public String getAuthenticationGatewayUrl() { - return "j_acegi_security_check"; + // Default as of Spring Security 3: https://stackoverflow.com/a/62552368/12916 + // Cannot use the 4+ default of /login since that would clash with Jenkins/login.jelly which would be activated even for GET requests, + // and which cannot trivially be renamed since it is a fairly well-known URL sometimes used e.g. for K8s liveness checks. + return "j_spring_security_check"; } /** @@ -258,12 +268,30 @@ public boolean canLogOut() { * This parameter allows you to redirect people to different pages depending on who they are. * @return * never null. - * @since 1.314 + * @since TODO * @see #doLogout(StaplerRequest, StaplerResponse) */ - protected String getPostLogOutUrl(StaplerRequest req, Authentication auth) { + protected String getPostLogOutUrl2(StaplerRequest req, Authentication auth) { + if (Util.isOverridden(SecurityRealm.class, getClass(), "getPostLogOutUrl", StaplerRequest.class, org.acegisecurity.Authentication.class) && !insideGetPostLogOutUrl.get()) { + insideGetPostLogOutUrl.set(true); + try { + return getPostLogOutUrl(req, org.acegisecurity.Authentication.fromSpring(auth)); + } finally { + insideGetPostLogOutUrl.set(false); + } + } return req.getContextPath()+"/"; } + private static final ThreadLocal insideGetPostLogOutUrl = ThreadLocal.withInitial(() -> false); + + /** + * @deprecated use {@link #getPostLogOutUrl2} + * @since 1.314 + */ + @Deprecated + protected String getPostLogOutUrl(StaplerRequest req, org.acegisecurity.Authentication auth) { + return getPostLogOutUrl2(req, auth.toSpring()); + } public CaptchaSupport getCaptchaSupport() { return captchaSupport; @@ -282,7 +310,7 @@ public List> getCaptchaSupportDescriptors() { * *

* The default implementation erases the session and do a few other clean up, then - * redirect the user to the URL specified by {@link #getPostLogOutUrl(StaplerRequest, Authentication)}. + * redirect the user to the URL specified by {@link #getPostLogOutUrl2(StaplerRequest, Authentication)}. * * @since 1.314 */ @@ -297,11 +325,11 @@ public void doLogout(StaplerRequest req, StaplerResponse rsp) throws IOException resetRememberMeCookie(req, rsp, contextPath); clearStaleSessionCookies(req, rsp, contextPath); - rsp.sendRedirect2(getPostLogOutUrl(req,auth)); + rsp.sendRedirect2(getPostLogOutUrl2(req,auth)); } private void resetRememberMeCookie(StaplerRequest req, StaplerResponse rsp, String contextPath) { - Cookie cookie = new Cookie(ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY, ""); + Cookie cookie = new Cookie(TokenBasedRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, ""); cookie.setMaxAge(0); cookie.setSecure(req.isSecure()); cookie.setHttpOnly(true); @@ -363,25 +391,36 @@ public boolean allowsSignup() { /** * Shortcut for {@link UserDetailsService#loadUserByUsername(String)}. * - * @throws UserMayOrMayNotExistException + * @throws UserMayOrMayNotExistException2 * If the security realm cannot even tell if the user exists or not. * @return * never null. + * @since TODO */ - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { - return getSecurityComponents().userDetails.loadUserByUsername(username); + public UserDetails loadUserByUsername2(String username) throws UsernameNotFoundException { + if (Util.isOverridden(SecurityRealm.class, getClass(), "loadUserByUsername", String.class)) { + try { + return loadUserByUsername(username).toSpring(); + } catch (org.acegisecurity.AcegiSecurityException x) { + throw x.toSpring(); + } catch (org.springframework.dao.DataAccessException x) { + throw x.toSpring(); + } + } else { + return getSecurityComponents().userDetails2.loadUserByUsername(username); + } } /** - * If this {@link SecurityRealm} supports a look up of {@link GroupDetails} by their names, override this method - * to provide the look up. - * - *

- * This information, when available, can be used by {@link AuthorizationStrategy}s to improve the UI and - * error diagnostics for the user. + * @deprecated use {@link #loadUserByUsername2} */ - public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException { - throw new UserMayOrMayNotExistException(groupname); + @Deprecated + public org.acegisecurity.userdetails.UserDetails loadUserByUsername(String username) throws org.acegisecurity.userdetails.UsernameNotFoundException, org.springframework.dao.DataAccessException { + try { + return org.acegisecurity.userdetails.UserDetails.fromSpring(loadUserByUsername2(username)); + } catch (AuthenticationException x) { + throw org.acegisecurity.AuthenticationException.fromSpring(x); + } } /** @@ -395,14 +434,56 @@ public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFou * @param fetchMembers if {@code true} then try and fetch the members of the group if it exists. Trying does not * imply that the members will be fetched and {@link hudson.security.GroupDetails#getMembers()} * may still return {@code null} - * @throws UserMayOrMayNotExistException if no conclusive result could be determined regarding the group existence. + * @throws UserMayOrMayNotExistException2 if no conclusive result could be determined regarding the group existence. * @throws UsernameNotFoundException if the group does not exist. - * @throws DataAccessException if the backing security realm could not be connected to. + * @since TODO + */ + public GroupDetails loadGroupByGroupname2(String groupname, boolean fetchMembers) + throws UsernameNotFoundException { + if (Util.isOverridden(SecurityRealm.class, getClass(), "loadGroupByGroupname", String.class)) { + try { + return loadGroupByGroupname(groupname); + } catch (org.acegisecurity.AcegiSecurityException x) { + throw x.toSpring(); + } catch (org.springframework.dao.DataAccessException x) { + throw x.toSpring(); + } + } else if (Util.isOverridden(SecurityRealm.class, getClass(), "loadGroupByGroupname", String.class, boolean.class)) { + try { + return loadGroupByGroupname(groupname, fetchMembers); + } catch (org.acegisecurity.AcegiSecurityException x) { + throw x.toSpring(); + } catch (org.springframework.dao.DataAccessException x) { + throw x.toSpring(); + } + } else { + throw new UserMayOrMayNotExistException2(groupname); + } + } + + /** + * @deprecated use {@link #loadGroupByGroupname2} + */ + @Deprecated + public GroupDetails loadGroupByGroupname(String groupname) throws org.acegisecurity.userdetails.UsernameNotFoundException, org.springframework.dao.DataAccessException { + try { + return loadGroupByGroupname2(groupname, false); + } catch (AuthenticationException x) { + throw org.acegisecurity.AuthenticationException.fromSpring(x); + } + } + + /** + * @deprecated use {@link #loadGroupByGroupname2} * @since 1.549 */ - public GroupDetails loadGroupByGroupname(String groupname, boolean fetchMembers) - throws UsernameNotFoundException, DataAccessException { - return loadGroupByGroupname(groupname); + @Deprecated + public GroupDetails loadGroupByGroupname(String groupname, boolean fetchMembers) throws org.acegisecurity.userdetails.UsernameNotFoundException, org.springframework.dao.DataAccessException { + try { + return loadGroupByGroupname2(groupname, fetchMembers); + } catch (AuthenticationException x) { + throw org.acegisecurity.AuthenticationException.fromSpring(x); + } } /** @@ -455,26 +536,6 @@ protected final boolean validateCaptcha(String text) { return true; } - /** - * Picks up the instance of the given type from the spring context. - * If there are multiple beans of the same type or if there are none, - * this method treats that as an {@link IllegalArgumentException}. - * - * This method is intended to be used to pick up a Acegi object from - * spring once the bean definition file is parsed. - */ - public static T findBean(Class type, ApplicationContext context) { - Map m = context.getBeansOfType(type); - switch(m.size()) { - case 0: - throw new IllegalArgumentException("No beans of "+type+" are defined"); - case 1: - return type.cast(m.values().iterator().next()); - default: - throw new IllegalArgumentException("Multiple beans of "+type+" are defined: "+m); - } - } - /** * Holder for the SecurityComponents. */ @@ -497,7 +558,7 @@ public synchronized SecurityComponents getSecurityComponents() { * *

* The default implementation uses {@link #getSecurityComponents()} and builds - * a standard filter chain from /WEB-INF/security/SecurityFilters.groovy. + * a standard filter chain. * But subclasses can override this to completely change the filter sequence. * *

@@ -509,14 +570,46 @@ public synchronized SecurityComponents getSecurityComponents() { public Filter createFilter(FilterConfig filterConfig) { LOGGER.entering(SecurityRealm.class.getName(), "createFilter"); - Binding binding = new Binding(); SecurityComponents sc = getSecurityComponents(); - binding.setVariable("securityComponents", sc); - binding.setVariable("securityRealm",this); - BeanBuilder builder = new BeanBuilder(); - builder.parse(filterConfig.getServletContext().getResourceAsStream("/WEB-INF/security/SecurityFilters.groovy"),binding); - WebApplicationContext context = builder.createApplicationContext(); - return (Filter) context.getBean("filter"); + List filters = new ArrayList<>(); + { + HttpSessionSecurityContextRepository httpSessionSecurityContextRepository = new HttpSessionSecurityContextRepository(); + httpSessionSecurityContextRepository.setAllowSessionCreation(false); + filters.add(new HttpSessionContextIntegrationFilter2(httpSessionSecurityContextRepository)); + } + { // if any "Authorization: Basic xxx:yyy" is sent this is the filter that processes it + BasicHeaderProcessor bhp = new BasicHeaderProcessor(); + // if basic authentication fails (which only happens incorrect basic auth credential is sent), + // respond with 401 with basic auth request, instead of redirecting the user to the login page, + // since users of basic auth tends to be a program and won't see the redirection to the form + // page as a failure + BasicAuthenticationEntryPoint basicAuthenticationEntryPoint = new BasicAuthenticationEntryPoint(); + basicAuthenticationEntryPoint.setRealmName("Jenkins"); + bhp.setAuthenticationEntryPoint(basicAuthenticationEntryPoint); + bhp.setRememberMeServices(sc.rememberMe2); + filters.add(bhp); + } + { + AuthenticationProcessingFilter2 apf = new AuthenticationProcessingFilter2(getAuthenticationGatewayUrl()); + apf.setAuthenticationManager(sc.manager2); + apf.setRememberMeServices(sc.rememberMe2); + apf.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/loginError")); + // TODO apf.defaultTargetUrl = "/" try SavedRequestAwareAuthenticationSuccessHandler + filters.add(apf); + } + filters.add(new RememberMeAuthenticationFilter(sc.manager2, sc.rememberMe2)); + filters.addAll(commonFilters()); + return new ChainedServletFilter(filters); + } + + protected final List commonFilters() { + // like Jenkins.ANONYMOUS: + AnonymousAuthenticationFilter apf = new AnonymousAuthenticationFilter("anonymous", "anonymous", Collections.singletonList(new SimpleGrantedAuthority("anonymous"))); + ExceptionTranslationFilter etf = new ExceptionTranslationFilter(new HudsonAuthenticationEntryPoint("/" + getLoginUrl() + "?from={0}")); + etf.setAccessDeniedHandler(new AccessDeniedHandlerImpl()); + UnwrapSecurityExceptionFilter usef = new UnwrapSecurityExceptionFilter(); + AcegiSecurityExceptionFilter asef = new AcegiSecurityExceptionFilter(); + return Arrays.asList(apf, etf, usef, asef); } /** @@ -578,7 +671,7 @@ public Authentication authenticate(Authentication authentication) { return authentication; } }, new UserDetailsService() { - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { throw new UsernameNotFoundException(username); } }); @@ -588,7 +681,7 @@ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundEx * There's no group. */ @Override - public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException { + public GroupDetails loadGroupByGroupname2(String groupname, boolean fetchMembers) throws UsernameNotFoundException { throw new UsernameNotFoundException(groupname); } @@ -633,9 +726,33 @@ public SecurityRealm newInstance(StaplerRequest req, JSONObject formData) throws * @see SecurityRealm#createSecurityComponents() */ public static final class SecurityComponents { - public final AuthenticationManager manager; - public final UserDetailsService userDetails; - public final RememberMeServices rememberMe; + /** + * @since TODO + */ + public final AuthenticationManager manager2; + /** + * @deprecated use {@link #manager2} + */ + @Deprecated + public final org.acegisecurity.AuthenticationManager manager; + /** + * @since TODO + */ + public final UserDetailsService userDetails2; + /** + * @deprecated use {@link #userDetails2} + */ + @Deprecated + public final org.acegisecurity.userdetails.UserDetailsService userDetails; + /** + * @since TODO + */ + public final RememberMeServices rememberMe2; + /** + * @deprecated use {@link #rememberMe2} + */ + @Deprecated + public final org.acegisecurity.ui.rememberme.RememberMeServices rememberMe; public SecurityComponents() { // we use AuthenticationManagerProxy here just as an implementation that fails all the time, @@ -643,39 +760,62 @@ public SecurityComponents() { this(new AuthenticationManagerProxy()); } + /** + * @since TODO + */ public SecurityComponents(AuthenticationManager manager) { // we use UserDetailsServiceProxy here just as an implementation that fails all the time, // not as a proxy. No one is supposed to use this as a proxy. this(manager,new UserDetailsServiceProxy()); } + /** + * @deprecated use {@link #SecurityComponents(AuthenticationManager)} + */ + @Deprecated + public SecurityComponents(org.acegisecurity.AuthenticationManager manager) { + this(manager.toSpring()); + } + + /** + * @since TODO + */ public SecurityComponents(AuthenticationManager manager, UserDetailsService userDetails) { this(manager,userDetails,createRememberMeService(userDetails)); } + /** + * @deprecated use {@link #SecurityComponents(AuthenticationManager, UserDetailsService)} + */ + @Deprecated + public SecurityComponents(org.acegisecurity.AuthenticationManager manager, org.acegisecurity.userdetails.UserDetailsService userDetails) { + this(manager.toSpring(), userDetails.toSpring()); + } + + /** + * @since TODO + */ public SecurityComponents(AuthenticationManager manager, UserDetailsService userDetails, RememberMeServices rememberMe) { assert manager!=null && userDetails!=null && rememberMe!=null; - this.manager = manager; - this.userDetails = userDetails; - this.rememberMe = rememberMe; + this.manager2 = manager; + this.userDetails2 = userDetails; + this.rememberMe2 = rememberMe; + this.manager = org.acegisecurity.AuthenticationManager.fromSpring(manager); + this.userDetails = org.acegisecurity.userdetails.UserDetailsService.fromSpring(userDetails); + this.rememberMe = org.acegisecurity.ui.rememberme.RememberMeServices.fromSpring(rememberMe); + } + + /** + * @deprecated use {@link #SecurityComponents(AuthenticationManager, UserDetailsService, RememberMeServices)} + */ + @Deprecated + public SecurityComponents(org.acegisecurity.AuthenticationManager manager, org.acegisecurity.userdetails.UserDetailsService userDetails, org.acegisecurity.ui.rememberme.RememberMeServices rememberMe) { + this(manager.toSpring(), userDetails.toSpring(), rememberMe.toSpring()); } - @SuppressWarnings("deprecation") private static RememberMeServices createRememberMeService(UserDetailsService uds) { // create our default TokenBasedRememberMeServices, which depends on the availability of the secret key - TokenBasedRememberMeServices2 rms = new TokenBasedRememberMeServices2(); - rms.setUserDetailsService(uds); - /* - TokenBasedRememberMeServices needs to be used in conjunction with RememberMeAuthenticationProvider, - and both needs to use the same key (this is a reflection of a poor design in AcegiSecurity, if you ask me) - and various security plugins have its own groovy script that configures them. - - So if we change this, it creates a painful situation for those plugins by forcing them to choose - to work with earlier version of Jenkins or newer version of Jenkins, and not both. - - So we keep this here. - */ - rms.setKey(Jenkins.get().getSecretKey()); + TokenBasedRememberMeServices2 rms = new TokenBasedRememberMeServices2(uds); rms.setParameter("remember_me"); // this is the form field name in login.jelly return rms; } @@ -703,6 +843,14 @@ public static DescriptorExtensionList> a /** * {@link GrantedAuthority} that represents the built-in "authenticated" role, which is granted to * anyone non-anonymous. + * @since TODO */ - public static final GrantedAuthority AUTHENTICATED_AUTHORITY = new GrantedAuthorityImpl("authenticated"); + public static final GrantedAuthority AUTHENTICATED_AUTHORITY2 = new SimpleGrantedAuthority("authenticated"); + + /** + * @deprecated use {@link #AUTHENTICATED_AUTHORITY2} + */ + @Deprecated + public static final org.acegisecurity.GrantedAuthority AUTHENTICATED_AUTHORITY = new org.acegisecurity.GrantedAuthorityImpl("authenticated"); + } diff --git a/core/src/main/java/hudson/security/SidACL.java b/core/src/main/java/hudson/security/SidACL.java index 1f9ca23610d0..3bdeb3ee4fe2 100644 --- a/core/src/main/java/hudson/security/SidACL.java +++ b/core/src/main/java/hudson/security/SidACL.java @@ -23,16 +23,15 @@ */ package hudson.security; -import org.acegisecurity.Authentication; -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.acls.sid.PrincipalSid; -import org.acegisecurity.acls.sid.GrantedAuthoritySid; -import org.acegisecurity.acls.sid.Sid; - import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.logging.Logger; import static java.util.logging.Level.FINE; import static java.util.logging.Level.FINER; +import java.util.logging.Logger; +import org.acegisecurity.acls.sid.GrantedAuthoritySid; +import org.acegisecurity.acls.sid.PrincipalSid; +import org.acegisecurity.acls.sid.Sid; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; /** * {@link ACL} that checks permissions based on {@link GrantedAuthority} @@ -43,8 +42,8 @@ public abstract class SidACL extends ACL { @Override - public boolean hasPermission(@NonNull Authentication a, Permission permission) { - if(a==SYSTEM) { + public boolean hasPermission2(@NonNull Authentication a, Permission permission) { + if(a.equals(SYSTEM2)) { if(LOGGER.isLoggable(FINE)) LOGGER.fine("hasPermission("+a+","+permission+")=>SYSTEM user has full access"); return true; @@ -59,7 +58,7 @@ public boolean hasPermission(@NonNull Authentication a, Permission permission) { } /** - * Implementation that backs up {@link #hasPermission(Authentication, Permission)}. + * Implementation that backs up {@link #hasPermission2(Authentication, Permission)}. * * @return * true or false if {@link #hasPermission(Sid, Permission)} returns it. @@ -100,7 +99,7 @@ protected Boolean _hasPermission(@NonNull Authentication a, Permission permissio * Checks if the given {@link Sid} has the given {@link Permission}. * *

- * {@link #hasPermission(Authentication, Permission)} is implemented + * {@link #hasPermission2(Authentication, Permission)} is implemented * by checking authentication's {@link GrantedAuthority} by using * this method. * diff --git a/core/src/main/java/hudson/security/SparseACL.java b/core/src/main/java/hudson/security/SparseACL.java index d9c67a05ecdd..0dd801f40501 100644 --- a/core/src/main/java/hudson/security/SparseACL.java +++ b/core/src/main/java/hudson/security/SparseACL.java @@ -23,13 +23,12 @@ */ package hudson.security; -import org.acegisecurity.Authentication; -import org.acegisecurity.acls.sid.Sid; - import java.util.ArrayList; import java.util.List; -import java.util.logging.Logger; import static java.util.logging.Level.FINE; +import java.util.logging.Logger; +import org.acegisecurity.acls.sid.Sid; +import org.springframework.security.core.Authentication; /** * Access control list. @@ -69,15 +68,15 @@ public void add(Sid sid, Permission permission, boolean allowed) { } @Override - public boolean hasPermission(Authentication a, Permission permission) { - if(a==SYSTEM) return true; + public boolean hasPermission2(Authentication a, Permission permission) { + if(a.equals(SYSTEM2)) return true; Boolean b = _hasPermission(a,permission); if(b!=null) return b; if(parent!=null) { if(LOGGER.isLoggable(FINE)) LOGGER.fine("hasPermission("+a+","+permission+") is delegating to parent ACL: "+parent); - return parent.hasPermission(a,permission); + return parent.hasPermission2(a,permission); } // the ultimate default is to reject everything diff --git a/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java b/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java index 6d2957cba9a0..065d8753c29e 100644 --- a/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java +++ b/core/src/main/java/hudson/security/TokenBasedRememberMeServices2.java @@ -23,38 +23,29 @@ */ package hudson.security; -import hudson.Functions; +import com.google.common.annotations.VisibleForTesting; import hudson.model.User; +import java.util.Date; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import jenkins.model.Jenkins; import jenkins.security.HMACConfidentialKey; -import jenkins.security.ImpersonatingUserDetailsService; +import jenkins.security.ImpersonatingUserDetailsService2; import jenkins.security.LastGrantedAuthoritiesProperty; import jenkins.security.seed.UserSeedProperty; import jenkins.util.SystemProperties; -import org.acegisecurity.Authentication; -import org.acegisecurity.providers.rememberme.RememberMeAuthenticationToken; -import org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices; -import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.UserDetailsService; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.util.Base64; -import java.util.Date; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; +import org.springframework.security.authentication.RememberMeAuthenticationProvider; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.authentication.rememberme.InvalidCookieException; +import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; /** * {@link TokenBasedRememberMeServices} with modification so as not to rely @@ -66,6 +57,7 @@ * * @author Kohsuke Kawaguchi */ +@Restricted(NoExternalUse.class) public class TokenBasedRememberMeServices2 extends TokenBasedRememberMeServices { private static final Logger LOGGER = Logger.getLogger(TokenBasedRememberMeServices2.class.getName()); @@ -73,35 +65,35 @@ public class TokenBasedRememberMeServices2 extends TokenBasedRememberMeServices /** * Escape hatch for the check on the maximum date for the expiration duration of the remember me cookie */ - @Restricted(NoExternalUse.class) public static /* Script Console modifiable */ boolean SKIP_TOO_FAR_EXPIRATION_DATE_CHECK = SystemProperties.getBoolean(TokenBasedRememberMeServices2.class.getName() + ".skipTooFarExpirationDateCheck"); /** * Decorate {@link UserDetailsService} so that we can use information stored in * {@link LastGrantedAuthoritiesProperty}. - * - * We wrap by {@link ImpersonatingUserDetailsService} in other places too, - * so this is possibly redundant, but there are many {@link AbstractPasswordBasedSecurityRealm#loadUserByUsername(String)} + *

+ * We wrap by {@link ImpersonatingUserDetailsService2} in other places too, + * so this is possibly redundant, but there are many {@link AbstractPasswordBasedSecurityRealm#loadUserByUsername2(String)} * implementations that do not do it, so doing it helps retrofit old plugins to benefit from - * the user impersonation improvements. Plus multiple {@link ImpersonatingUserDetailsService} + * the user impersonation improvements. Plus multiple {@link ImpersonatingUserDetailsService2} * do not incur any real performance penalty. + *

+ * {@link TokenBasedRememberMeServices} needs to be used in conjunction with {@link RememberMeAuthenticationProvider} + * (see {@link AbstractPasswordBasedSecurityRealm#createSecurityComponents}) + * and both need to use the same key and various security plugins need to do the same. */ - @Override - public void setUserDetailsService(UserDetailsService userDetailsService) { - super.setUserDetailsService(new ImpersonatingUserDetailsService(userDetailsService)); + @SuppressWarnings("deprecation") + public TokenBasedRememberMeServices2(UserDetailsService userDetailsService) { + super(Jenkins.get().getSecretKey(), new ImpersonatingUserDetailsService2(userDetailsService)); } @Override - protected String makeTokenSignature(long tokenExpiryTime, UserDetails userDetails) { + protected String makeTokenSignature(long tokenExpiryTime, String username, String password) { String userSeed; if (UserSeedProperty.DISABLE_USER_SEED) { userSeed = "no-seed"; } else { - User user = User.getById(userDetails.getUsername(), false); - if (user == null) { - return "no-user"; - } + User user = User.getById(username, true); UserSeedProperty userSeedProperty = user.getProperty(UserSeedProperty.class); if (userSeedProperty == null) { // if you want to filter out the user seed property, you should consider using the DISABLE_USER_SEED instead @@ -109,7 +101,7 @@ protected String makeTokenSignature(long tokenExpiryTime, UserDetails userDetail } userSeed = userSeedProperty.getSeed(); } - String token = String.join(":", userDetails.getUsername(), Long.toString(tokenExpiryTime), userSeed, getKey()); + String token = String.join(":", username, Long.toString(tokenExpiryTime), userSeed, getKey()); return MAC.mac(token); } @@ -119,7 +111,7 @@ protected String retrievePassword(Authentication successfulAuthentication) { } @Override - public void loginSuccess(HttpServletRequest request, HttpServletResponse response, + public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { // Exit if the principal hasn't asked to be remembered if (!rememberMeRequested(request, getParameter())) { @@ -141,17 +133,18 @@ public void loginSuccess(HttpServletRequest request, HttpServletResponse respons return; } - Assert.notNull(successfulAuthentication.getPrincipal()); - Assert.notNull(successfulAuthentication.getCredentials()); - Assert.isInstanceOf(UserDetails.class, successfulAuthentication.getPrincipal()); + // TODO is it really still necessary to reimplement all of the below, or could we simply override rememberMeRequested? - long expiryTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(tokenValiditySeconds); + Objects.requireNonNull(successfulAuthentication.getPrincipal()); + UserDetails.class.cast(successfulAuthentication.getPrincipal()); + + long expiryTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(getTokenValiditySeconds()); String username = ((UserDetails) successfulAuthentication.getPrincipal()).getUsername(); - String signatureValue = makeTokenSignature(expiryTime, (UserDetails)successfulAuthentication.getPrincipal()); - String tokenValue = username + ":" + expiryTime + ":" + signatureValue; - String tokenValueBase64 = Base64.getEncoder().encodeToString(tokenValue.getBytes()); - response.addCookie(makeValidCookie(tokenValueBase64, request, tokenValiditySeconds)); + String signatureValue = makeTokenSignature(expiryTime, username, ((UserDetails) successfulAuthentication.getPrincipal()).getPassword()); + int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication); + setCookie(new String[] { username, Long.toString(expiryTime), signatureValue }, + tokenLifetime, request, response); if (logger.isDebugEnabled()) { logger.debug("Added remember-me cookie for user '" + username + "', expiry: '" + new Date(expiryTime) @@ -160,123 +153,27 @@ public void loginSuccess(HttpServletRequest request, HttpServletResponse respons } @Override - public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { + protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request, HttpServletResponse response) { Jenkins j = Jenkins.getInstanceOrNull(); if (j == null) { // as this filter could be called during restart, this corrects at least the symptoms - return null; + throw new InvalidCookieException("Jenkins is not yet running"); } if (j.isDisableRememberMe()) { - cancelCookie(request, response, null); - return null; + cancelCookie(request, response); + throw new InvalidCookieException("rememberMe is disabled"); } else { - try { - // we use a patched version of the super.autoLogin - String rememberMeValue = findRememberMeCookieValue(request, response); - if (rememberMeValue == null) { - return null; - } - return retrieveAuthFromCookie(request, response, rememberMeValue); - } catch (Exception e) { - cancelCookie(request, response, "Failed to handle remember-me cookie: " + Functions.printThrowable(e)); - return null; - } + return super.processAutoLoginCookie(cookieTokens, request, response); } } - /** - * Patched version of the super.autoLogin with a time-independent equality check for the token validation - */ - private String findRememberMeCookieValue(HttpServletRequest request, HttpServletResponse response) { - Cookie[] cookies = request.getCookies(); - - if ((cookies == null) || (cookies.length == 0)) { - return null; - } - - for (Cookie cookie : cookies) { - if (ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY.equals(cookie.getName())) { - return cookie.getValue(); - } - } - - return null; - } - - // Taken from TokenBasedRememberMeService with slight modification - // around the token equality check to avoid timing attack - // and reducing drastically the information provided in the log to avoid potential disclosure - private @CheckForNull Authentication retrieveAuthFromCookie(HttpServletRequest request, HttpServletResponse response, String cookieValueBase64){ - String cookieValue = decodeCookieBase64(cookieValueBase64); - if (cookieValue == null) { - String reason = "Cookie token was not Base64 encoded; value was '" + cookieValueBase64 + "'"; - cancelCookie(request, response, reason); - return null; - } - if (logger.isDebugEnabled()) { - logger.debug("Remember-me cookie detected"); - } - - String[] cookieTokens = StringUtils.delimitedListToStringArray(cookieValue, ":"); - - if (cookieTokens.length != 3) { - cancelCookie(request, response, "Cookie token did not contain 3 tokens separated by [:]"); - return null; - } - - long tokenExpiryTime; - - try { - tokenExpiryTime = Long.parseLong(cookieTokens[1]); - } - catch (NumberFormatException nfe) { - cancelCookie(request, response, "Cookie token[1] did not contain a valid number"); - return null; - } - - if (isTokenExpired(tokenExpiryTime)) { - cancelCookie(request, response, "Cookie token[1] has expired"); - return null; - } - - // Check the user exists - // Defer lookup until after expiry time checked, to - // possibly avoid expensive lookup - UserDetails userDetails = loadUserDetails(request, response, cookieTokens); - - if (userDetails == null) { - cancelCookie(request, response, "Cookie token[0] contained a username without user associated"); - return null; - } - - if (!isValidUserDetails(request, response, userDetails, cookieTokens)) { - return null; - } - - String receivedTokenSignature = cookieTokens[2]; - String expectedTokenSignature = makeTokenSignature(tokenExpiryTime, userDetails); - - boolean tokenValid = MessageDigest.isEqual( - expectedTokenSignature.getBytes(StandardCharsets.US_ASCII), - receivedTokenSignature.getBytes(StandardCharsets.US_ASCII) - ); - if (!tokenValid) { - cancelCookie(request, response, "Cookie token[2] contained invalid signature"); - return null; - } - - // By this stage we have a valid token - if (logger.isDebugEnabled()) { - logger.debug("Remember-me cookie accepted"); - } - - RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(this.getKey(), userDetails, - userDetails.getAuthorities()); - auth.setDetails(authenticationDetailsSource.buildDetails(request)); + @Override + protected Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails userDetails) { + Authentication auth = super.createSuccessfulAuthentication(request, userDetails); // Ensure this session is linked to the user's seed if (!UserSeedProperty.DISABLE_USER_SEED) { - User user = User.get(auth); + User user = User.get2(auth); UserSeedProperty userSeed = user.getProperty(UserSeedProperty.class); String sessionSeed = userSeed.getSeed(); request.getSession().setAttribute(UserSeedProperty.USER_SESSION_SEED, sessionSeed); @@ -285,58 +182,6 @@ private String findRememberMeCookieValue(HttpServletRequest request, HttpServlet return auth; } - /** - * @return the decoded base64 of the cookie or {@code null} if the value was not correctly encoded - */ - private @CheckForNull String decodeCookieBase64(@NonNull String base64EncodedValue){ - StringBuilder base64EncodedValueBuilder = new StringBuilder(base64EncodedValue); - for (int j = 0; j < base64EncodedValueBuilder.length() % 4; j++) { - base64EncodedValueBuilder.append("="); - } - base64EncodedValue = base64EncodedValueBuilder.toString(); - - try { - // any charset should be fine but better safe than sorry - byte[] decodedPlainValue = Base64.getDecoder().decode(base64EncodedValue.getBytes(StandardCharsets.UTF_8)); - return new String(decodedPlainValue, StandardCharsets.UTF_8); - } catch (IllegalArgumentException e) { - return null; - } - } - - @Override - protected Cookie makeValidCookie(String tokenValueBase64, HttpServletRequest request, long maxAge) { - Cookie cookie = super.makeValidCookie(tokenValueBase64, request, maxAge); - secureCookie(cookie, request); - return cookie; - } - - @Override - protected Cookie makeCancelCookie(HttpServletRequest request) { - Cookie cookie = super.makeCancelCookie(request); - secureCookie(cookie, request); - return cookie; - } - - /** - * Force always the http-only flag and depending on the request, put also the secure flag. - */ - private void secureCookie(Cookie cookie, HttpServletRequest request){ - // if we can mark the cookie HTTP only, do so to protect this cookie even in case of XSS vulnerability. - if (SET_HTTP_ONLY!=null) { - try { - SET_HTTP_ONLY.invoke(cookie,true); - } catch (IllegalAccessException | InvocationTargetException e) { - // ignore - } - } - - // if the user is running Jenkins over HTTPS, we also want to prevent the cookie from leaking in HTTP. - // whether the login is done over HTTPS or not would be a good enough approximation of whether Jenkins runs in - // HTTPS or not, so use that. - cookie.setSecure(request.isSecure()); - } - /** * In addition to the expiration requested by the super class, we also check the expiration is not too far in the future. * Especially to detect maliciously crafted cookie. @@ -344,7 +189,7 @@ private void secureCookie(Cookie cookie, HttpServletRequest request){ @Override protected boolean isTokenExpired(long tokenExpiryTimeMs) { long nowMs = System.currentTimeMillis(); - long maxExpirationMs = TimeUnit.SECONDS.toMillis(tokenValiditySeconds) + nowMs; + long maxExpirationMs = TimeUnit.SECONDS.toMillis(getTokenValiditySeconds()) + nowMs; if(!SKIP_TOO_FAR_EXPIRATION_DATE_CHECK && tokenExpiryTimeMs > maxExpirationMs){ // attempt to use a cookie that has more than the maximum allowed expiration duration // was either created before a change of configuration or maliciously crafted @@ -359,19 +204,21 @@ protected boolean isTokenExpired(long tokenExpiryTimeMs) { return false; } + @VisibleForTesting + @Override + protected int getTokenValiditySeconds() { + return super.getTokenValiditySeconds(); + } + + @VisibleForTesting + @Override + protected String getCookieName() { + return super.getCookieName(); + } + /** * Used to compute the token signature securely. */ private static final HMACConfidentialKey MAC = new HMACConfidentialKey(TokenBasedRememberMeServices.class,"mac"); - private static final Method SET_HTTP_ONLY; - - static { - Method m = null; - try { - m = Cookie.class.getMethod("setHttpOnly", boolean.class); - } catch (NoSuchMethodException x) { // 3.0+ - } - SET_HTTP_ONLY = m; - } } diff --git a/core/src/main/java/hudson/security/UnwrapSecurityExceptionFilter.java b/core/src/main/java/hudson/security/UnwrapSecurityExceptionFilter.java index 8b428de6a454..e894da774a6f 100644 --- a/core/src/main/java/hudson/security/UnwrapSecurityExceptionFilter.java +++ b/core/src/main/java/hudson/security/UnwrapSecurityExceptionFilter.java @@ -23,20 +23,20 @@ */ package hudson.security; -import org.apache.commons.jelly.JellyTagException; -import org.acegisecurity.AcegiSecurityException; -import org.acegisecurity.ui.ExceptionTranslationFilter; - +import java.io.IOException; import javax.servlet.Filter; +import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; -import javax.servlet.FilterChain; -import java.io.IOException; +import org.apache.commons.jelly.JellyTagException; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.access.ExceptionTranslationFilter; /** - * If {@link AcegiSecurityException} caused {@link JellyTagException}, + * If a security exception caused {@link JellyTagException}, * rethrow it accordingly so that {@link ExceptionTranslationFilter} * can pick it up and initiate the redirection. * @@ -61,9 +61,8 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha if (t != null && t instanceof JellyTagException) { JellyTagException jte = (JellyTagException) t; Throwable cause = jte.getCause(); - if (cause instanceof AcegiSecurityException) { - AcegiSecurityException se = (AcegiSecurityException) cause; - throw new ServletException(se); + if (cause instanceof AccessDeniedException || cause instanceof AuthenticationException) { + throw new ServletException(cause); } } throw e; diff --git a/core/src/main/java/hudson/security/UserDetailsServiceProxy.java b/core/src/main/java/hudson/security/UserDetailsServiceProxy.java index 2ac0f39625b1..55db71a12e13 100644 --- a/core/src/main/java/hudson/security/UserDetailsServiceProxy.java +++ b/core/src/main/java/hudson/security/UserDetailsServiceProxy.java @@ -23,24 +23,26 @@ */ package hudson.security; -import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.UserDetailsService; -import org.acegisecurity.userdetails.UsernameNotFoundException; -import org.springframework.dao.DataAccessException; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * {@link UserDetailsService} proxy that delegates to another instance. * * @author Kohsuke Kawaguchi */ +@Restricted(NoExternalUse.class) public class UserDetailsServiceProxy implements UserDetailsService { private volatile UserDetailsService delegate; - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDetailsService uds = delegate; // fix the reference for concurrency support if(uds ==null) - throw new UserMayOrMayNotExistException(Messages.UserDetailsServiceProxy_UnableToQuery(username)); + throw new UserMayOrMayNotExistException2(Messages.UserDetailsServiceProxy_UnableToQuery(username)); return uds.loadUserByUsername(username); } diff --git a/core/src/main/java/hudson/security/UserMayOrMayNotExistException.java b/core/src/main/java/hudson/security/UserMayOrMayNotExistException.java index 6b6e3c9e6547..343dcea1f565 100644 --- a/core/src/main/java/hudson/security/UserMayOrMayNotExistException.java +++ b/core/src/main/java/hudson/security/UserMayOrMayNotExistException.java @@ -24,21 +24,12 @@ package hudson.security; import org.acegisecurity.userdetails.UsernameNotFoundException; -import org.acegisecurity.userdetails.UserDetailsService; /** - * Thrown from {@link UserDetailsService#loadUserByUsername(String)} - * to indicate that the underlying {@link SecurityRealm} is incapable - * of retrieving the information, and furthermore, the system cannot - * tell if such an user exists or not. - * - *

- * This happens, for example, when the security realm is on top of the servlet implementation, - * there's no way of even knowing if an user of a given name exists or not. - * - * @author Kohsuke Kawaguchi * @since 1.280 + * @deprecated use {@link UserMayOrMayNotExistException2} */ +@Deprecated public class UserMayOrMayNotExistException extends UsernameNotFoundException { public UserMayOrMayNotExistException(String msg) { super(msg); @@ -51,4 +42,14 @@ public UserMayOrMayNotExistException(String msg, Object extraInformation) { public UserMayOrMayNotExistException(String msg, Throwable t) { super(msg, t); } + + @Override + public UserMayOrMayNotExistException2 toSpring() { + return new UserMayOrMayNotExistException2(toString(), this); + } + + public static UserMayOrMayNotExistException fromSpring(UserMayOrMayNotExistException2 x) { + return new UserMayOrMayNotExistException(x.toString(), x); + } + } diff --git a/war/src/main/webapp/WEB-INF/security/AbstractPasswordBasedSecurityRealm.groovy b/core/src/main/java/hudson/security/UserMayOrMayNotExistException2.java similarity index 56% rename from war/src/main/webapp/WEB-INF/security/AbstractPasswordBasedSecurityRealm.groovy rename to core/src/main/java/hudson/security/UserMayOrMayNotExistException2.java index 4aaf71b19615..b7814c85ded7 100644 --- a/war/src/main/webapp/WEB-INF/security/AbstractPasswordBasedSecurityRealm.groovy +++ b/core/src/main/java/hudson/security/UserMayOrMayNotExistException2.java @@ -1,18 +1,18 @@ /* * The MIT License - * + * * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi - * + * * 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 @@ -21,29 +21,30 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -/* - Configure Hudson's own user database as the authentication realm. -*/ -import org.acegisecurity.providers.ProviderManager -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider -import org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider -import jenkins.model.Jenkins +package hudson.security; + +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * Thrown from {@link UserDetailsService#loadUserByUsername(String)} + * to indicate that the underlying {@link SecurityRealm} is incapable + * of retrieving the information, and furthermore, the system cannot + * tell if such an user exists or not. + * + *

+ * This happens, for example, when the security realm is on top of the servlet implementation, + * there's no way of even knowing if an user of a given name exists or not. + * + * @since TODO + */ +public class UserMayOrMayNotExistException2 extends UsernameNotFoundException { -authenticationManager(ProviderManager) { - providers = [ - // this does all the hard work. - // injected by the parsing code - authenticator, + public UserMayOrMayNotExistException2(String msg) { + super(msg); + } - // these providers apply everywhere - bean(RememberMeAuthenticationProvider) { - key = Jenkins.get().getSecretKey() - }, - // this doesn't mean we allow anonymous access. - // we just authenticate anonymous users as such, - // so that later authorization can reject them if so configured - bean(AnonymousAuthenticationProvider) { - key = "anonymous" - } - ] + public UserMayOrMayNotExistException2(String msg, Throwable t) { + super(msg, t); + } } diff --git a/core/src/main/java/hudson/security/WhoAmI.java b/core/src/main/java/hudson/security/WhoAmI.java index 35f1e99b9ef2..44d94cb68bae 100644 --- a/core/src/main/java/hudson/security/WhoAmI.java +++ b/core/src/main/java/hudson/security/WhoAmI.java @@ -16,8 +16,6 @@ import jenkins.util.MemoryReductionUtil; import jenkins.model.Jenkins; -import org.acegisecurity.Authentication; -import org.acegisecurity.GrantedAuthority; import org.jenkinsci.Symbol; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -25,6 +23,8 @@ import org.kohsuke.stapler.export.ExportedBean; import edu.umd.cs.findbugs.annotations.NonNull; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; /** * Expose the data needed for /whoAmI, so it can be exposed by Api. @@ -71,7 +71,7 @@ public String getToString() { } private @NonNull Authentication auth() { - return Jenkins.getAuthentication(); + return Jenkins.getAuthentication2(); } @Exported diff --git a/core/src/main/java/hudson/security/csrf/CrumbFilter.java b/core/src/main/java/hudson/security/csrf/CrumbFilter.java index c8a0f80b6129..dcf341f70883 100644 --- a/core/src/main/java/hudson/security/csrf/CrumbFilter.java +++ b/core/src/main/java/hudson/security/csrf/CrumbFilter.java @@ -8,7 +8,6 @@ import hudson.util.MultipartFormDataParser; import jenkins.model.Jenkins; import jenkins.util.SystemProperties; -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; import org.kohsuke.MetaInfServices; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -32,6 +31,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.AnonymousAuthenticationToken; /** * Checks for and validates crumbs on requests that cause state changes, to @@ -139,7 +139,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha } // JENKINS-40344: Don't spam the log just because a session is expired - Level level = Jenkins.getAuthentication() instanceof AnonymousAuthenticationToken ? Level.FINE : Level.WARNING; + Level level = Jenkins.getAuthentication2() instanceof AnonymousAuthenticationToken ? Level.FINE : Level.WARNING; if (crumb != null) { if (crumbIssuer.validateCrumb(httpRequest, crumbSalt, crumb)) { @@ -152,7 +152,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha if (valid) { chain.doFilter(request, response); } else { - LOGGER.log(level, "No valid crumb was included in request for {0} by {1}. Returning {2}.", new Object[] {httpRequest.getRequestURI(), Jenkins.getAuthentication().getName(), HttpServletResponse.SC_FORBIDDEN}); + LOGGER.log(level, "No valid crumb was included in request for {0} by {1}. Returning {2}.", new Object[] {httpRequest.getRequestURI(), Jenkins.getAuthentication2().getName(), HttpServletResponse.SC_FORBIDDEN}); httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN,"No valid crumb was included in the request"); } } else { diff --git a/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java b/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java index 5fc7bf959148..818cef9bc763 100644 --- a/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java +++ b/core/src/main/java/hudson/security/csrf/DefaultCrumbIssuer.java @@ -25,12 +25,12 @@ import net.sf.json.JSONObject; -import org.acegisecurity.Authentication; import org.jenkinsci.Symbol; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.StaplerRequest; +import org.springframework.security.core.Authentication; /** * A crumb issuing algorithm based on the request principal and the remote address. @@ -75,7 +75,7 @@ protected synchronized String issueCrumb(ServletRequest request, String salt) { if (md != null) { HttpServletRequest req = (HttpServletRequest) request; StringBuilder buffer = new StringBuilder(); - Authentication a = Jenkins.getAuthentication(); + Authentication a = Jenkins.getAuthentication2(); buffer.append(a.getName()); buffer.append(';'); if (!isExcludeClientIPFromCrumb()) { diff --git a/core/src/main/java/hudson/slaves/OfflineCause.java b/core/src/main/java/hudson/slaves/OfflineCause.java index 0f10582d6b18..362d6e6c414d 100644 --- a/core/src/main/java/hudson/slaves/OfflineCause.java +++ b/core/src/main/java/hudson/slaves/OfflineCause.java @@ -147,7 +147,7 @@ public UserCause(@CheckForNull User user, @CheckForNull String message) { } private UserCause(String userId, String message) { - super(hudson.slaves.Messages._SlaveComputer_DisconnectedBy(userId != null ? userId : Jenkins.ANONYMOUS.getName(), message)); + super(hudson.slaves.Messages._SlaveComputer_DisconnectedBy(userId != null ? userId : Jenkins.ANONYMOUS2.getName(), message)); this.userId = userId; } diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java index 786da68f8efd..afe6aad491b0 100644 --- a/core/src/main/java/hudson/slaves/SlaveComputer.java +++ b/core/src/main/java/hudson/slaves/SlaveComputer.java @@ -285,7 +285,7 @@ protected Future _connect(boolean forceReconnect) { // do this on another thread so that the lengthy launch operation // (which is typical) won't block UI thread. - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) {// background activity should run like a super user + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) {// background activity should run like a super user log.rewind(); try { for (ComputerListener cl : ComputerListener.all()) @@ -676,7 +676,7 @@ public void onClosed(Channel c, IOException cause) { channel.pinClassLoader(getClass().getClassLoader()); channel.call(new SlaveInitializer(DEFAULT_RING_BUFFER_SIZE)); - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { for (ComputerListener cl : ComputerListener.all()) { cl.preOnline(this,channel,root,taskListener); } @@ -706,7 +706,7 @@ public void onClosed(Channel c, IOException cause) { statusChangeLock.notifyAll(); } } - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { for (ComputerListener cl : ComputerListener.all()) { try { cl.onOnline(this,taskListener); diff --git a/core/src/main/java/hudson/tasks/BuildStep.java b/core/src/main/java/hudson/tasks/BuildStep.java index 2af5f84c0584..ac45c935b35d 100644 --- a/core/src/main/java/hudson/tasks/BuildStep.java +++ b/core/src/main/java/hudson/tasks/BuildStep.java @@ -47,10 +47,10 @@ import java.util.Iterator; import java.util.WeakHashMap; import jenkins.security.QueueItemAuthenticator; -import org.acegisecurity.Authentication; import edu.umd.cs.findbugs.annotations.NonNull; import jenkins.model.Jenkins; +import org.springframework.security.core.Authentication; /** * One step of the whole build process. @@ -102,9 +102,9 @@ public interface BuildStep { *

When this build step needs to make (direct or indirect) permission checks to {@link ACL} * (for example, to locate other projects by name, build them, or access their artifacts) * then it must be run under a specific {@link Authentication}. - * In such a case, the implementation should check whether {@link Jenkins#getAuthentication} is {@link ACL#SYSTEM}, + * In such a case, the implementation should check whether {@link Jenkins#getAuthentication2} is {@link ACL#SYSTEM2}, * and if so, replace it for the duration of this step with {@link Jenkins#ANONYMOUS}. - * (Either using {@link ACL#impersonate}, or by making explicit calls to {@link ACL#hasPermission(Authentication, Permission)}.) + * (Either using {@link ACL#impersonate2}, or by making explicit calls to {@link ACL#hasPermission2(Authentication, Permission)}.) * This would typically happen when no {@link QueueItemAuthenticator} was available, configured, and active. * * @return diff --git a/core/src/main/java/hudson/tasks/BuildTrigger.java b/core/src/main/java/hudson/tasks/BuildTrigger.java index aaa32b12b6f4..3a73f2fedf81 100644 --- a/core/src/main/java/hudson/tasks/BuildTrigger.java +++ b/core/src/main/java/hudson/tasks/BuildTrigger.java @@ -64,13 +64,13 @@ import jenkins.model.ParameterizedJobMixIn; import jenkins.triggers.ReverseBuildTrigger; import net.sf.json.JSONObject; -import org.acegisecurity.Authentication; import org.apache.commons.lang.StringUtils; import org.jenkinsci.Symbol; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; +import org.springframework.security.core.Authentication; /** * Triggers builds of other projects. @@ -200,7 +200,7 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListener lis PrintStream logger = listener.getLogger(); for (Job downstream : jobs) { if (Jenkins.get().getItemByFullName(downstream.getFullName()) != downstream) { - LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[] {Jenkins.getAuthentication().getName(), downstream, build.getParent()}); + LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[] {Jenkins.getAuthentication2().getName(), downstream, build.getParent()}); continue; } if (!downstream.hasPermission(Item.BUILD)) { @@ -297,7 +297,7 @@ public boolean shouldTriggerBuild(AbstractBuild build, TaskListener listener, List actions) { AbstractProject downstream = getDownstreamProject(); if (Jenkins.get().getItemByFullName(downstream.getFullName()) != downstream) { // this checks Item.READ also on parent folders - LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[] {Jenkins.getAuthentication().getName(), downstream, getUpstreamProject()}); + LOGGER.log(Level.WARNING, "Running as {0} cannot even see {1} for trigger from {2}", new Object[] {Jenkins.getAuthentication2().getName(), downstream, getUpstreamProject()}); return false; // do not even issue a warning to build log } if (!downstream.hasPermission(Item.BUILD)) { @@ -408,8 +408,8 @@ public FormValidation doCheck(@AncestorInPath AbstractProject project, @QueryPar if(!(item instanceof ParameterizedJobMixIn.ParameterizedJob)) return FormValidation.error(Messages.BuildTrigger_NotBuildable(projectName)); // check whether the supposed user is expected to be able to build - Authentication auth = Tasks.getAuthenticationOf(project); - if (!item.hasPermission(auth, Item.BUILD)) { + Authentication auth = Tasks.getAuthenticationOf2(project); + if (!item.hasPermission2(auth, Item.BUILD)) { return FormValidation.error(Messages.BuildTrigger_you_have_no_permission_to_build_(projectName)); } hasProjects = true; @@ -430,7 +430,7 @@ public AutoCompletionCandidates doAutoCompleteChildProjects(@QueryParameter Stri public static class ItemListenerImpl extends ItemListener { @Override public void onLocationChanged(final Item item, final String oldFullName, final String newFullName) { - try (ACLContext acl = ACL.as(ACL.SYSTEM)) { + try (ACLContext acl = ACL.as2(ACL.SYSTEM2)) { locationChanged(item, oldFullName, newFullName); } } diff --git a/core/src/main/java/hudson/tasks/Fingerprinter.java b/core/src/main/java/hudson/tasks/Fingerprinter.java index 375f69dc18b0..7eb9bf2a55b9 100644 --- a/core/src/main/java/hudson/tasks/Fingerprinter.java +++ b/core/src/main/java/hudson/tasks/Fingerprinter.java @@ -51,7 +51,6 @@ import hudson.util.PackedMap; import hudson.util.RunList; import net.sf.json.JSONObject; -import org.acegisecurity.AccessDeniedException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.types.FileSet; import org.jenkinsci.Symbol; @@ -78,6 +77,7 @@ import java.util.logging.Logger; import jenkins.model.RunAction2; import jenkins.tasks.SimpleBuildStep; +import org.springframework.security.access.AccessDeniedException; /** * Records fingerprints of the specified files. diff --git a/core/src/main/java/hudson/triggers/SafeTimerTask.java b/core/src/main/java/hudson/triggers/SafeTimerTask.java index bda2fe1657fe..ff91c34aa45e 100644 --- a/core/src/main/java/hudson/triggers/SafeTimerTask.java +++ b/core/src/main/java/hudson/triggers/SafeTimerTask.java @@ -87,7 +87,7 @@ public interface ExceptionRunnable { public final void run() { // background activity gets system credential, // just like executors get it. - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { doRun(); } catch(Throwable t) { LOGGER.log(Level.SEVERE, "Timer task "+this+" failed",t); diff --git a/core/src/main/java/hudson/util/FormFieldValidator.java b/core/src/main/java/hudson/util/FormFieldValidator.java index 1de147efa2b9..2e42b4a3aaa7 100644 --- a/core/src/main/java/hudson/util/FormFieldValidator.java +++ b/core/src/main/java/hudson/util/FormFieldValidator.java @@ -32,7 +32,6 @@ import hudson.security.AccessControlled; import hudson.security.Permission; import jenkins.model.Jenkins; -import org.acegisecurity.AccessDeniedException; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; @@ -48,6 +47,7 @@ import java.util.Locale; import static hudson.Util.fixEmpty; +import org.springframework.security.access.AccessDeniedException; /** * Base class that provides the framework for doing on-the-fly form field validation. diff --git a/core/src/main/java/hudson/util/spring/BeanBuilder.java b/core/src/main/java/hudson/util/spring/BeanBuilder.java deleted file mode 100644 index 33c6e1d98eed..000000000000 --- a/core/src/main/java/hudson/util/spring/BeanBuilder.java +++ /dev/null @@ -1,652 +0,0 @@ -/* - * Copyright 2004-2005 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 - * - * http://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 hudson.util.spring; - -import groovy.lang.Binding; -import groovy.lang.Closure; -import groovy.lang.GString; -import groovy.lang.GroovyObject; -import groovy.lang.GroovyObjectSupport; -import groovy.lang.GroovyShell; -import groovy.lang.MetaClass; -import groovy.lang.MissingMethodException; -import org.apache.commons.lang.ArrayUtils; -import org.codehaus.groovy.control.CompilerConfiguration; -import org.codehaus.groovy.runtime.DefaultGroovyMethods; -import org.codehaus.groovy.runtime.InvokerHelper; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.ManagedList; -import org.springframework.beans.factory.support.ManagedMap; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.web.context.WebApplicationContext; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Map.Entry; - -/** - *

Runtime bean configuration wrapper. Like a Groovy builder, but more of a DSL for - * Spring configuration. Allows syntax like:

- * - *
- * import org.hibernate.SessionFactory
- * import org.apache.commons.dbcp.BasicDataSource
- *
- * BeanBuilder builder = new BeanBuilder()
- * builder.beans {
- *   dataSource(BasicDataSource) {                  // ← invokeMethod
- *      driverClassName = "org.hsqldb.jdbcDriver"
- *      url = "jdbc:hsqldb:mem:grailsDB"
- *      username = "sa"                            // ← setProperty
- *      password = ""
- *      settings = [mynew:"setting"]
- *  }
- *  sessionFactory(SessionFactory) {
- *  	   dataSource = dataSource                 // ← getProperty for retrieving refs
- *  }
- *  myService(MyService) {
- *      nestedBean = { AnotherBean bean->          // ← setProperty with closure for nested bean
- *      		dataSource = dataSource
- *      }
- *  }
- * }
- * 
- *

- * You can also use the Spring IO API to load resources containing beans defined as a Groovy - * script using either the constructors or the loadBeans(Resource[] resources) method - *

- * - * @author Graeme Rocher - * @since 0.4 - * - */ -public class BeanBuilder extends GroovyObjectSupport { - private static final String ANONYMOUS_BEAN = "bean"; - private RuntimeSpringConfiguration springConfig = new DefaultRuntimeSpringConfiguration(); - private BeanConfiguration currentBeanConfig; - private Map deferredProperties = new HashMap<>(); - private ApplicationContext parentCtx; - private Map binding = new HashMap(); - private ClassLoader classLoader = null; - - - public BeanBuilder() { - super(); - } - - public BeanBuilder(ClassLoader classLoader) { - super(); - this.classLoader = classLoader; - } - - public BeanBuilder(ApplicationContext parent) { - super(); - this.parentCtx = parent; - this.springConfig = new DefaultRuntimeSpringConfiguration(parent); - } - - public BeanBuilder(ApplicationContext parent,ClassLoader classLoader) { - super(); - this.parentCtx = parent; - this.springConfig = new DefaultRuntimeSpringConfiguration(parent); - this.classLoader = classLoader; - } - - /** - * Parses the bean definition groovy script. - */ - public void parse(InputStream script) { - parse(script,new Binding()); - } - - /** - * Parses the bean definition groovy script by first exporting the given {@link Binding}. - */ - public void parse(InputStream script, Binding binding) { - if (script==null) - throw new IllegalArgumentException("No script is provided"); - setBinding(binding); - CompilerConfiguration cc = new CompilerConfiguration(); - cc.setScriptBaseClass(ClosureScript.class.getName()); - GroovyShell shell = new GroovyShell(classLoader,binding,cc); - - ClosureScript s = (ClosureScript)shell.parse(new InputStreamReader(script)); - s.setDelegate(this); - s.run(); - } - - /** - * Retrieves the parent ApplicationContext - * @return The parent ApplicationContext - */ - public ApplicationContext getParentCtx() { - return parentCtx; - } - - /** - * Retrieves the RuntimeSpringConfiguration instance used by the BeanBuilder - * @return The RuntimeSpringConfiguration instance - */ - public RuntimeSpringConfiguration getSpringConfig() { - return springConfig; - } - - /** - * Retrieves a BeanDefinition for the given name - * @param name The bean definition - * @return The BeanDefinition instance - */ - public BeanDefinition getBeanDefinition(String name) { - if(!getSpringConfig().containsBean(name)) - return null; - return getSpringConfig().getBeanConfig(name).getBeanDefinition(); - } - - /** - * Retrieves all BeanDefinitions for this BeanBuilder - * - * @return A map of BeanDefinition instances with the bean id as the key - */ - public Map getBeanDefinitions() { - - Map beanDefinitions = new HashMap<>(); - for (String beanName : getSpringConfig().getBeanNames()) { - BeanDefinition bd = getSpringConfig() - .getBeanConfig(beanName) - .getBeanDefinition(); - beanDefinitions.put(beanName, bd); - } - return beanDefinitions; - } - - /** - * Sets the runtime Spring configuration instance to use. This is not necessary to set - * and is configured to default value if not, but is useful for integrating with other - * spring configuration mechanisms @see org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator - * - * @param springConfig The spring config - */ - public void setSpringConfig(RuntimeSpringConfiguration springConfig) { - this.springConfig = springConfig; - } - - - - /** - * This class is used to defer the adding of a property to a bean definition until later - * This is for a case where you assign a property to a list that may not contain bean references at - * that point of assignment, but may later hence it would need to be managed - * - * @author Graeme Rocher - */ - private static class DeferredProperty { - private BeanConfiguration config; - private String name; - private Object value; - - DeferredProperty(BeanConfiguration config, String name, Object value) { - this.config = config; - this.name = name; - this.value = value; - } - - public void setInBeanConfig() { - this.config.addProperty(name, value); - } - } - - /** - * A RuntimeBeanReference that takes care of adding new properties to runtime references - * - * @author Graeme Rocher - * @since 0.4 - * - */ - private class ConfigurableRuntimeBeanReference extends RuntimeBeanReference implements GroovyObject { - - private MetaClass metaClass; - private BeanConfiguration beanConfig; - - public ConfigurableRuntimeBeanReference(String beanName, BeanConfiguration beanConfig) { - this(beanName, beanConfig, false); - } - - public ConfigurableRuntimeBeanReference(String beanName, BeanConfiguration beanConfig, boolean toParent) { - super(beanName, toParent); - this.beanConfig = beanConfig; - if(beanConfig == null) - throw new IllegalArgumentException("Argument [beanConfig] cannot be null"); - this.metaClass = InvokerHelper.getMetaClass(this); - } - - public MetaClass getMetaClass() { - return this.metaClass; - } - - public Object getProperty(String property) { - if(property.equals("beanName")) - return getBeanName(); - else if(property.equals("source")) - return getSource(); - else if(this.beanConfig != null) { - return new WrappedPropertyValue(property,beanConfig.getPropertyValue(property)); - } - else - return this.metaClass.getProperty(this, property); - } - - - - /** - * Wraps a BeanConfiguration property an ensures that any RuntimeReference additions to it are - * deferred for resolution later - * - * @author Graeme Rocher - * @since 0.4 - * - */ - private class WrappedPropertyValue extends GroovyObjectSupport { - private Object propertyValue; - private String propertyName; - public WrappedPropertyValue(String propertyName, Object propertyValue) { - this.propertyValue = propertyValue; - this.propertyName = propertyName; - } - - public void leftShift(Object value) { - InvokerHelper.invokeMethod(propertyValue, "leftShift", value); - if(value instanceof RuntimeBeanReference) { - deferredProperties.put(beanConfig.getName(), new DeferredProperty(beanConfig, propertyName, propertyValue)); - } - } - } - public Object invokeMethod(String name, Object args) { - return this.metaClass.invokeMethod(this, name, args); - } - - public void setMetaClass(MetaClass metaClass) { - this.metaClass = metaClass; - } - - public void setProperty(String property, Object newValue) { - if(!addToDeferred(beanConfig,property, newValue)) { - beanConfig.setPropertyValue(property, newValue); - } - } - } - - /** - * Loads a single Resource into the bean builder - * - * @param resource The resource to load - * @throws IOException When an error occurs - */ - public void loadBeans(Resource resource) throws IOException { - loadBeans(new Resource[]{resource}); - } - - /** - * Loads a set of given beans - * @param resources The resources to load - * @throws IOException - */ - public void loadBeans(Resource[] resources) throws IOException { - Closure beans = new Closure(this){ - @Override - public Object call(Object... args) { - return beans((Closure)args[0]); - } - }; - Binding b = new Binding(); - b.setVariable("beans", beans); - - GroovyShell shell = classLoader != null ? new GroovyShell(classLoader,b) : new GroovyShell(b); - for (Resource resource : resources) { - shell.evaluate(new InputStreamReader(resource.getInputStream())); - } - } - - public void registerBeans(StaticApplicationContext ctx) { - finalizeDeferredProperties(); - springConfig.registerBeansWithContext(ctx); - } - - public RuntimeBeanReference ref(String refName) { - return ref(refName,false); - } - - public RuntimeBeanReference parentRef(String refName) { - return ref(refName,true); - } - - public RuntimeBeanReference ref(String refName, boolean parentRef) { - return new RuntimeBeanReference(refName, parentRef); - } - - /** - * This method is invoked by Groovy when a method that's not defined in Java is invoked. - * We use that as a syntax for bean definition. - */ - public Object methodMissing(String name, Object arg) { - Object[] args = (Object[])arg; - - if(args.length == 0) - throw new MissingMethodException(name,getClass(),args); - - if(args[0] instanceof Closure) { - // abstract bean definition - return invokeBeanDefiningMethod(name, args); - } - else if(args[0] instanceof Class || args[0] instanceof RuntimeBeanReference || args[0] instanceof Map) { - return invokeBeanDefiningMethod(name, args); - } - else if (args.length > 1 && args[args.length -1] instanceof Closure) { - return invokeBeanDefiningMethod(name, args); - } - WebApplicationContext ctx = springConfig.getUnrefreshedApplicationContext(); - MetaClass mc = DefaultGroovyMethods.getMetaClass(ctx); - if(!mc.respondsTo(ctx, name, args).isEmpty()){ - return mc.invokeMethod(ctx,name, args); - } - return this; - } - - public WebApplicationContext createApplicationContext() { - finalizeDeferredProperties(); - return springConfig.getApplicationContext(); - } - - private void finalizeDeferredProperties() { - for (DeferredProperty dp : deferredProperties.values()) { - if (dp.value instanceof List) { - dp.value = manageListIfNecessary((List)dp.value); - } else if (dp.value instanceof Map) { - dp.value = manageMapIfNecessary((Map)dp.value); - } - dp.setInBeanConfig(); - } - deferredProperties.clear(); - } - - private boolean addToDeferred(BeanConfiguration beanConfig,String property, Object newValue) { - if(newValue instanceof List) { - deferredProperties.put(currentBeanConfig.getName()+property,new DeferredProperty(currentBeanConfig, property, newValue)); - return true; - } - else if(newValue instanceof Map) { - deferredProperties.put(currentBeanConfig.getName()+property,new DeferredProperty(currentBeanConfig, property, newValue)); - return true; - } - return false; - } - /** - * This method is called when a bean definition node is called - * - * @param name The name of the bean to define - * @param args The arguments to the bean. The first argument is the class name, the last argument is sometimes a closure. All - * the arguments in between are constructor arguments - * @return The bean configuration instance - */ - private BeanConfiguration invokeBeanDefiningMethod(String name, Object[] args) { - BeanConfiguration old = currentBeanConfig; - try { - if(args[0] instanceof Class) { - Class beanClass = args[0] instanceof Class ? (Class)args[0] : args[0].getClass(); - - if(args.length >= 1) { - if(args[args.length-1] instanceof Closure) { - if(args.length-1 != 1) { - Object[] constructorArgs = ArrayUtils.subarray(args, 1, args.length-1); - filterGStringReferences(constructorArgs); - if(name.equals(ANONYMOUS_BEAN)) - currentBeanConfig = springConfig.createSingletonBean(beanClass,Arrays.asList(constructorArgs)); - else - currentBeanConfig = springConfig.addSingletonBean(name, beanClass, Arrays.asList(constructorArgs)); - } - else { - if(name.equals(ANONYMOUS_BEAN)) - currentBeanConfig = springConfig.createSingletonBean(beanClass); - else - currentBeanConfig = springConfig.addSingletonBean(name, beanClass); - } - } - else { - Object[] constructorArgs = ArrayUtils.subarray(args, 1, args.length); - filterGStringReferences(constructorArgs); - if(name.equals(ANONYMOUS_BEAN)) - currentBeanConfig = springConfig.createSingletonBean(beanClass,Arrays.asList(constructorArgs)); - else - currentBeanConfig = springConfig.addSingletonBean(name, beanClass, Arrays.asList(constructorArgs)); - } - - } - } - else if(args[0] instanceof RuntimeBeanReference) { - currentBeanConfig = springConfig.addSingletonBean(name); - currentBeanConfig.setFactoryBean(((RuntimeBeanReference)args[0]).getBeanName()); - } - else if(args[0] instanceof Map) { - currentBeanConfig = springConfig.addSingletonBean(name); - Map.Entry factoryBeanEntry = (Map.Entry)((Map)args[0]).entrySet().iterator().next(); - currentBeanConfig.setFactoryBean(factoryBeanEntry.getKey().toString()); - currentBeanConfig.setFactoryMethod(factoryBeanEntry.getValue().toString()); - } - else if(args[0] instanceof Closure) { - currentBeanConfig = springConfig.addAbstractBean(name); - } - else { - Object[] constructorArgs; - if(args[args.length-1] instanceof Closure) { - constructorArgs= ArrayUtils.subarray(args, 0, args.length-1); - } - else { - constructorArgs= ArrayUtils.subarray(args, 0, args.length); - } - filterGStringReferences(constructorArgs); - currentBeanConfig = new DefaultBeanConfiguration(name, null, Arrays.asList(constructorArgs)); - springConfig.addBeanConfiguration(name,currentBeanConfig); - } - if(args[args.length-1] instanceof Closure) { - Closure callable = (Closure)args[args.length-1]; - callable.setDelegate(this); - callable.setResolveStrategy(Closure.DELEGATE_FIRST); - callable.call(new Object[]{currentBeanConfig}); - - } - - return currentBeanConfig; - } finally { - currentBeanConfig = old; - } - } - - private void filterGStringReferences(Object[] constructorArgs) { - for (int i = 0; i < constructorArgs.length; i++) { - Object constructorArg = constructorArgs[i]; - if(constructorArg instanceof GString) constructorArgs[i] = constructorArg.toString(); - } - } - - /** - * When an methods argument is only a closure it is a set of bean definitions - * - * @param callable The closure argument - */ - public BeanBuilder beans(Closure callable) { - callable.setDelegate(this); - // callable.setResolveStrategy(Closure.DELEGATE_FIRST); - callable.call(); - finalizeDeferredProperties(); - - return this; - } - - /** - * This method overrides property setting in the scope of the BeanBuilder to set - * properties on the current BeanConfiguration - */ - @Override - public void setProperty(String name, Object value) { - if(currentBeanConfig != null) { - if(value instanceof GString)value = value.toString(); - if(addToDeferred(currentBeanConfig, name, value)) { - return; - } - else if(value instanceof Closure) { - BeanConfiguration current = currentBeanConfig; - try { - Closure callable = (Closure)value; - - Class parameterType = callable.getParameterTypes()[0]; - if(parameterType.equals(Object.class)) { - currentBeanConfig = springConfig.createSingletonBean(""); - callable.call(new Object[]{currentBeanConfig}); - } - else { - currentBeanConfig = springConfig.createSingletonBean(parameterType); - callable.call(null); - } - - value = currentBeanConfig.getBeanDefinition(); - } - finally { - currentBeanConfig = current; - } - } - currentBeanConfig.addProperty(name, value); - } else { - binding.put(name,value); - } - } - - /** - * Checks whether there are any runtime refs inside a Map and converts - * it to a ManagedMap if necessary - * - * @param value The current map - * @return A ManagedMap or a normal map - */ - private Object manageMapIfNecessary(Map value) { - boolean containsRuntimeRefs = false; - for (Entry e : value.entrySet()) { - Object v = e.getValue(); - if (v instanceof RuntimeBeanReference) { - containsRuntimeRefs = true; - } - if (v instanceof BeanConfiguration) { - BeanConfiguration c = (BeanConfiguration) v; - e.setValue(c.getBeanDefinition()); - containsRuntimeRefs = true; - } - } - if(containsRuntimeRefs) { -// return new ManagedMap(map); - ManagedMap m = new ManagedMap(); - m.putAll(value); - return m; - } - return value; - } - - /** - * Checks whether there are any runtime refs inside the list and - * converts it to a ManagedList if necessary - * - * @param value The object that represents the list - * @return Either a new list or a managed one - */ - private Object manageListIfNecessary(List value) { - boolean containsRuntimeRefs = false; - for (ListIterator i = value.listIterator(); i.hasNext();) { - Object e = i.next(); - if(e instanceof RuntimeBeanReference) { - containsRuntimeRefs = true; - } - if (e instanceof BeanConfiguration) { - BeanConfiguration c = (BeanConfiguration) e; - i.set(c.getBeanDefinition()); - containsRuntimeRefs = true; - } - } - if(containsRuntimeRefs) { - List tmp = new ManagedList(); - tmp.addAll(value); - value = tmp; - } - return value; - } - - /** - * This method overrides property retrieval in the scope of the BeanBuilder to either: - * - * a) Retrieve a variable from the bean builder's binding if it exits - * b) Retrieve a RuntimeBeanReference for a specific bean if it exists - * c) Otherwise just delegate to super.getProperty which will resolve properties from the BeanBuilder itself - */ - @Override - public Object getProperty(String name) { - if(binding.containsKey(name)) { - return binding.get(name); - } - else { - if(springConfig.containsBean(name)) { - BeanConfiguration beanConfig = springConfig.getBeanConfig(name); - if(beanConfig != null) { - return new ConfigurableRuntimeBeanReference(name, springConfig.getBeanConfig(name) ,false); - } - else - return new RuntimeBeanReference(name,false); - } - // this is to deal with the case where the property setter is the last - // statement in a closure (hence the return value) - else if(currentBeanConfig != null) { - if(currentBeanConfig.hasProperty(name)) - return currentBeanConfig.getPropertyValue(name); - else { - DeferredProperty dp = deferredProperties.get(currentBeanConfig.getName()+name); - if(dp!=null) { - return dp.value; - } - else { - return super.getProperty(name); - } - } - } - else { - return super.getProperty(name); - } - } - } - - /** - * Sets the binding (the variables available in the scope of the BeanBuilder) - * @param b The Binding instance - */ - public void setBinding(Binding b) { - this.binding = b.getVariables(); - } - -} diff --git a/core/src/main/java/hudson/util/spring/BeanConfiguration.java b/core/src/main/java/hudson/util/spring/BeanConfiguration.java deleted file mode 100644 index 5d01a37db338..000000000000 --- a/core/src/main/java/hudson/util/spring/BeanConfiguration.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2004-2005 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 - * - * http://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 hudson.util.spring; - -import org.springframework.beans.factory.support.AbstractBeanDefinition; - -/** - * An interface that represent a runtime bean configuration - * - * Credit must go to Solomon Duskis and the - * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring - * - * @author Graeme - * @since 0.3 - * - */ - -public interface BeanConfiguration { - String AUTOWIRE_BY_TYPE = "byType"; - String AUTOWIRE_BY_NAME = "byName"; - - /** - * - * @return The name of the bean - */ - String getName(); - - /** - * - * @return True if the bean is singleton - */ - boolean isSingleton(); - - /** - * - * @return The Spring bean definition instance - */ - AbstractBeanDefinition getBeanDefinition(); - - /** - * Adds a property value to this bean - * @param propertyName The name of the property - * @param propertyValue The value of the property - * - * @return Returns this bean configuration - */ - BeanConfiguration addProperty(String propertyName, Object propertyValue); - - /** - * Sets the name of the method to call when destroying the bean - * - * @param methodName The method name - * @return This bean configuration - */ - BeanConfiguration setDestroyMethod(String methodName); - - /** - * Sets the names of the beans this bean configuration depends on - * - * @param dependsOn Bean names it depends on - * @return This bean configuration - */ - BeanConfiguration setDependsOn(String[] dependsOn); - - /** - * - * @param beanName - * @return This BeanConfiguration - */ - BeanConfiguration setFactoryBean(String beanName); - - /** - * - * @param methodName - * @return This BeanConfiguration - */ - BeanConfiguration setFactoryMethod(String methodName); - - /** - * Sets the autowire type, either "byType" or "byName" - * - * @param type The type - * @return This BeanConfiguration - */ - BeanConfiguration setAutowire(String type); - - - /** - * Sets the name of the bean in the app ctx - * @param beanName The bean name - */ - void setName(String beanName); - - /** - * Returns true if the bean config has the name property set - * @param name The name of the property - * @return True if it does have a property with the given name - */ - boolean hasProperty(String name); - - /** - * Returns the value of the given property or throws a MissingPropertyException - * - * @param name The name of the property - * @return The value of the property - */ - Object getPropertyValue(String name); - - /** - * Sets a property value on the bean configuration - * - * @param property The name of the property - * @param newValue The value - */ - void setPropertyValue(String property, Object newValue); - - /** - * Sets the BeanConfiguration as an Abstract bean definition - * @param isAbstract Whether its abstract or not - * @return This BeanConfiguration object - */ - BeanConfiguration setAbstract(boolean isAbstract); - - /** - * Sets the name of the parent bean - * - * @param name Either a string which is the name of the bean, a RuntimeBeanReference or a BeanConfiguration - */ - void setParent(Object name); - -} diff --git a/core/src/main/java/hudson/util/spring/ClosureScript.java b/core/src/main/java/hudson/util/spring/ClosureScript.java deleted file mode 100644 index 1784219f2155..000000000000 --- a/core/src/main/java/hudson/util/spring/ClosureScript.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi - * - * 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 hudson.util.spring; - -import groovy.lang.Binding; -import groovy.lang.Closure; -import groovy.lang.GroovyObject; -import groovy.lang.MissingMethodException; -import groovy.lang.MissingPropertyException; -import groovy.lang.Script; - -/** - * {@link Script} that performs method invocations and property access like {@link Closure} does. - * - *

- * For example, when the script is: - * - *

- * a = 1;
- * b(2);
- * 
- * - *

- * Using {@link ClosureScript} as the base class would run it as: - * - *

- * delegate.a = 1;
- * delegate.b(2);
- * 
- * - * ... whereas in plain {@link Script}, this will be run as: - * - *
- * binding.setProperty("a",1);
- * ((Closure)binding.getProperty("b")).call(2);
- * 
- * - * @author Kohsuke Kawaguchi - */ -// TODO: moved to stapler -public abstract class ClosureScript extends Script { - private GroovyObject delegate; - - protected ClosureScript() { - super(); - } - - protected ClosureScript(Binding binding) { - super(binding); - } - - /** - * Sets the delegation target. - */ - public void setDelegate(GroovyObject delegate) { - this.delegate = delegate; - } - - @Override - public Object invokeMethod(String name, Object args) { - try { - return delegate.invokeMethod(name,args); - } catch (MissingMethodException mme) { - return super.invokeMethod(name, args); - } - } - - @Override - public Object getProperty(String property) { - try { - return delegate.getProperty(property); - } catch (MissingPropertyException e) { - return super.getProperty(property); - } - } - - @Override - public void setProperty(String property, Object newValue) { - try { - delegate.setProperty(property,newValue); - } catch (MissingPropertyException e) { - super.setProperty(property,newValue); - } - } -} diff --git a/core/src/main/java/hudson/util/spring/DefaultBeanConfiguration.java b/core/src/main/java/hudson/util/spring/DefaultBeanConfiguration.java deleted file mode 100644 index 7e2cf68b32a4..000000000000 --- a/core/src/main/java/hudson/util/spring/DefaultBeanConfiguration.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright 2004-2005 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 - * - * http://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 hudson.util.spring; - -import groovy.lang.GroovyObjectSupport; -import org.apache.commons.lang.StringUtils; -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; -import org.springframework.beans.factory.config.ConstructorArgumentValues; -import org.springframework.beans.factory.config.RuntimeBeanReference; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.ChildBeanDefinition; -import org.springframework.beans.factory.support.RootBeanDefinition; - -import java.util.*; - -/** - * Default implementation of the BeanConfiguration interface - * - * Credit must go to Solomon Duskis and the - * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring - * - * @author Graeme - * @since 0.3 - * - */ -class DefaultBeanConfiguration extends GroovyObjectSupport implements BeanConfiguration { - - private static final String AUTOWIRE = "autowire"; - private static final String CONSTRUCTOR_ARGS = "constructorArgs"; - private static final String DESTROY_METHOD = "destroyMethod"; - private static final String FACTORY_BEAN = "factoryBean"; - private static final String FACTORY_METHOD = "factoryMethod"; - private static final String INIT_METHOD = "initMethod"; - private static final String BY_NAME = "byName"; - private static final String PARENT = "parent"; - private static final String BY_TYPE = "byType"; - private static final String BY_CONSTRUCTOR = "constructor"; - private static final Set DYNAMIC_PROPS = new HashSet<>(Arrays.asList(AUTOWIRE, CONSTRUCTOR_ARGS, DESTROY_METHOD, FACTORY_BEAN, FACTORY_METHOD, INIT_METHOD, BY_NAME, BY_TYPE, BY_CONSTRUCTOR)); - private String parentName; - - @Override - public Object getProperty(String property) { - getBeanDefinition(); - if(wrapper.isReadableProperty(property)) { - return wrapper.getPropertyValue(property); - } - else if(DYNAMIC_PROPS.contains(property)) { - return null; - } - return super.getProperty(property); - } - - @Override - public void setProperty(String property, Object newValue) { - if(PARENT.equals(property)) { - setParent(newValue); - } - else { - AbstractBeanDefinition bd = getBeanDefinition(); - if(AUTOWIRE.equals(property)) { - if(BY_NAME.equals(newValue)) { - bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); - } - else if(BY_TYPE.equals(newValue)) { - bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE); - } - else if(Boolean.TRUE.equals(newValue)) { - bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME); - } - else if(BY_CONSTRUCTOR.equals(newValue)) { - bd.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR); - } - } - // constructorArgs - else if(CONSTRUCTOR_ARGS.equals(property) && newValue instanceof List) { - ConstructorArgumentValues cav = new ConstructorArgumentValues(); - List args = (List)newValue; - for (Object e : args) { - cav.addGenericArgumentValue(e); - } - bd.setConstructorArgumentValues(cav); - } - // destroyMethod - else if(DESTROY_METHOD.equals(property)) { - if(newValue != null) - bd.setDestroyMethodName(newValue.toString()); - } - // factoryBean - else if(FACTORY_BEAN.equals(property)) { - if(newValue != null) - bd.setFactoryBeanName(newValue.toString()); - } - // factoryMethod - else if(FACTORY_METHOD.equals(property)) { - if(newValue != null) - bd.setFactoryMethodName(newValue.toString()); - } - // initMethod - else if(INIT_METHOD.equals(property)) { - if(newValue != null) - bd.setInitMethodName(newValue.toString()); - } - else if(wrapper.isWritableProperty(property)) { - - wrapper.setPropertyValue(property, newValue); - } - // autowire - else { - super.setProperty(property, newValue); - } - } - } - - private Class clazz; - private String name; - private boolean singleton = true; - private AbstractBeanDefinition definition; - private Collection constructorArgs = Collections.EMPTY_LIST; - private BeanWrapper wrapper; - - public DefaultBeanConfiguration(String name, Class clazz) { - this.name = name; - this.clazz = clazz; - } - - public DefaultBeanConfiguration(String name, Class clazz, boolean prototype) { - this(name,clazz,Collections.EMPTY_LIST); - this.singleton = !prototype; - } - - public DefaultBeanConfiguration(String name) { - this(name,null,Collections.EMPTY_LIST); - } - - public DefaultBeanConfiguration(Class clazz2) { - this.clazz = clazz2; - } - - public DefaultBeanConfiguration(String name2, Class clazz2, Collection args) { - this.name = name2; - this.clazz = clazz2; - this.constructorArgs = args; - } - - public DefaultBeanConfiguration(String name2, boolean prototype) { - this(name2,null,Collections.EMPTY_LIST); - this.singleton = !prototype; - } - - public DefaultBeanConfiguration(Class clazz2, Collection constructorArguments) { - this.clazz = clazz2; - this.constructorArgs = constructorArguments; - } - - public String getName() { - return this.name; - } - - public boolean isSingleton() { - return this.singleton ; - } - - public AbstractBeanDefinition getBeanDefinition() { - if (definition == null) - definition = createBeanDefinition(); - return definition; - } - - protected AbstractBeanDefinition createBeanDefinition() { - AbstractBeanDefinition bd; - if(constructorArgs.size() > 0) { - ConstructorArgumentValues cav = new ConstructorArgumentValues(); - for (Object constructorArg : constructorArgs) { - cav.addGenericArgumentValue(constructorArg); - } - if(StringUtils.isBlank(parentName)) { - bd = new RootBeanDefinition(clazz,cav,null); - } - else { - bd = new ChildBeanDefinition(parentName,clazz,cav, null); - } - bd.setSingleton(singleton); - } - else { - if(StringUtils.isBlank(parentName)) { - bd = new RootBeanDefinition(clazz,singleton); - } - else { - bd = new ChildBeanDefinition(parentName,clazz, null,null); - bd.setSingleton(singleton); - } - - } - wrapper = new BeanWrapperImpl(bd); - return bd; - } - - public BeanConfiguration addProperty(String propertyName, Object propertyValue) { - if(propertyValue instanceof BeanConfiguration) { - propertyValue = ((BeanConfiguration)propertyValue).getBeanDefinition(); - } - getBeanDefinition() - .getPropertyValues() - .addPropertyValue(propertyName,propertyValue); - - return this; - } - - public BeanConfiguration setDestroyMethod(String methodName) { - getBeanDefinition().setDestroyMethodName(methodName); - return this; - } - - public BeanConfiguration setDependsOn(String[] dependsOn) { - getBeanDefinition().setDependsOn(dependsOn); - return this; - } - - public BeanConfiguration setFactoryBean(String beanName) { - getBeanDefinition().setFactoryBeanName(beanName); - - return this; - } - - public BeanConfiguration setFactoryMethod(String methodName) { - getBeanDefinition().setFactoryMethodName(methodName); - return this; - } - - public BeanConfiguration setAutowire(String type) { - if("byName".equals(type)) { - getBeanDefinition().setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME); - } - else if("byType".equals(type)){ - getBeanDefinition().setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); - } - return this; - } - - public void setName(String beanName) { - this.name = beanName; - } - - public Object getPropertyValue(String name) { - return getBeanDefinition() - .getPropertyValues() - .getPropertyValue(name) - .getValue(); - } - - public boolean hasProperty(String name) { - return getBeanDefinition().getPropertyValues().contains(name); - } - - public void setPropertyValue(String property, Object newValue) { - getBeanDefinition().getPropertyValues().addPropertyValue(property, newValue); - } - - public BeanConfiguration setAbstract(boolean isAbstract) { - getBeanDefinition().setAbstract(isAbstract); - return this; - } - - public void setParent(Object obj) { - if(obj == null) throw new IllegalArgumentException("Parent bean cannot be set to a null runtime bean reference!"); - if(obj instanceof String) - this.parentName = (String)obj; - else if(obj instanceof RuntimeBeanReference) { - this.parentName = ((RuntimeBeanReference)obj).getBeanName(); - } - else if(obj instanceof BeanConfiguration) { - this.parentName = ((BeanConfiguration)obj).getName(); - } - } - - -} diff --git a/core/src/main/java/hudson/util/spring/DefaultRuntimeSpringConfiguration.java b/core/src/main/java/hudson/util/spring/DefaultRuntimeSpringConfiguration.java deleted file mode 100644 index 545402b14176..000000000000 --- a/core/src/main/java/hudson/util/spring/DefaultRuntimeSpringConfiguration.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2004-2005 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 - * - * http://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 hudson.util.spring; - -import org.springframework.beans.PropertyValue; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.StaticWebApplicationContext; - -import javax.servlet.ServletContext; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A programmable runtime Spring configuration that allows a spring ApplicationContext - * to be constructed at runtime - * - * Credit must go to Solomon Duskis and the - * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring - * - * @author Graeme - * @since 0.3 - * - */ -class DefaultRuntimeSpringConfiguration implements RuntimeSpringConfiguration { - private static final Logger LOGGER = Logger.getLogger(DefaultRuntimeSpringConfiguration.class.getName()); - private StaticWebApplicationContext context; - private Map beanConfigs = new HashMap<>(); - private Map beanDefinitions = new HashMap<>(); - private List beanNames = new ArrayList<>(); - - public DefaultRuntimeSpringConfiguration() { - super(); - this.context = new StaticWebApplicationContext(); - } - - public DefaultRuntimeSpringConfiguration(ApplicationContext parent) { - super(); - this.context = new StaticWebApplicationContext(); - context.setParent(parent); -// if(parent != null){ -// trySettingClassLoaderOnContextIfFoundInParent(parent); -// } - } - -// private void trySettingClassLoaderOnContextIfFoundInParent(ApplicationContext parent) { -// try{ -// Object classLoader = parent.getBean(GrailsRuntimeConfigurator.CLASS_LOADER_BEAN); -// if(classLoader instanceof ClassLoader){ -// // this.context.setClassLoader((ClassLoader) classLoader); -// } -// }catch(NoSuchBeanDefinitionException nsbde){ -// //ignore, we tried our best -// } -// } - - - public BeanConfiguration addSingletonBean(String name, Class clazz) { - BeanConfiguration bc = new DefaultBeanConfiguration(name,clazz); - registerBeanConfiguration(name, bc); - return bc; - } - - public BeanConfiguration addPrototypeBean(String name, Class clazz) { - BeanConfiguration bc = new DefaultBeanConfiguration(name,clazz,true); - registerBeanConfiguration(name, bc); - return bc; - } - - public WebApplicationContext getApplicationContext() { - registerBeansWithContext(context); - context.refresh(); - return context; - } - - public WebApplicationContext getUnrefreshedApplicationContext() { - return context; - } - - public BeanConfiguration addSingletonBean(String name) { - BeanConfiguration bc = new DefaultBeanConfiguration(name); - registerBeanConfiguration(name, bc); - return bc; - } - - public BeanConfiguration createSingletonBean(Class clazz) { - return new DefaultBeanConfiguration(clazz); - } - - public BeanConfiguration addSingletonBean(String name, Class clazz, Collection args) { - BeanConfiguration bc = new DefaultBeanConfiguration(name,clazz,args); - registerBeanConfiguration(name, bc); - return bc; - } - - public BeanConfiguration addPrototypeBean(String name) { - BeanConfiguration bc = new DefaultBeanConfiguration(name,true); - registerBeanConfiguration(name, bc); - return bc; - } - - private void registerBeanConfiguration(String name, BeanConfiguration bc) { - beanConfigs.put(name,bc); - beanNames.add(name); - } - - public BeanConfiguration createSingletonBean(Class clazz, Collection constructorArguments) { - return new DefaultBeanConfiguration(clazz, constructorArguments); - } - - public void setServletContext(ServletContext context) { - this.context.setServletContext(context); - } - - public BeanConfiguration createPrototypeBean(String name) { - return new DefaultBeanConfiguration(name,true); - } - - public BeanConfiguration createSingletonBean(String name) { - return new DefaultBeanConfiguration(name); - } - - public void addBeanConfiguration(String beanName, BeanConfiguration beanConfiguration) { - beanConfiguration.setName(beanName); - registerBeanConfiguration(beanName, beanConfiguration); - } - - public void addBeanDefinition(String name, BeanDefinition bd) { - beanDefinitions.put(name,bd); - beanNames.add(name); - } - - public boolean containsBean(String name) { - return beanNames .contains(name); - } - - public BeanConfiguration getBeanConfig(String name) { - return beanConfigs.get(name); - } - - public AbstractBeanDefinition createBeanDefinition(String name) { - if(containsBean(name)) { - if(beanDefinitions.containsKey(name)) - return (AbstractBeanDefinition)beanDefinitions.get(name); - else if(beanConfigs.containsKey(name)) - return beanConfigs.get(name).getBeanDefinition(); - } - return null; - } - - public void registerPostProcessor(BeanFactoryPostProcessor processor) { - this.context.addBeanFactoryPostProcessor(processor); - } - - - - public List getBeanNames() { - return beanNames; - } - - public void registerBeansWithContext(StaticApplicationContext applicationContext) { - for (BeanConfiguration bc : beanConfigs.values()) { - if (LOGGER.isLoggable(Level.FINER)) { - LOGGER.finer("[RuntimeConfiguration] Registering bean [" + bc.getName() + "]"); - if (LOGGER.isLoggable(Level.FINEST)) { - PropertyValue[] pvs = bc.getBeanDefinition() - .getPropertyValues() - .getPropertyValues(); - for (PropertyValue pv : pvs) { - LOGGER.finest("[RuntimeConfiguration] With property [" + pv.getName() + "] set to [" + pv.getValue() + "]"); - } - } - } - - - if (applicationContext.containsBeanDefinition(bc.getName())) - applicationContext.removeBeanDefinition(bc.getName()); - - applicationContext.registerBeanDefinition(bc.getName(), - bc.getBeanDefinition()); - } - beanDefinitions.forEach((key, bd) -> { - if (LOGGER.isLoggable(Level.FINER)) { - LOGGER.finer("[RuntimeConfiguration] Registering bean [" + key + "]"); - if (LOGGER.isLoggable(Level.FINEST)) { - for (PropertyValue pv : bd.getPropertyValues().getPropertyValues()) { - LOGGER.finest("[RuntimeConfiguration] With property [" + pv.getName() + "] set to [" + pv.getValue() + "]"); - } - } - } - if (applicationContext.containsBean(key)) { - applicationContext.removeBeanDefinition(key); - } - - applicationContext.registerBeanDefinition(key, bd); - - }); - } - - /** - * Adds an abstract bean and returns the BeanConfiguration instance - * - * @param name The name of the bean - * @return The BeanConfiguration object - */ - public BeanConfiguration addAbstractBean(String name) { - BeanConfiguration bc = new DefaultBeanConfiguration(name); - bc.setAbstract(true); - registerBeanConfiguration(name, bc); - - return bc; - } -} diff --git a/core/src/main/java/hudson/util/spring/RuntimeSpringConfiguration.java b/core/src/main/java/hudson/util/spring/RuntimeSpringConfiguration.java deleted file mode 100644 index e17bd0662cfc..000000000000 --- a/core/src/main/java/hudson/util/spring/RuntimeSpringConfiguration.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2004-2005 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 - * - * http://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 hudson.util.spring; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.web.context.ServletContextAware; -import org.springframework.web.context.WebApplicationContext; - -import javax.servlet.ServletContext; -import java.util.Collection; -import java.util.List; - -/** - * A programmable runtime Spring configuration that allows a spring ApplicationContext - * to be constructed at runtime - * - * Credit must go to Solomon Duskis and the - * article: http://jroller.com/page/Solomon?entry=programmatic_configuration_in_spring - * - * @author Graeme - * @since 0.3 - * - */ -interface RuntimeSpringConfiguration extends ServletContextAware { - - /** - * Adds a singleton bean definition - * - * @param name The name of the bean - * @param clazz The class of the bean - * @return A BeanConfiguration instance - */ - BeanConfiguration addSingletonBean(String name, Class clazz); - - WebApplicationContext getUnrefreshedApplicationContext(); - /** - * Adds a prototype bean definition - * - * @param name The name of the bean - * @param clazz The class of the bean - * @return A BeanConfiguration instance - */ - BeanConfiguration addPrototypeBean(String name, Class clazz); - - /** - * Retrieves the application context from the current state - * - * @return The ApplicationContext instance - */ - WebApplicationContext getApplicationContext(); - - /** - * Adds an empty singleton bean configuration - * @param name The name of the singleton bean - * - * @return A BeanConfiguration instance - */ - BeanConfiguration addSingletonBean(String name); - - /** - * Adds an empty prototype bean configuration - * - * @param name The name of the prototype bean - * @return A BeanConfiguration instance - */ - BeanConfiguration addPrototypeBean(String name); - - /** - * Creates a singleton bean configuration. Differs from addSingletonBean in that - * it doesn't add the bean to the list of bean references. Hence should be used for - * creating nested beans - * - * @param clazz - * @return A BeanConfiguration instance - */ - BeanConfiguration createSingletonBean(Class clazz); - - /** - * Creates a new singleton bean and adds it to the list of bean references - * - * @param name The name of the bean - * @param clazz The class of the bean - * @param args The constructor arguments of the bean - * @return A BeanConfiguration instance - */ - BeanConfiguration addSingletonBean(String name, Class clazz, Collection args); - - /** - * Creates a singleton bean configuration. Differs from addSingletonBean in that - * it doesn't add the bean to the list of bean references. Hence should be used for - * creating nested beans - * - * @param clazz The bean class - * @param constructorArguments The constructor arguments - * @return A BeanConfiguration instance - */ - BeanConfiguration createSingletonBean(Class clazz, Collection constructorArguments); - - /** - * Sets the servlet context - * - * @param context The servlet Context - */ - void setServletContext(ServletContext context); - - /** - * Creates a new prototype bean configuration. Differs from addPrototypeBean in that - * it doesn't add the bean to the list of bean references to be created via the getApplicationContext() - * method, hence can be used for creating nested beans - * - * @param name The bean name - * @return A BeanConfiguration instance - * - */ - BeanConfiguration createPrototypeBean(String name); - - /** - * Creates a new singleton bean configuration. Differs from addSingletonBean in that - * it doesn't add the bean to the list of bean references to be created via the getApplicationContext() - * method, hence can be used for creating nested beans - * - * @param name The bean name - * @return A BeanConfiguration instance - * - */ - BeanConfiguration createSingletonBean(String name); - - /** - * Adds a bean configuration to the list of beans to be created - * - * @param beanName The name of the bean in the context - * @param beanConfiguration The BeanConfiguration instance - */ - void addBeanConfiguration(String beanName, BeanConfiguration beanConfiguration); - /** - * Adds a Spring BeanDefinition. Differs from BeanConfiguration which is a factory class - * for creating BeanDefinition instances - * @param name The name of the bean - * @param bd The BeanDefinition instance - */ - void addBeanDefinition(String name, BeanDefinition bd); - - /** - * Returns whether the runtime spring config contains the specified bean - * - * @param name The bean name - * @return True if it does - */ - boolean containsBean(String name); - /** - * Returns the BeanConfiguration for the specified name - * @param name The name of the bean configuration - * @return The BeanConfiguration - */ - BeanConfiguration getBeanConfig(String name); - - /** - * Creates and returns the BeanDefinition that is registered within the given name or returns null - * - * @param name The name of the bean definition - * @return A BeanDefinition - */ - AbstractBeanDefinition createBeanDefinition(String name); - - /** - * Registers a bean factory post processor with the context - * - * @param processor The BeanFactoryPostProcessor instance - */ - void registerPostProcessor(BeanFactoryPostProcessor processor); - - List getBeanNames(); - - /** - * Registers the beans held within this RuntimeSpringConfiguration instance with the given ApplicationContext - * - * @param applicationContext The ApplicationContext instance - */ - void registerBeansWithContext(StaticApplicationContext applicationContext); - - /** - * Adds an abstract bean definition to the bean factory and returns the BeanConfiguration object - * - * @param name The name of the bean - * @return The BeanConfiguration object - */ - BeanConfiguration addAbstractBean(String name); -} diff --git a/core/src/main/java/hudson/util/spring/package.html b/core/src/main/java/hudson/util/spring/package.html deleted file mode 100644 index fec56708535d..000000000000 --- a/core/src/main/java/hudson/util/spring/package.html +++ /dev/null @@ -1,41 +0,0 @@ - - - -Spring Bean Builder by using Groovy. - -

-This code is originally from Grails -but modifications are made since then to make the syntax more consistent. - -

Changes to the original code

-

- Our version has support for getting rid of surrounding "bb.beans { ... }" if the script - is parsed via the BeanBuilder.parse() method. -

-

- Anonymous bean definition syntax is changed to bean(CLASS) {...} from - {CLASS _ -> ...} to increase consistency with named bean definition. -

- \ No newline at end of file diff --git a/core/src/main/java/jenkins/InitReactorRunner.java b/core/src/main/java/jenkins/InitReactorRunner.java index be183b47e17c..965e847e9dc5 100644 --- a/core/src/main/java/jenkins/InitReactorRunner.java +++ b/core/src/main/java/jenkins/InitReactorRunner.java @@ -47,7 +47,7 @@ public void run(Reactor reactor) throws InterruptedException, ReactorException, else es = Executors.newSingleThreadExecutor(new NamingThreadFactory(new DaemonThreadFactory(), "InitReactorRunner")); try { - reactor.execute(new ImpersonatingExecutorService(es, ACL.SYSTEM), buildReactorListener()); + reactor.execute(new ImpersonatingExecutorService(es, ACL.SYSTEM2), buildReactorListener()); } finally { es.shutdownNow(); // upon a successful return the executor queue should be empty. Upon an exception, we want to cancel all pending tasks } diff --git a/core/src/main/java/jenkins/install/SetupWizard.java b/core/src/main/java/jenkins/install/SetupWizard.java index f0500bbfd7ea..087e632728d9 100644 --- a/core/src/main/java/jenkins/install/SetupWizard.java +++ b/core/src/main/java/jenkins/install/SetupWizard.java @@ -33,10 +33,6 @@ import jenkins.security.seed.UserSeedProperty; import jenkins.util.SystemProperties; import jenkins.util.UrlHelper; -import org.acegisecurity.Authentication; -import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.userdetails.UsernameNotFoundException; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.HttpResponse; @@ -77,6 +73,10 @@ import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.verb.POST; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * A Jenkins instance used during first-run to provide a limited set of services while @@ -312,7 +312,7 @@ public boolean isUsingSecurityToken() { HudsonPrivateSecurityRealm securityRealm = (HudsonPrivateSecurityRealm)j.getSecurityRealm(); try { if(securityRealm.getAllUsers().size() == 1) { - HudsonPrivateSecurityRealm.Details details = securityRealm.loadUserByUsername(SetupWizard.initialSetupAdminUserName); + HudsonPrivateSecurityRealm.Details details = securityRealm.load(SetupWizard.initialSetupAdminUserName); FilePath iapf = getInitialAdminPasswordFile(); if (iapf.exists()) { if (details.isPasswordCorrect(iapf.readToString().trim())) { @@ -378,7 +378,7 @@ public HttpResponse doCreateAdminUser(StaplerRequest req, StaplerResponse rsp) t // ... and then login Authentication auth = new UsernamePasswordAuthenticationToken(newUser.getId(), req.getParameter("password1")); - auth = securityRealm.getSecurityComponents().manager.authenticate(auth); + auth = securityRealm.getSecurityComponents().manager2.authenticate(auth); SecurityContextHolder.getContext().setAuthentication(auth); HttpSession session = req.getSession(false); diff --git a/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java b/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java index e7bc8c46a0f8..9a839697a29d 100644 --- a/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java +++ b/core/src/main/java/jenkins/management/AsynchronousAdministrativeMonitor.java @@ -99,7 +99,7 @@ protected class FixThread extends Thread { @Override public void run() { StreamTaskListener listener = null; - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { listener = new StreamTaskListener(getLogFile()); try { doRun(listener); diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 9286a93bd4f1..1b6fad027d0b 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -205,14 +205,6 @@ import jenkins.util.xml.XMLUtils; import net.jcip.annotations.GuardedBy; import net.sf.json.JSONObject; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.AcegiSecurityException; -import org.acegisecurity.Authentication; -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.GrantedAuthorityImpl; -import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; -import org.acegisecurity.ui.AbstractProcessingFilter; import org.apache.commons.jelly.JellyException; import org.apache.commons.jelly.Script; import org.apache.commons.logging.LogFactory; @@ -308,6 +300,11 @@ import static javax.servlet.http.HttpServletResponse.*; import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.stapler.WebMethod; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; /** * Root object of the system. @@ -864,7 +861,7 @@ protected Jenkins(File root, ServletContext context, PluginManager pluginManager long start = System.currentTimeMillis(); STARTUP_MARKER_FILE = new FileBoolean(new File(root, ".lastStarted")); // As Jenkins is starting, grant this process full control - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { this.root = root; this.servletContext = context; computeVersion(context); @@ -1126,7 +1123,7 @@ protected void runTask(Task task) throws Exception { String name = t.getName(); if (taskName !=null) t.setName(taskName); - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { // full access in the initialization thread + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { // full access in the initialization thread long start = System.currentTimeMillis(); super.runTask(task); if(LOG_STARTUP_PERFORMANCE) @@ -2634,7 +2631,7 @@ public void setSecurityRealm(@CheckForNull SecurityRealm securityRealm) { } } catch (ServletException e) { // for binary compatibility, this method cannot throw a checked exception - throw new AcegiSecurityException("Failed to configure filter",e) {}; + throw new RuntimeException("Failed to configure filter",e) {}; } saveQuietly(); } @@ -4085,6 +4082,7 @@ public void doLoginEntry( StaplerRequest req, StaplerResponse rsp ) throws IOExc return; } + /* TODO unclear what the Spring Security equivalent is; check AbstractAuthenticationProcessingFilter, SavedRequest String url = AbstractProcessingFilter.obtainFullRequestUrl(req); if(url!=null) { // if the login redirect is initiated by Acegi @@ -4092,6 +4090,7 @@ public void doLoginEntry( StaplerRequest req, StaplerResponse rsp ) throws IOExc rsp.sendRedirect2(url); return; } + */ rsp.sendRedirect2("."); } @@ -4100,7 +4099,7 @@ public void doLoginEntry( StaplerRequest req, StaplerResponse rsp ) throws IOExc * Logs out the user. */ public void doLogout( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException { - String user = getAuthentication().getName(); + String user = getAuthentication2().getName(); securityRealm.doLogout(req, rsp); SecurityListener.fireLoggedOut(user); } @@ -4122,7 +4121,7 @@ public Slave.JnlpJar doJnlpJars(StaplerRequest req) { @RequirePOST public synchronized HttpResponse doReload() throws IOException { checkPermission(MANAGE); - LOGGER.log(Level.WARNING, "Reloading Jenkins as requested by {0}", getAuthentication().getName()); + LOGGER.log(Level.WARNING, "Reloading Jenkins as requested by {0}", getAuthentication2().getName()); // engage "loading ..." UI and then run the actual task in a separate thread WebApp.get(servletContext).setApp(new HudsonIsLoading()); @@ -4130,7 +4129,7 @@ public synchronized HttpResponse doReload() throws IOException { new Thread("Jenkins config reload thread") { @Override public void run() { - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { reload(); } catch (Exception e) { LOGGER.log(SEVERE,"Failed to reload Jenkins config",e); @@ -4312,10 +4311,10 @@ public void restart() throws RestartNotSupportedException { servletContext.setAttribute("app", new HudsonIsRestarting()); new Thread("restart thread") { - final String exitUser = getAuthentication().getName(); + final String exitUser = getAuthentication2().getName(); @Override public void run() { - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { // give some time for the browser to load the "reloading" page Thread.sleep(TimeUnit.SECONDS.toMillis(5)); LOGGER.info(String.format("Restarting VM as requested by %s",exitUser)); @@ -4339,10 +4338,10 @@ public void safeRestart() throws RestartNotSupportedException { isQuietingDown = true; new Thread("safe-restart thread") { - final String exitUser = getAuthentication().getName(); + final String exitUser = getAuthentication2().getName(); @Override public void run() { - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { // Wait 'til we have no active executors. doQuietDown(true, 0); @@ -4412,9 +4411,9 @@ public void doExit( StaplerRequest req, StaplerResponse rsp ) throws IOException @Override @SuppressFBWarnings(value = "DM_EXIT", justification = "Exit is really intended.") public void run() { - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { LOGGER.info(String.format("Shutting down VM as requested by %s from %s", - getAuthentication().getName(), req != null ? req.getRemoteAddr() : "???")); + getAuthentication2().getName(), req != null ? req.getRemoteAddr() : "???")); cleanUp(); System.exit(0); @@ -4434,13 +4433,13 @@ public void run() { public HttpResponse doSafeExit(StaplerRequest req) throws IOException { checkPermission(ADMINISTER); isQuietingDown = true; - final String exitUser = getAuthentication().getName(); + final String exitUser = getAuthentication2().getName(); final String exitAddr = req!=null ? req.getRemoteAddr() : "unknown"; new Thread("safe-exit thread") { @Override @SuppressFBWarnings(value = "DM_EXIT", justification = "Exit is really intended.") public void run() { - try (ACLContext ctx = ACL.as(ACL.SYSTEM)) { + try (ACLContext ctx = ACL.as2(ACL.SYSTEM2)) { LOGGER.info(String.format("Shutting down VM as requested by %s from %s", exitUser, exitAddr)); // Wait 'til we have no active executors. @@ -4462,18 +4461,27 @@ public void run() { /** * Gets the {@link Authentication} object that represents the user * associated with the current request. + * @since TODO */ - public static @NonNull Authentication getAuthentication() { + public static @NonNull Authentication getAuthentication2() { Authentication a = SecurityContextHolder.getContext().getAuthentication(); // on Tomcat while serving the login page, this is null despite the fact // that we have filters. Looking at the stack trace, Tomcat doesn't seem to // run the request through filters when this is the login request. // see http://www.nabble.com/Matrix-authorization-problem-tp14602081p14886312.html if(a==null) - a = ANONYMOUS; + a = ANONYMOUS2; return a; } + /** + * @deprecated use {@link #getAuthentication2} + */ + @Deprecated + public static @NonNull org.acegisecurity.Authentication getAuthentication() { + return org.acegisecurity.Authentication.fromSpring(getAuthentication2()); + } + /** * For system diagnostics. * Run arbitrary Groovy script. @@ -5366,17 +5374,22 @@ public boolean shouldShowStackTrace() { /** * {@link Authentication} object that represents the anonymous user. - * Because Acegi creates its own {@link AnonymousAuthenticationToken} instances, the code must not + * Because Spring Security creates its own {@link AnonymousAuthenticationToken} instances, the code must not * expect the singleton semantics. This is just a convenient instance. * + * @since TODO + */ + public static final Authentication ANONYMOUS2 = new AnonymousAuthenticationToken("anonymous", "anonymous", Collections.singleton(new SimpleGrantedAuthority("anonymous"))); + + /** + * @deprecated use {@link #ANONYMOUS2} * @since 1.343 */ - public static final Authentication ANONYMOUS; + @Deprecated + public static final org.acegisecurity.Authentication ANONYMOUS = new org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken("anonymous", "anonymous", new org.acegisecurity.GrantedAuthority[] {new org.acegisecurity.GrantedAuthorityImpl("anonymous")}); static { try { - ANONYMOUS = new AnonymousAuthenticationToken( - "anonymous", "anonymous", new GrantedAuthority[]{new GrantedAuthorityImpl("anonymous")}); XSTREAM = XSTREAM2 = new XStream2(); XSTREAM.alias("jenkins", Jenkins.class); diff --git a/core/src/main/java/jenkins/security/AcegiSecurityExceptionFilter.java b/core/src/main/java/jenkins/security/AcegiSecurityExceptionFilter.java new file mode 100644 index 000000000000..5409791fed63 --- /dev/null +++ b/core/src/main/java/jenkins/security/AcegiSecurityExceptionFilter.java @@ -0,0 +1,89 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 jenkins.security; + +import edu.umd.cs.findbugs.annotations.CheckForNull; +import hudson.security.UnwrapSecurityExceptionFilter; +import java.io.IOException; +import java.util.function.Function; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import org.acegisecurity.AcegiSecurityException; +import org.springframework.security.web.access.ExceptionTranslationFilter; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * Translates {@link AcegiSecurityException}s to Spring Security equivalents. + * Used by other filters like {@link UnwrapSecurityExceptionFilter} and {@link ExceptionTranslationFilter}. + */ +@Restricted(NoExternalUse.class) +public class AcegiSecurityExceptionFilter implements Filter { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + try { + chain.doFilter(request, response); + } catch (IOException x) { + throw translate(x, IOException::new); + } catch (ServletException x) { + throw translate(x, ServletException::new); + } catch (RuntimeException x) { + throw translate(x, RuntimeException::new); + } + } + + private static T translate(T t, Function ctor) { + RuntimeException cause = convertedCause(t); + if (cause != null) { + T t2 = ctor.apply(cause); + t2.addSuppressed(t); + return t2; + } else { + return t; + } + } + + private static @CheckForNull RuntimeException convertedCause(@CheckForNull Throwable t) { + if (t instanceof AcegiSecurityException) { + return ((AcegiSecurityException) t).toSpring(); + } else if (t != null) { + return convertedCause(t.getCause()); + } else { + return null; + } + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException {} + + @Override + public void destroy() {} + +} diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java index d9a5d4f1a8c2..09e1080bba28 100644 --- a/core/src/main/java/jenkins/security/ApiTokenProperty.java +++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java @@ -224,7 +224,7 @@ private static boolean canCurrentUserControlObject(boolean trustAdmins, User pro } // SYSTEM user is always eligible to see tokens - if (Jenkins.getAuthentication() == ACL.SYSTEM) { + if (Jenkins.getAuthentication2().equals(ACL.SYSTEM2)) { return true; } diff --git a/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java b/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java index 2b0fb730ba2e..c667dace445a 100644 --- a/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java +++ b/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java @@ -2,17 +2,16 @@ import hudson.Extension; import hudson.model.User; -import org.acegisecurity.Authentication; -import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.UsernameNotFoundException; -import org.springframework.dao.DataAccessException; - +import static java.util.logging.Level.*; +import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.util.logging.Logger; - -import static java.util.logging.Level.*; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; /** * Checks if the password given in the BASIC header matches the user's API token. @@ -20,6 +19,7 @@ * @author Kohsuke Kawaguchi * @since 1.576 */ +@Restricted(NoExternalUse.class) @Extension public class BasicHeaderApiTokenAuthenticator extends BasicHeaderAuthenticator { /** @@ -27,22 +27,20 @@ public class BasicHeaderApiTokenAuthenticator extends BasicHeaderAuthenticator { * because it will be done in the {@link BasicHeaderRealPasswordAuthenticator} in the case the password is not valid either */ @Override - public Authentication authenticate(HttpServletRequest req, HttpServletResponse rsp, String username, String password) throws ServletException { + public Authentication authenticate2(HttpServletRequest req, HttpServletResponse rsp, String username, String password) throws ServletException { User u = BasicApiTokenHelper.isConnectingUsingApiToken(username, password); if(u != null) { Authentication auth; try { - UserDetails userDetails = u.getUserDetailsForImpersonation(); + UserDetails userDetails = u.getUserDetailsForImpersonation2(); auth = u.impersonate(userDetails); - SecurityListener.fireAuthenticated(userDetails); + SecurityListener.fireAuthenticated2(userDetails); } catch (UsernameNotFoundException x) { // The token was valid, but the impersonation failed. This token is clearly not his real password, // so there's no point in continuing the request processing. Report this error and abort. LOGGER.log(WARNING, "API token matched for user " + username + " but the impersonation failed", x); throw new ServletException(x); - } catch (DataAccessException x) { - throw new ServletException(x); } req.setAttribute(BasicHeaderApiTokenAuthenticator.class.getName(), true); diff --git a/core/src/main/java/jenkins/security/BasicHeaderAuthenticator.java b/core/src/main/java/jenkins/security/BasicHeaderAuthenticator.java index ade67de48a51..27c9e208846d 100644 --- a/core/src/main/java/jenkins/security/BasicHeaderAuthenticator.java +++ b/core/src/main/java/jenkins/security/BasicHeaderAuthenticator.java @@ -1,13 +1,14 @@ package jenkins.security; +import edu.umd.cs.findbugs.annotations.CheckForNull; import hudson.ExtensionList; import hudson.ExtensionPoint; -import org.acegisecurity.Authentication; - +import hudson.Util; +import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; +import org.springframework.security.core.Authentication; /** * When Jenkins receives HTTP basic authentication, this hook will validate the username/password @@ -34,8 +35,27 @@ public abstract class BasicHeaderAuthenticator implements ExtensionPoint { *

* When no processor can validate the username/password pair, caller will make * the request processing fail. + * @since TODO + */ + @CheckForNull + public Authentication authenticate2(HttpServletRequest req, HttpServletResponse rsp, String username, String password) throws IOException, ServletException { + if (Util.isOverridden(BasicHeaderAuthenticator.class, getClass(), "authenticate", HttpServletRequest.class, HttpServletResponse.class, String.class, String.class)) { + org.acegisecurity.Authentication a = authenticate(req, rsp, username, password); + return a != null ? a.toSpring() : null; + } else { + throw new AbstractMethodError("implement authenticate2"); + } + } + + /** + * @deprecated use {@link #authenticate2} */ - public abstract Authentication authenticate(HttpServletRequest req, HttpServletResponse rsp, String username, String password) throws IOException, ServletException; + @Deprecated + @CheckForNull + public org.acegisecurity.Authentication authenticate(HttpServletRequest req, HttpServletResponse rsp, String username, String password) throws IOException, ServletException { + Authentication a = authenticate2(req, rsp, username, password); + return a != null ? org.acegisecurity.Authentication.fromSpring(a) : null; + } public static ExtensionList all() { return ExtensionList.lookup(BasicHeaderAuthenticator.class); diff --git a/core/src/main/java/jenkins/security/BasicHeaderProcessor.java b/core/src/main/java/jenkins/security/BasicHeaderProcessor.java index c10e74661875..eff341205102 100644 --- a/core/src/main/java/jenkins/security/BasicHeaderProcessor.java +++ b/core/src/main/java/jenkins/security/BasicHeaderProcessor.java @@ -4,16 +4,10 @@ import hudson.security.ACLContext; import hudson.security.SecurityRealm; import hudson.util.Scrambler; -import org.acegisecurity.Authentication; -import org.acegisecurity.BadCredentialsException; -import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; -import org.acegisecurity.ui.AuthenticationEntryPoint; -import org.acegisecurity.ui.rememberme.NullRememberMeServices; -import org.acegisecurity.ui.rememberme.RememberMeServices; -import org.apache.commons.lang.StringUtils; - +import java.io.IOException; +import java.util.List; +import static java.util.logging.Level.*; +import java.util.logging.Logger; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -22,11 +16,17 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.List; -import java.util.logging.Logger; - -import static java.util.logging.Level.*; +import org.apache.commons.lang.StringUtils; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.authentication.NullRememberMeServices; +import org.springframework.security.web.authentication.RememberMeServices; /** * Takes "username:password" given in the {@code Authorization} HTTP header and authenticates @@ -40,8 +40,8 @@ * * @author Kohsuke Kawaguchi */ +@Restricted(NoExternalUse.class) public class BasicHeaderProcessor implements Filter { - // these fields are supposed to be injected by Spring private AuthenticationEntryPoint authenticationEntryPoint; private RememberMeServices rememberMeServices = new NullRememberMeServices(); @@ -76,7 +76,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha for (BasicHeaderAuthenticator a : all()) { LOGGER.log(FINER, "Attempting to authenticate with {0}", a); - Authentication auth = a.authenticate(req, rsp, username, password); + Authentication auth = a.authenticate2(req, rsp, username, password); if (auth!=null) { LOGGER.log(FINE, "Request authenticated as {0} by {1}", new Object[]{auth,a}); success(req, rsp, chain, auth); @@ -131,7 +131,7 @@ protected boolean authenticationIsRequired(String username) { protected void success(HttpServletRequest req, HttpServletResponse rsp, FilterChain chain, Authentication auth) throws IOException, ServletException { rememberMeServices.loginSuccess(req, rsp, auth); - try (ACLContext ctx = ACL.as(auth)){ + try (ACLContext ctx = ACL.as2(auth)){ chain.doFilter(req,rsp); } } diff --git a/core/src/main/java/jenkins/security/BasicHeaderRealPasswordAuthenticator.java b/core/src/main/java/jenkins/security/BasicHeaderRealPasswordAuthenticator.java index 5acba8daa461..dc9a13568566 100644 --- a/core/src/main/java/jenkins/security/BasicHeaderRealPasswordAuthenticator.java +++ b/core/src/main/java/jenkins/security/BasicHeaderRealPasswordAuthenticator.java @@ -15,22 +15,22 @@ package jenkins.security; import hudson.Extension; -import jenkins.util.SystemProperties; -import jenkins.ExtensionFilter; -import jenkins.model.Jenkins; -import org.acegisecurity.Authentication; -import org.acegisecurity.AuthenticationException; -import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; -import org.acegisecurity.ui.AuthenticationDetailsSource; -import org.acegisecurity.ui.AuthenticationDetailsSourceImpl; - +import java.io.IOException; +import static java.util.logging.Level.*; +import java.util.logging.Logger; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.logging.Logger; - -import static java.util.logging.Level.*; +import jenkins.ExtensionFilter; +import jenkins.model.Jenkins; +import jenkins.util.SystemProperties; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.authentication.AuthenticationDetailsSource; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; /** * Checks if the password given in the BASIC header matches the user's actual password, @@ -39,12 +39,13 @@ * @author Kohsuke Kawaguchi * @since 1.576 */ +@Restricted(NoExternalUse.class) @Extension public class BasicHeaderRealPasswordAuthenticator extends BasicHeaderAuthenticator { - private AuthenticationDetailsSource authenticationDetailsSource = new AuthenticationDetailsSourceImpl(); + private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); @Override - public Authentication authenticate(HttpServletRequest req, HttpServletResponse rsp, String username, String password) throws IOException, ServletException { + public Authentication authenticate2(HttpServletRequest req, HttpServletResponse rsp, String username, String password) throws IOException, ServletException { if (DISABLE) return null; @@ -53,7 +54,7 @@ public Authentication authenticate(HttpServletRequest req, HttpServletResponse r authRequest.setDetails(authenticationDetailsSource.buildDetails(req)); try { - Authentication a = Jenkins.get().getSecurityRealm().getSecurityComponents().manager.authenticate(authRequest); + Authentication a = Jenkins.get().getSecurityRealm().getSecurityComponents().manager2.authenticate(authRequest); // Authentication success LOGGER.log(FINER, "Authentication success: {0}", a); return a; diff --git a/core/src/main/java/jenkins/security/ExceptionTranslationFilter.java b/core/src/main/java/jenkins/security/ExceptionTranslationFilter.java deleted file mode 100644 index 9dd7db7f0913..000000000000 --- a/core/src/main/java/jenkins/security/ExceptionTranslationFilter.java +++ /dev/null @@ -1,239 +0,0 @@ -/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * Copyright (c) 2020 CloudBees, Inc. - * - * 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 - * - * http://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 jenkins.security; - -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.AcegiSecurityException; -import org.acegisecurity.AuthenticationException; -import org.acegisecurity.AuthenticationTrustResolver; -import org.acegisecurity.AuthenticationTrustResolverImpl; -import org.acegisecurity.InsufficientAuthenticationException; -import org.acegisecurity.context.SecurityContextHolder; -import org.acegisecurity.ui.AbstractProcessingFilter; -import org.acegisecurity.ui.AccessDeniedHandler; -import org.acegisecurity.ui.AccessDeniedHandlerImpl; -import org.acegisecurity.ui.AuthenticationEntryPoint; -import org.acegisecurity.ui.savedrequest.SavedRequest; -import org.acegisecurity.util.PortResolver; -import org.acegisecurity.util.PortResolverImpl; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Handles any {@code AccessDeniedException} and {@code AuthenticationException} thrown within the - * filter chain. - *

- * This filter is necessary because it provides the bridge between Java exceptions and HTTP responses. - * It is solely concerned with maintaining the user interface. This filter does not do any actual security enforcement. - *

- *

- * If an {@link AuthenticationException} is detected, the filter will launch the {@code authenticationEntryPoint}. - * This allows common handling of authentication failures originating from any subclass of - * {@code AbstractSecurityInterceptor}. - *

- *

- * If an {@link AccessDeniedException} is detected, the filter will determine whether or not the user is an anonymous - * user. If they are an anonymous user, the {@code authenticationEntryPoint} will be launched. If they are not - * an anonymous user, the filter will delegate to the {@code AccessDeniedHandler}. - * By default the filter will use {@code AccessDeniedHandlerImpl}. - *

- *

- * To use this filter, it is necessary to specify the following properties: - *

- *
    - *
  • {@code authenticationEntryPoint} indicates the handler that - * should commence the authentication process if an - * {@code AuthenticationException} is detected. Note that this may also - * switch the current protocol from http to https for an SSL login.
  • - *
  • {@code portResolver} is used to determine the "real" port that a - * request was received on.
  • - *
- *

- * Do not use this class directly. Instead configure - * {@code web.xml} to use the {@code FilterToBeanProxy}. - *

- * - * @author Ben Alex - * @author colin sampaleanu - * @version $Id: ExceptionTranslationFilter.java 2134 2007-09-19 16:41:06Z luke_t $ - */ -public class ExceptionTranslationFilter implements Filter, InitializingBean { - - //~ Static fields/initializers ===================================================================================== - - private static final Logger LOGGER = Logger.getLogger(ExceptionTranslationFilter.class.getName()); - - //~ Instance fields ================================================================================================ - - private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl(); - private AuthenticationEntryPoint authenticationEntryPoint; - private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); - private PortResolver portResolver = new PortResolverImpl(); - private boolean createSessionAllowed = true; - - //~ Methods ======================================================================================================== - - public void afterPropertiesSet() throws Exception { - Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint must be specified"); - Assert.notNull(portResolver, "portResolver must be specified"); - Assert.notNull(authenticationTrustResolver, "authenticationTrustResolver must be specified"); - } - - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, - ServletException { - if (!(request instanceof HttpServletRequest)) { - throw new ServletException("HttpServletRequest required"); - } - - if (!(response instanceof HttpServletResponse)) { - throw new ServletException("HttpServletResponse required"); - } - - try { - chain.doFilter(request, response); - - LOGGER.finer("Chain processed normally"); - } - catch (AuthenticationException | AccessDeniedException ex) { - handleException(request, response, chain, ex); - } catch (ServletException ex) { - if (ex.getRootCause() instanceof AuthenticationException || ex.getRootCause() instanceof AccessDeniedException) { - handleException(request, response, chain, (AcegiSecurityException) ex.getRootCause()); - } - else { - throw ex; - } - } - catch (IOException ex) { - throw ex; - } - } - - public AuthenticationEntryPoint getAuthenticationEntryPoint() { - return authenticationEntryPoint; - } - - public AuthenticationTrustResolver getAuthenticationTrustResolver() { - return authenticationTrustResolver; - } - - public PortResolver getPortResolver() { - return portResolver; - } - - private void handleException(ServletRequest request, ServletResponse response, FilterChain chain, - AcegiSecurityException exception) throws IOException, ServletException { - if (exception instanceof AuthenticationException) { - LOGGER.log(Level.FINER, "Authentication exception occurred; redirecting to authentication entry point", exception); - - sendStartAuthentication(request, response, chain, (AuthenticationException) exception); - } - else if (exception instanceof AccessDeniedException) { - if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) { - LOGGER.log(Level.FINER, "Access is denied (user is anonymous); redirecting to authentication entry point", - exception); - - sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException( - "Full authentication is required to access this resource",exception)); - } - else { - LOGGER.log(Level.FINER, "Access is denied (user is not anonymous); delegating to AccessDeniedHandler", - exception); - - accessDeniedHandler.handle(request, response, (AccessDeniedException) exception); - } - } - } - - /** - * If {@code true}, indicates that {@code SecurityEnforcementFilter} is permitted to store the target - * URL and exception information in the {@link HttpSession} (the default). - * In situations where you do not wish to unnecessarily create {@link HttpSession}s - because the user agent - * will know the failed URL, such as with BASIC or Digest authentication - you may wish to - * set this property to {@code false}. Remember to also set the - * {@link org.acegisecurity.context.HttpSessionContextIntegrationFilter#allowSessionCreation} - * to {@code false} if you set this property to {@code false}. - * - * @return {@code true} if the {@link HttpSession} will be - * used to store information about the failed request, {@code false} - * if the {@link HttpSession} will not be used - */ - public boolean isCreateSessionAllowed() { - return createSessionAllowed; - } - - protected void sendStartAuthentication(ServletRequest request, ServletResponse response, FilterChain chain, - AuthenticationException reason) throws ServletException, IOException { - HttpServletRequest httpRequest = (HttpServletRequest) request; - - SavedRequest savedRequest = new SavedRequest(httpRequest, portResolver); - - LOGGER.finer("Authentication entry point being called; SavedRequest added to Session: " + savedRequest); - - if (createSessionAllowed) { - // Store the HTTP request itself. Used by AbstractProcessingFilter - // for redirection after successful authentication (SEC-29) - httpRequest.getSession().setAttribute(AbstractProcessingFilter.ACEGI_SAVED_REQUEST_KEY, savedRequest); - } - - // SEC-112: Clear the SecurityContextHolder's Authentication, as the - // existing Authentication is no longer considered valid - SecurityContextHolder.getContext().setAuthentication(null); - - authenticationEntryPoint.commence(httpRequest, response, reason); - } - - public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) { - Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required"); - this.accessDeniedHandler = accessDeniedHandler; - } - - public void setAuthenticationEntryPoint(AuthenticationEntryPoint authenticationEntryPoint) { - this.authenticationEntryPoint = authenticationEntryPoint; - } - - public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) { - this.authenticationTrustResolver = authenticationTrustResolver; - } - - public void setCreateSessionAllowed(boolean createSessionAllowed) { - this.createSessionAllowed = createSessionAllowed; - } - - public void setPortResolver(PortResolver portResolver) { - this.portResolver = portResolver; - } - - public void init(FilterConfig filterConfig) throws ServletException { - } - - public void destroy() { - } - -} diff --git a/core/src/main/java/jenkins/security/ImpersonatingExecutorService.java b/core/src/main/java/jenkins/security/ImpersonatingExecutorService.java index a4a3c0de44e1..0fd9619f1d60 100644 --- a/core/src/main/java/jenkins/security/ImpersonatingExecutorService.java +++ b/core/src/main/java/jenkins/security/ImpersonatingExecutorService.java @@ -29,10 +29,10 @@ import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import jenkins.util.InterceptingExecutorService; -import org.acegisecurity.Authentication; +import org.springframework.security.core.Authentication; /** - * Uses {@link ACL#impersonate(Authentication)} for all tasks. + * Uses {@link ACL#impersonate2(Authentication)} for all tasks. * @see SecurityContextExecutorService * @since 2.51 */ @@ -43,19 +43,28 @@ public final class ImpersonatingExecutorService extends InterceptingExecutorServ /** * Creates a wrapper service. * @param base the base service - * @param authentication for example {@link ACL#SYSTEM} + * @param authentication for example {@link ACL#SYSTEM2} + * @since TODO */ public ImpersonatingExecutorService(ExecutorService base, Authentication authentication) { super(base); this.authentication = authentication; } + /** + * @deprecated use {@link #ImpersonatingExecutorService(ExecutorService, Authentication)} + */ + @Deprecated + public ImpersonatingExecutorService(ExecutorService base, org.acegisecurity.Authentication authentication) { + this(base, authentication.toSpring()); + } + @Override protected Runnable wrap(final Runnable r) { return new Runnable() { @Override public void run() { - try (ACLContext ctxt = ACL.as(authentication)) { + try (ACLContext ctxt = ACL.as2(authentication)) { r.run(); } } @@ -67,7 +76,7 @@ protected Callable wrap(final Callable r) { return new Callable() { @Override public V call() throws Exception { - try (ACLContext ctxt = ACL.as(authentication)) { + try (ACLContext ctxt = ACL.as2(authentication)) { return r.call(); } } diff --git a/core/src/main/java/jenkins/security/ImpersonatingScheduledExecutorService.java b/core/src/main/java/jenkins/security/ImpersonatingScheduledExecutorService.java index 8bb31c24301d..c278b52bb9ae 100644 --- a/core/src/main/java/jenkins/security/ImpersonatingScheduledExecutorService.java +++ b/core/src/main/java/jenkins/security/ImpersonatingScheduledExecutorService.java @@ -29,7 +29,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; import jenkins.util.InterceptingScheduledExecutorService; -import org.acegisecurity.Authentication; +import org.springframework.security.core.Authentication; /** * Variant of {@link ImpersonatingExecutorService} for scheduled services. @@ -42,19 +42,28 @@ public final class ImpersonatingScheduledExecutorService extends InterceptingSch /** * Creates a wrapper service. * @param base the base service - * @param authentication for example {@link ACL#SYSTEM} + * @param authentication for example {@link ACL#SYSTEM2} + * @since TODO */ public ImpersonatingScheduledExecutorService(ScheduledExecutorService base, Authentication authentication) { super(base); this.authentication = authentication; } + /** + * @deprecated use {@link #ImpersonatingScheduledExecutorService(ScheduledExecutorService, Authentication)} + */ + @Deprecated + public ImpersonatingScheduledExecutorService(ScheduledExecutorService base, org.acegisecurity.Authentication authentication) { + this(base, authentication.toSpring()); + } + @Override protected Runnable wrap(final Runnable r) { return new Runnable() { @Override public void run() { - try (ACLContext ctxt = ACL.as(authentication)) { + try (ACLContext ctxt = ACL.as2(authentication)) { r.run(); } } @@ -66,7 +75,7 @@ protected Callable wrap(final Callable r) { return new Callable() { @Override public V call() throws Exception { - try (ACLContext ctxt = ACL.as(authentication)) { + try (ACLContext ctxt = ACL.as2(authentication)) { return r.call(); } } diff --git a/core/src/main/java/jenkins/security/ImpersonatingUserDetailsService.java b/core/src/main/java/jenkins/security/ImpersonatingUserDetailsService.java index 0c97d5d48cd0..86a029851007 100644 --- a/core/src/main/java/jenkins/security/ImpersonatingUserDetailsService.java +++ b/core/src/main/java/jenkins/security/ImpersonatingUserDetailsService.java @@ -16,7 +16,9 @@ * information stored in {@link LastGrantedAuthoritiesProperty}. * * @author Kohsuke Kawaguchi + * @deprecated use {@link ImpersonatingUserDetailsService2} */ +@Deprecated public class ImpersonatingUserDetailsService implements UserDetailsService { private final UserDetailsService base; diff --git a/core/src/main/java/jenkins/security/ImpersonatingUserDetailsService2.java b/core/src/main/java/jenkins/security/ImpersonatingUserDetailsService2.java new file mode 100644 index 000000000000..ad2b0cc1697f --- /dev/null +++ b/core/src/main/java/jenkins/security/ImpersonatingUserDetailsService2.java @@ -0,0 +1,46 @@ +package jenkins.security; + +import hudson.model.User; +import hudson.security.SecurityRealm; +import hudson.security.UserMayOrMayNotExistException2; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * {@link UserDetailsService} for those {@link SecurityRealm} + * that doesn't allow query of other users. + * + * When the backend responds with {@link UserMayOrMayNotExistException2}, we try to replace that with + * information stored in {@link LastGrantedAuthoritiesProperty}. + * + * @author Kohsuke Kawaguchi + */ +public class ImpersonatingUserDetailsService2 implements UserDetailsService { + private final UserDetailsService base; + + public ImpersonatingUserDetailsService2(UserDetailsService base) { + this.base = base; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + try { + return base.loadUserByUsername(username); + } catch (UserMayOrMayNotExistException2 e) { + return attemptToImpersonate(username, e); + } + } + + protected UserDetails attemptToImpersonate(String username, RuntimeException e) { + // this backend cannot tell if the user name exists or not. so substitute by what we know + User u = User.getById(username, false); + if (u != null) { + LastGrantedAuthoritiesProperty p = u.getProperty(LastGrantedAuthoritiesProperty.class); + if (p != null) { + return new org.springframework.security.core.userdetails.User(username, "", true, true, true, true, p.getAuthorities2()); + } + } + throw e; + } +} diff --git a/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java b/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java index 1ba8e92ef93a..5d298f39e40b 100644 --- a/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java +++ b/core/src/main/java/jenkins/security/LastGrantedAuthoritiesProperty.java @@ -8,10 +8,6 @@ import hudson.security.SecurityRealm; import jenkins.model.Jenkins; import net.sf.json.JSONObject; -import org.acegisecurity.Authentication; -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.GrantedAuthorityImpl; -import org.acegisecurity.userdetails.UserDetails; import org.jenkinsci.Symbol; import org.kohsuke.stapler.StaplerRequest; @@ -19,14 +15,22 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; /** * Remembers the set of {@link GrantedAuthority}s that was obtained the last time the user has logged in. * - * This allows us to implement {@link User#impersonate()} with proper set of groups. + * This allows us to implement {@link User#impersonate2()} with proper set of groups. * * @author Kohsuke Kawaguchi * @since 1.556 @@ -45,30 +49,42 @@ public UserProperty reconfigure(StaplerRequest req, JSONObject form) throws Form return this; } - public GrantedAuthority[] getAuthorities() { + /** + * @since TODO + */ + public Collection getAuthorities2() { String[] roles = this.roles; // capture to a variable for immutability if(roles == null){ - return new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY}; + return Collections.singleton(SecurityRealm.AUTHENTICATED_AUTHORITY2); } - String authenticatedRole = SecurityRealm.AUTHENTICATED_AUTHORITY.getAuthority(); + String authenticatedRole = SecurityRealm.AUTHENTICATED_AUTHORITY2.getAuthority(); List grantedAuthorities = new ArrayList<>(roles.length + 1); - grantedAuthorities.add(new GrantedAuthorityImpl(authenticatedRole)); + grantedAuthorities.add(new SimpleGrantedAuthority(authenticatedRole)); for (String role : roles) { // to avoid having twice that role if (!authenticatedRole.equals(role)) { - grantedAuthorities.add(new GrantedAuthorityImpl(role)); + grantedAuthorities.add(new SimpleGrantedAuthority(role)); } } - return grantedAuthorities.toArray(new GrantedAuthority[0]); + return grantedAuthorities; + } + + /** + * @deprecated use {@link #getAuthorities2} + */ + @Deprecated + public org.acegisecurity.GrantedAuthority[] getAuthorities() { + return org.acegisecurity.GrantedAuthority.fromSpring(getAuthorities2()); } /** * Persist the information with the new {@link UserDetails}. */ + @Restricted(NoExternalUse.class) public void update(@NonNull Authentication auth) throws IOException { List roles = new ArrayList<>(); for (GrantedAuthority ga : auth.getAuthorities()) { @@ -107,7 +123,7 @@ protected void loggedIn(@NonNull String username) { LastGrantedAuthoritiesProperty o = u.getProperty(LastGrantedAuthoritiesProperty.class); if (o==null) u.addProperty(o=new LastGrantedAuthoritiesProperty()); - Authentication a = Jenkins.getAuthentication(); + Authentication a = Jenkins.getAuthentication2(); if (a!=null && a.getName().equals(username)) o.update(a); // just for defensive sanity checking } catch (IOException e) { diff --git a/core/src/main/java/jenkins/security/NonSerializableSecurityContext.java b/core/src/main/java/jenkins/security/NonSerializableSecurityContext.java index 87a3aeba9688..2c8523b1cb08 100644 --- a/core/src/main/java/jenkins/security/NonSerializableSecurityContext.java +++ b/core/src/main/java/jenkins/security/NonSerializableSecurityContext.java @@ -15,12 +15,14 @@ package jenkins.security; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextImpl; -import org.acegisecurity.Authentication; -import org.acegisecurity.userdetails.UserDetails; import javax.servlet.http.HttpSession; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.core.userdetails.UserDetails; /** * The same as {@link SecurityContextImpl} but doesn't serialize {@link Authentication}. @@ -42,6 +44,7 @@ * @since 1.509 */ @SuppressFBWarnings(value = "SE_TRANSIENT_FIELD_NOT_RESTORED", justification = "It is not intended to be serialized. Default values will be used in case of deserialization") +@Restricted(NoExternalUse.class) public class NonSerializableSecurityContext implements SecurityContext { private transient Authentication authentication; diff --git a/core/src/main/java/jenkins/security/QueueItemAuthenticator.java b/core/src/main/java/jenkins/security/QueueItemAuthenticator.java index f0deb015bb19..d45682152b47 100644 --- a/core/src/main/java/jenkins/security/QueueItemAuthenticator.java +++ b/core/src/main/java/jenkins/security/QueueItemAuthenticator.java @@ -13,11 +13,11 @@ import java.util.Calendar; import java.util.Collections; import edu.umd.cs.findbugs.annotations.CheckForNull; -import org.acegisecurity.Authentication; +import org.springframework.security.core.Authentication; /** * Extension point to run {@link hudson.model.Queue.Executable}s under a specific identity for better access control. - * You must override either {@link #authenticate(hudson.model.Queue.Item)}, or {@link #authenticate(hudson.model.Queue.Task)}, or both. + * You must override either {@link #authenticate2(hudson.model.Queue.Item)}, or {@link #authenticate2(hudson.model.Queue.Task)}, or both. * @author Kohsuke Kawaguchi * @since 1.520 * @see QueueItemAuthenticatorConfiguration @@ -28,7 +28,7 @@ public abstract class QueueItemAuthenticator extends AbstractDescribableImpl implements ExtensionPoint { /** * Determines the identity in which the {@link hudson.model.Queue.Executable} will run as. - * The default implementation delegates to {@link #authenticate(hudson.model.Queue.Task)}. + * The default implementation delegates to {@link #authenticate2(hudson.model.Queue.Task)}. * @param item * The contextual information to assist the authentication. * The primary interest is likely {@link hudson.model.Queue.Item#task}, which is often {@link AbstractProject}. @@ -38,18 +38,25 @@ public abstract class QueueItemAuthenticator extends AbstractDescribableImpl "Determined DBS URL: " + dbsUrl + " from restOfUrl: " + completeUrl + " and restOfPath: " + dbsFile); - Authentication authentication = Jenkins.getAuthentication(); - String authenticationName = authentication == Jenkins.ANONYMOUS ? "" : authentication.getName(); + Authentication authentication = Jenkins.getAuthentication2(); + String authenticationName = authentication.equals(Jenkins.ANONYMOUS2) ? "" : authentication.getName(); try { return new Token(dbsUrl, authenticationName, Instant.now()); @@ -207,12 +205,12 @@ public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOExceptio LOGGER.fine(() -> "Performing a request as authentication: " + authenticationName + " and restOfUrl: " + requestUrlSuffix + " and restOfPath: " + restOfPath); - Authentication auth = Jenkins.ANONYMOUS; + Authentication auth = Jenkins.ANONYMOUS2; if (Util.fixEmpty(authenticationName) != null) { User user = User.getById(authenticationName, false); if (user != null) { try { - auth = user.impersonate(); + auth = user.impersonate2(); LOGGER.fine(() -> "Successfully impersonated " + authenticationName); } catch (UsernameNotFoundException ex) { LOGGER.log(Level.FINE, "Failed to impersonate " + authenticationName, ex); @@ -222,7 +220,7 @@ public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOExceptio } } - try (ACLContext ignored = ACL.as(auth)) { + try (ACLContext ignored = ACL.as2(auth)) { try { String path = requestUrlSuffix + Arrays.stream(restOfPath.split("[/]")).map(Util::rawEncode).collect(Collectors.joining("/")); Stapler.getCurrent().invoke(req, rsp, Jenkins.get(), path); diff --git a/core/src/main/java/jenkins/security/SecurityContextExecutorService.java b/core/src/main/java/jenkins/security/SecurityContextExecutorService.java index 6875f5b357fe..6f09e5a68ddf 100644 --- a/core/src/main/java/jenkins/security/SecurityContextExecutorService.java +++ b/core/src/main/java/jenkins/security/SecurityContextExecutorService.java @@ -24,12 +24,11 @@ package jenkins.security; import jenkins.util.InterceptingExecutorService; -import org.acegisecurity.context.SecurityContext; - import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; - -import static org.acegisecurity.context.SecurityContextHolder.*; +import org.springframework.security.core.context.SecurityContext; +import static org.springframework.security.core.context.SecurityContextHolder.getContext; +import static org.springframework.security.core.context.SecurityContextHolder.setContext; /** * Creates a delegating {@link ExecutorService} diff --git a/core/src/main/java/jenkins/security/SecurityListener.java b/core/src/main/java/jenkins/security/SecurityListener.java index d5256aade734..f261e6d5e98c 100644 --- a/core/src/main/java/jenkins/security/SecurityListener.java +++ b/core/src/main/java/jenkins/security/SecurityListener.java @@ -32,8 +32,9 @@ import java.util.logging.Level; import java.util.logging.Logger; import edu.umd.cs.findbugs.annotations.NonNull; -import org.acegisecurity.GrantedAuthority; -import org.acegisecurity.userdetails.UserDetails; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; /** * Listener notified of various significant events related to security. @@ -48,22 +49,31 @@ public abstract class SecurityListener implements ExtensionPoint { * This might be via the web UI, or via REST (using API token or Basic), or CLI (remoting, auth, ssh) * or any other way plugins can propose. * @param details details of the newly authenticated user, such as name and groups. + * @since TODO */ - protected void authenticated(@NonNull UserDetails details){} + protected void authenticated2(@NonNull UserDetails details) { + authenticated(org.acegisecurity.userdetails.UserDetails.fromSpring(details)); + } + + /** + * @deprecated use {@link #authenticated2} + */ + @Deprecated + protected void authenticated(@NonNull org.acegisecurity.userdetails.UserDetails details) {} /** * Fired when a user tried to authenticate but failed. * In case the authentication method uses multiple layers to validate the credentials, * we do fire this event only when even the last layer failed to authenticate. * @param username the user - * @see #authenticated + * @see #authenticated2 */ protected void failedToAuthenticate(@NonNull String username){} /** * Fired when a user has logged in. Compared to authenticated, there is a notion of storage / cache. - * Would be called after {@link #authenticated}. - * It should be called after the {@link org.acegisecurity.context.SecurityContextHolder#getContext()}'s authentication is set. + * Would be called after {@link #authenticated2}. + * It should be called after the {@link SecurityContextHolder#getContext()}'s authentication is set. * @param username the user */ protected void loggedIn(@NonNull String username){} @@ -90,22 +100,33 @@ protected void failedToLogIn(@NonNull String username){} */ protected void loggedOut(@NonNull String username){} - /** @since 1.569 */ - public static void fireAuthenticated(@NonNull UserDetails details) { + /** + * @since TODO + */ + public static void fireAuthenticated2(@NonNull UserDetails details) { if (LOGGER.isLoggable(Level.FINE)) { List groups = new ArrayList<>(); for (GrantedAuthority auth : details.getAuthorities()) { - if (!auth.equals(SecurityRealm.AUTHENTICATED_AUTHORITY)) { + if (!auth.equals(SecurityRealm.AUTHENTICATED_AUTHORITY2)) { groups.add(auth.getAuthority()); } } LOGGER.log(Level.FINE, "authenticated: {0} {1}", new Object[] {details.getUsername(), groups}); } for (SecurityListener l : all()) { - l.authenticated(details); + l.authenticated2(details); } } + /** + * @deprecated use {@link #fireAuthenticated2} + * @since 1.569 + */ + @Deprecated + public static void fireAuthenticated(@NonNull org.acegisecurity.userdetails.UserDetails details) { + fireAuthenticated2(details.toSpring()); + } + /** @since 2.161 */ public static void fireUserCreated(@NonNull String username) { LOGGER.log(Level.FINE, "new user created: {0}", username); diff --git a/core/src/main/java/jenkins/security/UserDetailsCache.java b/core/src/main/java/jenkins/security/UserDetailsCache.java index 653164efd420..4c958edfcb92 100644 --- a/core/src/main/java/jenkins/security/UserDetailsCache.java +++ b/core/src/main/java/jenkins/security/UserDetailsCache.java @@ -24,31 +24,27 @@ package jenkins.security; import com.google.common.cache.Cache; +import static com.google.common.cache.CacheBuilder.newBuilder; import com.google.common.util.concurrent.UncheckedExecutionException; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.ExtensionList; -import hudson.security.UserMayOrMayNotExistException; +import hudson.security.UserMayOrMayNotExistException2; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import jenkins.model.Jenkins; import jenkins.util.SystemProperties; -import org.acegisecurity.userdetails.UserDetails; -import org.acegisecurity.userdetails.UsernameNotFoundException; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.springframework.dao.DataAccessException; - -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import static com.google.common.cache.CacheBuilder.newBuilder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UsernameNotFoundException; /** - * Cache layer for {@link org.acegisecurity.userdetails.UserDetails} lookup. - * - * @since 2.15 + * Cache layer for {@link UserDetails} lookup. */ +@Restricted(NoExternalUse.class) @Extension public final class UserDetailsCache { @@ -99,7 +95,7 @@ public static UserDetailsCache get() { public UserDetails getCached(String idOrFullName) throws UsernameNotFoundException { Boolean exists = existenceCache.getIfPresent(idOrFullName); if (exists != null && !exists) { - throw new UserMayOrMayNotExistException(String.format("\"%s\" does not exist", idOrFullName)); + throw new UserMayOrMayNotExistException2(String.format("\"%s\" does not exist", idOrFullName)); } else { return detailsCache.getIfPresent(idOrFullName); } @@ -107,18 +103,17 @@ public UserDetails getCached(String idOrFullName) throws UsernameNotFoundExcepti /** * Locates the user based on the username, by first looking in the cache and then delegate to - * {@link hudson.security.SecurityRealm#loadUserByUsername(String)}. + * {@link hudson.security.SecurityRealm#loadUserByUsername2(String)}. * * @param idOrFullName the username * @return the details * - * @throws UsernameNotFoundException (normally a {@link hudson.security.UserMayOrMayNotExistException}) + * @throws UsernameNotFoundException (normally a {@link hudson.security.UserMayOrMayNotExistException2}) * if the user could not be found or the user has no GrantedAuthority - * @throws DataAccessException if user could not be found for a repository-specific reason * @throws ExecutionException if anything else went wrong in the cache lookup/retrieval */ @NonNull - public UserDetails loadUserByUsername(String idOrFullName) throws UsernameNotFoundException, DataAccessException, ExecutionException { + public UserDetails loadUserByUsername(String idOrFullName) throws UsernameNotFoundException, ExecutionException { Boolean exists = existenceCache.getIfPresent(idOrFullName); if(exists != null && !exists) { throw new UsernameNotFoundException(String.format("\"%s\" does not exist", idOrFullName)); @@ -128,8 +123,6 @@ public UserDetails loadUserByUsername(String idOrFullName) throws UsernameNotFou } catch (ExecutionException | UncheckedExecutionException e) { if (e.getCause() instanceof UsernameNotFoundException) { throw ((UsernameNotFoundException)e.getCause()); - } else if (e.getCause() instanceof DataAccessException) { - throw ((DataAccessException)e.getCause()); } else { throw e; } @@ -169,7 +162,7 @@ private Retriever(final String idOrFullName) { public UserDetails call() throws Exception { try { Jenkins jenkins = Jenkins.get(); - UserDetails userDetails = jenkins.getSecurityRealm().loadUserByUsername(idOrFullName); + UserDetails userDetails = jenkins.getSecurityRealm().loadUserByUsername2(idOrFullName); if (userDetails == null) { existenceCache.put(this.idOrFullName, Boolean.FALSE); throw new NullPointerException("hudson.security.SecurityRealm should never return null. " @@ -180,9 +173,6 @@ public UserDetails call() throws Exception { } catch (UsernameNotFoundException e) { existenceCache.put(this.idOrFullName, Boolean.FALSE); throw e; - } catch (DataAccessException e) { - existenceCache.invalidate(this.idOrFullName); - throw e; } } } diff --git a/core/src/main/java/jenkins/security/seed/UserSeedSecurityListener.java b/core/src/main/java/jenkins/security/seed/UserSeedSecurityListener.java index 754700e62b14..744fee0384ed 100644 --- a/core/src/main/java/jenkins/security/seed/UserSeedSecurityListener.java +++ b/core/src/main/java/jenkins/security/seed/UserSeedSecurityListener.java @@ -26,7 +26,6 @@ import hudson.Extension; import hudson.model.User; import jenkins.security.SecurityListener; -import org.acegisecurity.userdetails.UserDetails; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.Stapler; @@ -34,6 +33,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import javax.servlet.http.HttpSession; +import org.springframework.security.core.userdetails.UserDetails; /** * Inject the user seed inside the session (when there is an existing request) as part of the re-authentication mechanism @@ -48,7 +48,7 @@ protected void loggedIn(@NonNull String username) { } @Override - protected void authenticated(@NonNull UserDetails details) { + protected void authenticated2(@NonNull UserDetails details) { putUserSeedInSession(details.getUsername(), false); } @@ -56,7 +56,7 @@ private static void putUserSeedInSession(String username, boolean overwriteSessi StaplerRequest req = Stapler.getCurrentRequest(); if (req == null) { // expected case: CLI - // But also HudsonPrivateSecurityRealm because of a redirect from Acegi, the request is not a Stapler one + // But also HudsonPrivateSecurityRealm because of a redirect from Spring Security, the request is not a Stapler one return; } diff --git a/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java b/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java index c0dbc11ebf91..2b764f2f80b2 100644 --- a/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java +++ b/core/src/main/java/jenkins/triggers/ReverseBuildTrigger.java @@ -24,6 +24,8 @@ package jenkins.triggers; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.ExtensionList; import hudson.Util; @@ -65,17 +67,14 @@ import jenkins.model.DependencyDeclarer; import jenkins.model.Jenkins; import jenkins.model.ParameterizedJobMixIn; -import org.acegisecurity.AccessDeniedException; -import org.acegisecurity.Authentication; import org.apache.commons.lang.StringUtils; import org.jenkinsci.Symbol; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; - -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.Authentication; /** * Like {@link BuildTrigger} but defined on the downstream project. @@ -144,12 +143,12 @@ private boolean shouldTrigger(Run upstreamBuild, TaskListener listener) { downstreamDiscoverable = true; } - Authentication originalAuth = Jenkins.getAuthentication(); + Authentication originalAuth = Jenkins.getAuthentication2(); Job upstream = upstreamBuild.getParent(); - Authentication auth = Tasks.getAuthenticationOf((Queue.Task) job); + Authentication auth = Tasks.getAuthenticationOf2((Queue.Task) job); Item authUpstream = null; - try (ACLContext ctx = ACL.as(auth)) { + try (ACLContext ctx = ACL.as2(auth)) { authUpstream = jenkins.getItemByFullName(upstream.getFullName()); // No need to check Item.BUILD on downstream, because the downstream project’s configurer has asked for this. } catch (AccessDeniedException ade) { @@ -246,7 +245,7 @@ synchronized void invalidateCache() { } private Map> calculateCache() { - try (ACLContext acl = ACL.as(ACL.SYSTEM)) { + try (ACLContext acl = ACL.as2(ACL.SYSTEM2)) { final Map> result = new WeakHashMap<>(); for (Job downstream : Jenkins.get().allItems(Job.class)) { ReverseBuildTrigger trigger = @@ -307,7 +306,7 @@ private Map> calculateCache() { public static class ItemListenerImpl extends ItemListener { @Override public void onLocationChanged(Item item, final String oldFullName, final String newFullName) { - try (ACLContext acl = ACL.as(ACL.SYSTEM)) { + try (ACLContext acl = ACL.as2(ACL.SYSTEM2)) { for (Job p : Jenkins.get().allItems(Job.class)) { ReverseBuildTrigger t = ParameterizedJobMixIn.getTrigger(p, ReverseBuildTrigger.class); if (t != null) { diff --git a/core/src/main/java/jenkins/util/AtmostOneTaskExecutor.java b/core/src/main/java/jenkins/util/AtmostOneTaskExecutor.java index 6f4b04a17606..dd3e6f2f1664 100644 --- a/core/src/main/java/jenkins/util/AtmostOneTaskExecutor.java +++ b/core/src/main/java/jenkins/util/AtmostOneTaskExecutor.java @@ -75,7 +75,7 @@ public AtmostOneTaskExecutor(Callable task) { this(new ImpersonatingExecutorService(new AtmostOneThreadExecutor(new NamingThreadFactory( new DaemonThreadFactory(), String.format("AtmostOneTaskExecutor[%s]", task) - )), ACL.SYSTEM), + )), ACL.SYSTEM2), task ); } diff --git a/core/src/main/java/jenkins/util/ProgressiveRendering.java b/core/src/main/java/jenkins/util/ProgressiveRendering.java index 137ca68b63b2..f69ca02c3fc8 100644 --- a/core/src/main/java/jenkins/util/ProgressiveRendering.java +++ b/core/src/main/java/jenkins/util/ProgressiveRendering.java @@ -42,8 +42,6 @@ import javax.servlet.http.HttpServletRequest; import net.sf.json.JSON; import net.sf.json.JSONObject; -import org.acegisecurity.context.SecurityContext; -import org.acegisecurity.context.SecurityContextHolder; import org.kohsuke.stapler.Ancestor; import org.kohsuke.stapler.RequestImpl; import org.kohsuke.stapler.Stapler; @@ -52,6 +50,8 @@ import org.kohsuke.stapler.bind.BoundObjectTable; import org.kohsuke.stapler.bind.JavaScriptMethod; import org.kohsuke.stapler.jelly.BindTag; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; /** * A helper thread which does some computation in the background and displays incremental results using JavaScript. diff --git a/core/src/main/java/jenkins/util/Timer.java b/core/src/main/java/jenkins/util/Timer.java index 3dd80b216643..26b15f16497f 100644 --- a/core/src/main/java/jenkins/util/Timer.java +++ b/core/src/main/java/jenkins/util/Timer.java @@ -44,7 +44,7 @@ public static synchronized ScheduledExecutorService get() { // corePoolSize is set to 10, but will only be created if needed. // ScheduledThreadPoolExecutor "acts as a fixed-sized pool using corePoolSize threads" // TODO consider also wrapping in ContextResettingExecutorService - executorService = new ImpersonatingScheduledExecutorService(new ErrorLoggingScheduledThreadPoolExecutor(10, new NamingThreadFactory(new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()), "jenkins.util.Timer")), ACL.SYSTEM); + executorService = new ImpersonatingScheduledExecutorService(new ErrorLoggingScheduledThreadPoolExecutor(10, new NamingThreadFactory(new ClassLoaderSanityThreadFactory(new DaemonThreadFactory()), "jenkins.util.Timer")), ACL.SYSTEM2); } return executorService; } diff --git a/core/src/main/java/org/acegisecurity/AccessDeniedException.java b/core/src/main/java/org/acegisecurity/AccessDeniedException.java new file mode 100644 index 000000000000..210bcf8de452 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/AccessDeniedException.java @@ -0,0 +1,46 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +/** + * @deprecated use {@link org.springframework.security.access.AccessDeniedException} + */ +@Deprecated +public class AccessDeniedException extends AcegiSecurityException { + + public AccessDeniedException(String msg) { + super(msg); + } + + public AccessDeniedException(String msg, Throwable t) { + super(msg, t); + } + + @Override + public org.springframework.security.access.AccessDeniedException toSpring() { + return new org.springframework.security.access.AccessDeniedException(toString(), this); + } + +} diff --git a/core/src/main/java/org/acegisecurity/AccountExpiredException.java b/core/src/main/java/org/acegisecurity/AccountExpiredException.java new file mode 100644 index 000000000000..7d9bfc931c46 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/AccountExpiredException.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +/** + * @deprecated use {@link org.springframework.security.authentication.AccountExpiredException} + */ +@Deprecated +public class AccountExpiredException extends AuthenticationException { + + public AccountExpiredException(String msg) { + super(msg); + } + + public AccountExpiredException(String msg, Throwable t) { + super(msg, t); + } + + public AccountExpiredException(String msg, Object extraInformation) { + super(msg, extraInformation); + } + + @Override + public org.springframework.security.core.AuthenticationException toSpring() { + return new org.springframework.security.authentication.AccountExpiredException(toString(), this); + } + + public static AccountExpiredException fromSpring(org.springframework.security.authentication.AccountExpiredException x) { + return new AccountExpiredException(x.toString(), x); + } + +} diff --git a/core/src/main/java/org/acegisecurity/AcegiSecurityException.java b/core/src/main/java/org/acegisecurity/AcegiSecurityException.java new file mode 100644 index 000000000000..bfc529de387c --- /dev/null +++ b/core/src/main/java/org/acegisecurity/AcegiSecurityException.java @@ -0,0 +1,49 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +import org.springframework.core.NestedRuntimeException; + +/** + * @deprecated use {@link RuntimeException} + */ +@Deprecated +public abstract class AcegiSecurityException extends NestedRuntimeException { + + public AcegiSecurityException(String msg) { + super(msg); + } + + public AcegiSecurityException(String msg, Throwable cause) { + super(msg, cause); + } + + public RuntimeException toSpring() { + return new RuntimeException(toString(), this); + } + + // TODO fromSpring as needed, delegating to AccessDeniedException or AuthenticationException + +} diff --git a/core/src/main/java/org/acegisecurity/Authentication.java b/core/src/main/java/org/acegisecurity/Authentication.java new file mode 100644 index 000000000000..09aebc731692 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/Authentication.java @@ -0,0 +1,114 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.security.ACL; +import java.io.Serializable; +import java.security.Principal; +import java.util.Objects; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; +import org.acegisecurity.userdetails.UserDetails; + +/** + * @deprecated use TODO or {@link org.springframework.security.core.Authentication} + */ +@Deprecated +public interface Authentication extends Principal, Serializable { + + GrantedAuthority[] getAuthorities(); + + Object getCredentials(); + + Object getDetails(); + + Object getPrincipal(); + + boolean isAuthenticated(); + + void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; + + static @NonNull Authentication fromSpring(@NonNull org.springframework.security.core.Authentication a) { + Objects.requireNonNull(a); + if (a == ACL.SYSTEM2) { + return ACL.SYSTEM; + } else if (a instanceof org.springframework.security.authentication.AnonymousAuthenticationToken) { + return new AnonymousAuthenticationToken((org.springframework.security.authentication.AnonymousAuthenticationToken) a); + } else if (a instanceof org.springframework.security.authentication.UsernamePasswordAuthenticationToken) { + return new UsernamePasswordAuthenticationToken((org.springframework.security.authentication.UsernamePasswordAuthenticationToken) a); + } else if (a instanceof AuthenticationSpringImpl) { + return ((AuthenticationSpringImpl) a).delegate; + } else { + return new Authentication() { + @Override + public GrantedAuthority[] getAuthorities() { + return GrantedAuthority.fromSpring(a.getAuthorities()); + } + @Override + public Object getCredentials() { + return a.getCredentials(); // seems to be String, typically, so nothing to wrap + } + @Override + public Object getDetails() { + // Could try to wrap WebAuthenticationDetails, but it does not appear that any code actual checkcasts this. + return a.getDetails(); + } + @Override + public Object getPrincipal() { + return UserDetails.fromSpringPrincipal(a.getPrincipal()); + } + @Override + public boolean isAuthenticated() { + return a.isAuthenticated(); + } + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + a.setAuthenticated(isAuthenticated); + } + @Override + public String getName() { + return a.getName(); + } + @Override + public boolean equals(Object o) { + return o instanceof Authentication && ((Authentication) o).getName().equals(getName()); + } + @Override + public int hashCode() { + return getName().hashCode(); + } + @Override + public String toString() { + return super.toString() + ": " + getName(); + } + }; + } + } + + default @NonNull org.springframework.security.core.Authentication toSpring() { + return new AuthenticationSpringImpl(this); + } + +} diff --git a/core/src/main/java/org/acegisecurity/AuthenticationException.java b/core/src/main/java/org/acegisecurity/AuthenticationException.java new file mode 100644 index 000000000000..ff3f9bc9a545 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/AuthenticationException.java @@ -0,0 +1,104 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +import hudson.security.UserMayOrMayNotExistException2; +import org.acegisecurity.providers.ProviderNotFoundException; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import org.springframework.dao.DataAccessException; + +/** + * @deprecated use {@link org.springframework.security.core.AuthenticationException} + */ +@Deprecated +public abstract class AuthenticationException extends AcegiSecurityException { + + private Authentication authentication; + private Object extraInformation; + + public AuthenticationException(String msg) { + super(msg); + } + + public AuthenticationException(String msg, Object extraInformation) { + super(msg); + this.extraInformation = extraInformation; + } + + public AuthenticationException(String msg, Throwable t) { + super(msg, t); + } + + public Authentication getAuthentication() { + return authentication; + } + + public void setAuthentication(Authentication authentication) { + this.authentication = authentication; + } + + public Object getExtraInformation() { + return extraInformation; + } + + public void clearExtraInformation() { + extraInformation = null; + } + + @Override + public org.springframework.security.core.AuthenticationException toSpring() { + return new org.springframework.security.core.AuthenticationException(toString(), this) {}; + } + + /** + * @return either an {@link AuthenticationException} or a {@link DataAccessException} + */ + public static RuntimeException fromSpring(org.springframework.security.core.AuthenticationException x) { + if (x instanceof org.springframework.security.authentication.BadCredentialsException) { + return BadCredentialsException.fromSpring((org.springframework.security.authentication.BadCredentialsException) x); + } else if (x instanceof org.springframework.security.authentication.AuthenticationServiceException) { + return AuthenticationServiceException.fromSpring((org.springframework.security.authentication.AuthenticationServiceException) x); + } else if (x instanceof org.springframework.security.authentication.AccountExpiredException) { + return AccountExpiredException.fromSpring((org.springframework.security.authentication.AccountExpiredException) x); + } else if (x instanceof org.springframework.security.authentication.CredentialsExpiredException) { + return CredentialsExpiredException.fromSpring((org.springframework.security.authentication.CredentialsExpiredException) x); + } else if (x instanceof org.springframework.security.authentication.DisabledException) { + return DisabledException.fromSpring((org.springframework.security.authentication.DisabledException) x); + } else if (x instanceof org.springframework.security.authentication.InsufficientAuthenticationException) { + return InsufficientAuthenticationException.fromSpring((org.springframework.security.authentication.InsufficientAuthenticationException) x); + } else if (x instanceof org.springframework.security.authentication.LockedException) { + return LockedException.fromSpring((org.springframework.security.authentication.LockedException) x); + } else if (x instanceof org.springframework.security.authentication.ProviderNotFoundException) { + return ProviderNotFoundException.fromSpring((org.springframework.security.authentication.ProviderNotFoundException) x); + } else if (x instanceof UserMayOrMayNotExistException2 && x.getCause() instanceof DataAccessException) { + return (DataAccessException) x.getCause(); + } else if (x instanceof org.springframework.security.core.userdetails.UsernameNotFoundException) { + return UsernameNotFoundException.fromSpring((org.springframework.security.core.userdetails.UsernameNotFoundException) x); + } else { + return new AuthenticationException(x.toString(), x) {}; + } + } + +} diff --git a/core/src/main/java/org/acegisecurity/AuthenticationManager.java b/core/src/main/java/org/acegisecurity/AuthenticationManager.java new file mode 100644 index 000000000000..b9a0365ac1db --- /dev/null +++ b/core/src/main/java/org/acegisecurity/AuthenticationManager.java @@ -0,0 +1,55 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +/** + * @deprecated use {@link org.springframework.security.authentication.AuthenticationManager} + */ +@Deprecated +public interface AuthenticationManager { + + Authentication authenticate(Authentication authentication) throws AuthenticationException; + + static AuthenticationManager fromSpring(org.springframework.security.authentication.AuthenticationManager am) { + return authentication -> { + try { + return Authentication.fromSpring(am.authenticate(authentication.toSpring())); + } catch (org.springframework.security.core.AuthenticationException x) { + throw AuthenticationException.fromSpring(x); + } + }; + } + + default org.springframework.security.authentication.AuthenticationManager toSpring() { + return authentication -> { + try { + return authenticate(Authentication.fromSpring(authentication)).toSpring(); + } catch (AcegiSecurityException x) { + throw x.toSpring(); + } + }; + } + +} diff --git a/core/src/main/java/org/acegisecurity/AuthenticationServiceException.java b/core/src/main/java/org/acegisecurity/AuthenticationServiceException.java new file mode 100644 index 000000000000..f3471853b98d --- /dev/null +++ b/core/src/main/java/org/acegisecurity/AuthenticationServiceException.java @@ -0,0 +1,50 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +/** + * @deprecated use {@link org.springframework.security.authentication.AuthenticationServiceException} + */ +@Deprecated +public class AuthenticationServiceException extends AuthenticationException { + + public AuthenticationServiceException(String msg) { + super(msg); + } + + public AuthenticationServiceException(String msg, Throwable t) { + super(msg, t); + } + + @Override + public org.springframework.security.core.AuthenticationException toSpring() { + return new org.springframework.security.authentication.AuthenticationServiceException(toString(), this); + } + + public static AuthenticationServiceException fromSpring(org.springframework.security.authentication.AuthenticationServiceException x) { + return new AuthenticationServiceException(x.toString(), x); + } + +} diff --git a/core/src/main/java/org/acegisecurity/AuthenticationSpringImpl.java b/core/src/main/java/org/acegisecurity/AuthenticationSpringImpl.java new file mode 100644 index 000000000000..c2c8216ddc83 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/AuthenticationSpringImpl.java @@ -0,0 +1,89 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +import java.util.Collection; +import org.acegisecurity.userdetails.UserDetails; + +@Deprecated +final class AuthenticationSpringImpl implements org.springframework.security.core.Authentication { + + final Authentication delegate; + + AuthenticationSpringImpl(Authentication delegate) { + this.delegate = delegate; + } + + @Override + public Collection getAuthorities() { + return GrantedAuthority.toSpring(delegate.getAuthorities()); + } + + @Override + public Object getCredentials() { + return delegate.getCredentials(); + } + + @Override + public Object getDetails() { + return delegate.getDetails(); + } + + @Override + public Object getPrincipal() { + return UserDetails.toSpringPrincipal(delegate.getPrincipal()); + } + + @Override + public boolean isAuthenticated() { + return delegate.isAuthenticated(); + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + delegate.setAuthenticated(isAuthenticated); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public boolean equals(Object o) { + return o instanceof org.springframework.security.core.Authentication && ((org.springframework.security.core.Authentication) o).getName().equals(getName()); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public String toString() { + return super.toString() + ": " + getName(); + } + +} diff --git a/core/src/main/java/org/acegisecurity/BadCredentialsException.java b/core/src/main/java/org/acegisecurity/BadCredentialsException.java new file mode 100644 index 000000000000..cbce0baffa8c --- /dev/null +++ b/core/src/main/java/org/acegisecurity/BadCredentialsException.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +import org.acegisecurity.userdetails.UsernameNotFoundException; + +/** + * @deprecated use {@link org.springframework.security.authentication.BadCredentialsException} + */ +@Deprecated +public class BadCredentialsException extends AuthenticationException { + + public BadCredentialsException(String msg) { + super(msg); + } + + public BadCredentialsException(String msg, Object extraInformation) { + super(msg, extraInformation); + } + + public BadCredentialsException(String msg, Throwable t) { + super(msg, t); + } + + @Override + public org.springframework.security.core.AuthenticationException toSpring() { + return new org.springframework.security.authentication.BadCredentialsException(toString(), this); + } + + public static BadCredentialsException fromSpring(org.springframework.security.core.AuthenticationException x) { + if (x instanceof org.springframework.security.core.userdetails.UsernameNotFoundException) { + return UsernameNotFoundException.fromSpring((org.springframework.security.core.userdetails.UsernameNotFoundException) x); + } else { + return new BadCredentialsException(x.toString(), x); + } + } + +} diff --git a/core/src/main/java/org/acegisecurity/CredentialsExpiredException.java b/core/src/main/java/org/acegisecurity/CredentialsExpiredException.java new file mode 100644 index 000000000000..a35a8a10b54f --- /dev/null +++ b/core/src/main/java/org/acegisecurity/CredentialsExpiredException.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +/** + * @deprecated use {@link org.springframework.security.authentication.CredentialsExpiredException} + */ +@Deprecated +public class CredentialsExpiredException extends AuthenticationException { + + public CredentialsExpiredException(String msg) { + super(msg); + } + + public CredentialsExpiredException(String msg, Throwable t) { + super(msg, t); + } + + public CredentialsExpiredException(String msg, Object extraInformation) { + super(msg, extraInformation); + } + + @Override + public org.springframework.security.core.AuthenticationException toSpring() { + return new org.springframework.security.authentication.CredentialsExpiredException(toString(), this); + } + + public static CredentialsExpiredException fromSpring(org.springframework.security.authentication.CredentialsExpiredException x) { + return new CredentialsExpiredException(x.toString(), x); + } + +} diff --git a/core/src/main/java/org/acegisecurity/DisabledException.java b/core/src/main/java/org/acegisecurity/DisabledException.java new file mode 100644 index 000000000000..7861a9463b05 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/DisabledException.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +/** + * @deprecated use {@link org.springframework.security.authentication.DisabledException} + */ +@Deprecated +public class DisabledException extends AuthenticationException { + + public DisabledException(String msg) { + super(msg); + } + + public DisabledException(String msg, Throwable t) { + super(msg, t); + } + + public DisabledException(String msg, Object extraInformation) { + super(msg, extraInformation); + } + + @Override + public org.springframework.security.core.AuthenticationException toSpring() { + return new org.springframework.security.authentication.DisabledException(toString(), this); + } + + public static DisabledException fromSpring(org.springframework.security.authentication.DisabledException x) { + return new DisabledException(x.toString(), x); + } + +} diff --git a/core/src/main/java/org/acegisecurity/GrantedAuthority.java b/core/src/main/java/org/acegisecurity/GrantedAuthority.java new file mode 100644 index 000000000000..8601a1ff264c --- /dev/null +++ b/core/src/main/java/org/acegisecurity/GrantedAuthority.java @@ -0,0 +1,74 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.security.SecurityRealm; +import java.util.Collection; +import java.util.Collections; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +/** + * @deprecated use TODO or {@link org.springframework.security.core.GrantedAuthority} + */ +@Deprecated +public interface GrantedAuthority { + + String getAuthority(); + + @Override + String toString(); + + @Override + boolean equals(Object obj); + + @Override + int hashCode(); + + static @NonNull GrantedAuthority fromSpring(@NonNull org.springframework.security.core.GrantedAuthority ga) { + if (ga == SecurityRealm.AUTHENTICATED_AUTHORITY2) { + return SecurityRealm.AUTHENTICATED_AUTHORITY; + } + return new GrantedAuthorityImpl(ga.getAuthority()); + } + + default @NonNull org.springframework.security.core.GrantedAuthority toSpring() { + if (this == SecurityRealm.AUTHENTICATED_AUTHORITY) { + return SecurityRealm.AUTHENTICATED_AUTHORITY2; + } + return new SimpleGrantedAuthority(getAuthority()); + } + + static @NonNull GrantedAuthority[] fromSpring(@NonNull Collection gas) { + return gas.stream().map(GrantedAuthority::fromSpring).toArray(GrantedAuthority[]::new); + } + + static @NonNull Collection toSpring(@NonNull GrantedAuthority[] gas) { + return gas != null ? Stream.of(gas).map(GrantedAuthority::toSpring).collect(Collectors.toList()) : Collections.emptySet(); + } + +} diff --git a/core/src/main/java/org/acegisecurity/GrantedAuthorityImpl.java b/core/src/main/java/org/acegisecurity/GrantedAuthorityImpl.java new file mode 100644 index 000000000000..4c7f57275a21 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/GrantedAuthorityImpl.java @@ -0,0 +1,64 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +import java.io.Serializable; +import org.springframework.security.core.authority.SimpleGrantedAuthority; + +/** + * @deprecated use TODO or {@link SimpleGrantedAuthority} + */ +@Deprecated +public class GrantedAuthorityImpl implements GrantedAuthority, Serializable { + + private static final long serialVersionUID = 1; + + private final String role; + + public GrantedAuthorityImpl(String role) { + this.role = role; + } + + @Override + public String getAuthority() { + return role; + } + + @Override + public String toString() { + return role; + } + + @Override + public boolean equals(Object o) { + return o instanceof GrantedAuthorityImpl && role.equals(((GrantedAuthorityImpl) o).role); + } + + @Override + public int hashCode() { + return role.hashCode(); + } + +} diff --git a/core/src/main/java/org/acegisecurity/InsufficientAuthenticationException.java b/core/src/main/java/org/acegisecurity/InsufficientAuthenticationException.java new file mode 100644 index 000000000000..1d8e28c9ffb9 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/InsufficientAuthenticationException.java @@ -0,0 +1,50 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +/** + * @deprecated use {@link org.springframework.security.authentication.InsufficientAuthenticationException} + */ +@Deprecated +public class InsufficientAuthenticationException extends AuthenticationException { + + public InsufficientAuthenticationException(String msg) { + super(msg); + } + + public InsufficientAuthenticationException(String msg, Throwable t) { + super(msg, t); + } + + @Override + public org.springframework.security.core.AuthenticationException toSpring() { + return new org.springframework.security.authentication.InsufficientAuthenticationException(toString(), this); + } + + public static InsufficientAuthenticationException fromSpring(org.springframework.security.authentication.InsufficientAuthenticationException x) { + return new InsufficientAuthenticationException(x.toString(), x); + } + +} diff --git a/core/src/main/java/org/acegisecurity/LockedException.java b/core/src/main/java/org/acegisecurity/LockedException.java new file mode 100644 index 000000000000..2b64549f105f --- /dev/null +++ b/core/src/main/java/org/acegisecurity/LockedException.java @@ -0,0 +1,54 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity; + +/** + * @deprecated use {@link org.springframework.security.authentication.LockedException} + */ +@Deprecated +public class LockedException extends AuthenticationException { + + public LockedException(String msg) { + super(msg); + } + + public LockedException(String msg, Throwable t) { + super(msg, t); + } + + public LockedException(String msg, Object extraInformation) { + super(msg, extraInformation); + } + + @Override + public org.springframework.security.core.AuthenticationException toSpring() { + return new org.springframework.security.authentication.LockedException(toString(), this); + } + + public static LockedException fromSpring(org.springframework.security.authentication.LockedException x) { + return new LockedException(x.toString(), x); + } + +} diff --git a/core/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java b/core/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java new file mode 100644 index 000000000000..30f3ab5dbf4c --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acls/sid/GrantedAuthoritySid.java @@ -0,0 +1,72 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.acls.sid; + +import java.util.Objects; +import org.springframework.security.core.GrantedAuthority; + +public class GrantedAuthoritySid implements Sid { + + private final String grantedAuthority; + + public GrantedAuthoritySid(String grantedAuthority) { + this.grantedAuthority = grantedAuthority; + } + + /** + * @since TODO + */ + public GrantedAuthoritySid(GrantedAuthority ga) { + grantedAuthority = ga.getAuthority(); + } + + /** + * @deprecated use {@link #GrantedAuthoritySid(GrantedAuthority)} + */ + @Deprecated + public GrantedAuthoritySid(org.acegisecurity.GrantedAuthority ga) { + this(ga.toSpring()); + } + + public String getGrantedAuthority() { + return grantedAuthority; + } + + @Override + public boolean equals(Object o) { + return o instanceof GrantedAuthoritySid && Objects.equals(grantedAuthority, ((GrantedAuthoritySid) o).grantedAuthority); + } + + @Override + public int hashCode() { + return grantedAuthority.hashCode(); + } + + @Override + public String toString() { + return grantedAuthority; + } + +} diff --git a/core/src/main/java/org/acegisecurity/acls/sid/PrincipalSid.java b/core/src/main/java/org/acegisecurity/acls/sid/PrincipalSid.java new file mode 100644 index 000000000000..9f620b136469 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acls/sid/PrincipalSid.java @@ -0,0 +1,74 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.acls.sid; + +import java.util.Objects; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; + +public class PrincipalSid implements Sid { + + private final String principal; + + public PrincipalSid(String principal) { + this.principal = principal; + } + + /** + * @since TODO + */ + public PrincipalSid(Authentication a) { + Object p = a.getPrincipal(); + this.principal = p instanceof UserDetails ? ((UserDetails) p).getUsername() : p.toString(); + } + + /** + * @deprecated use {@link #PrincipalSid(Authentication)} + */ + @Deprecated + public PrincipalSid(org.acegisecurity.Authentication a) { + this(a.toSpring()); + } + + public String getPrincipal() { + return principal; + } + + @Override + public boolean equals(Object o) { + return o instanceof PrincipalSid && Objects.equals(principal, ((PrincipalSid) o).principal); + } + + @Override + public int hashCode() { + return principal.hashCode(); + } + + @Override + public String toString() { + return principal; + } + +} diff --git a/core/src/main/java/org/acegisecurity/acls/sid/Sid.java b/core/src/main/java/org/acegisecurity/acls/sid/Sid.java new file mode 100644 index 000000000000..9d829d9c022f --- /dev/null +++ b/core/src/main/java/org/acegisecurity/acls/sid/Sid.java @@ -0,0 +1,36 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.acls.sid; + +// TODO perhaps delegate to the org.springframework.security.acls package +public interface Sid { + + @Override + public boolean equals(Object obj); + + @Override + public int hashCode(); + +} diff --git a/core/src/main/java/org/acegisecurity/context/SecurityContext.java b/core/src/main/java/org/acegisecurity/context/SecurityContext.java new file mode 100644 index 000000000000..f88949453656 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/context/SecurityContext.java @@ -0,0 +1,70 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.context; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.model.User; +import hudson.security.ACL; +import org.acegisecurity.Authentication; + +/** + * @deprecated Use {@link ACL#as(User)} or {@link org.springframework.security.core.context.SecurityContext} + */ +@Deprecated +public interface SecurityContext { + + Authentication getAuthentication(); + + void setAuthentication(Authentication a); + + static @NonNull SecurityContext fromSpring(@NonNull org.springframework.security.core.context.SecurityContext c) { + return new SecurityContext() { + @Override + public Authentication getAuthentication() { + org.springframework.security.core.Authentication a = c.getAuthentication(); + return a != null ? Authentication.fromSpring(a) : null; + } + @Override + public void setAuthentication(Authentication a) { + c.setAuthentication(a != null ? a.toSpring() : null); + } + }; + } + + default @NonNull org.springframework.security.core.context.SecurityContext toSpring() { + return new org.springframework.security.core.context.SecurityContext() { + @Override + public org.springframework.security.core.Authentication getAuthentication() { + Authentication a = SecurityContext.this.getAuthentication(); + return a != null ? a.toSpring() : null; + } + @Override + public void setAuthentication(org.springframework.security.core.Authentication authentication) { + SecurityContext.this.setAuthentication(authentication != null ? Authentication.fromSpring(authentication) : null); + } + }; + } + +} diff --git a/core/src/main/java/org/acegisecurity/context/SecurityContextHolder.java b/core/src/main/java/org/acegisecurity/context/SecurityContextHolder.java new file mode 100644 index 000000000000..db619d69cce0 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/context/SecurityContextHolder.java @@ -0,0 +1,49 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.context; + +import hudson.model.User; +import hudson.security.ACL; +import org.acegisecurity.Authentication; + +/** + * @deprecated Use {@link ACL#as(User)} or {@link org.springframework.security.core.context.SecurityContext}. + */ +@Deprecated +public class SecurityContextHolder { + + public static SecurityContext getContext() { + return SecurityContext.fromSpring(org.springframework.security.core.context.SecurityContextHolder.getContext()); + } + + public static void setContext(SecurityContext c) { + org.springframework.security.core.context.SecurityContextHolder.setContext(c.toSpring()); + } + + public static void clearContext() { + org.springframework.security.core.context.SecurityContextHolder.clearContext(); + } + +} diff --git a/core/src/main/java/org/acegisecurity/context/SecurityContextImpl.java b/core/src/main/java/org/acegisecurity/context/SecurityContextImpl.java new file mode 100644 index 000000000000..30a084035630 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/context/SecurityContextImpl.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.context; + +import org.acegisecurity.Authentication; + +/** + * @deprecated use {@link org.springframework.security.core.context.SecurityContextImpl} + */ +@Deprecated +public class SecurityContextImpl implements SecurityContext { + + private Authentication authentication; + + @Override + public Authentication getAuthentication() { + return authentication; + } + + @Override + public void setAuthentication(Authentication authentication) { + this.authentication = authentication; + } + +} diff --git a/core/src/main/java/org/acegisecurity/package-info.java b/core/src/main/java/org/acegisecurity/package-info.java new file mode 100644 index 000000000000..e583fe6245aa --- /dev/null +++ b/core/src/main/java/org/acegisecurity/package-info.java @@ -0,0 +1,29 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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. + */ + +/** + * Façade for {@code org.springframework.security}. + * Should only be used for plugin compatibility. + */ +package org.acegisecurity; diff --git a/core/src/main/java/org/acegisecurity/providers/AbstractAuthenticationToken.java b/core/src/main/java/org/acegisecurity/providers/AbstractAuthenticationToken.java new file mode 100644 index 000000000000..266e581e40d6 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/AbstractAuthenticationToken.java @@ -0,0 +1,101 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.providers; + +import java.util.Arrays; +import java.util.Objects; +import org.acegisecurity.Authentication; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.userdetails.UserDetails; + +/** + * @deprecated use {@link org.springframework.security.authentication.AbstractAuthenticationToken} + */ +@Deprecated +public abstract class AbstractAuthenticationToken implements Authentication { + + private final GrantedAuthority[] authorities; + private Object details; + private boolean authenticated; + + public AbstractAuthenticationToken() { + this.authorities = new GrantedAuthority[0]; + } + + public AbstractAuthenticationToken(GrantedAuthority[] authorities) { + this.authorities = authorities; + } + + @Override + public String getName() { + Object principal = getPrincipal(); + return principal instanceof UserDetails ? ((UserDetails) principal).getUsername() : String.valueOf(principal); + } + + @Override + public GrantedAuthority[] getAuthorities() { + return authorities; + } + + @Override + public Object getDetails() { + return details; + } + + public void setDetails(Object details) { + this.details = details; + } + + @Override + public boolean isAuthenticated() { + return authenticated; + } + + @Override + public void setAuthenticated(boolean authenticated) { + this.authenticated = authenticated; + } + + @Override + public String toString() { + return super.toString() + getName(); + } + + @Override + public boolean equals(Object o) { + return o instanceof AbstractAuthenticationToken && + Objects.equals(getPrincipal(), ((AbstractAuthenticationToken) o).getPrincipal()) && + Objects.equals(getDetails(), ((AbstractAuthenticationToken) o).getDetails()) && + Objects.equals(getCredentials(), ((AbstractAuthenticationToken) o).getCredentials()) && + isAuthenticated() == ((AbstractAuthenticationToken) o).isAuthenticated() && + Arrays.equals(getAuthorities(), ((AbstractAuthenticationToken) o).getAuthorities()); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + +} diff --git a/core/src/main/java/hudson/security/NotSerilizableSecurityContext.java b/core/src/main/java/org/acegisecurity/providers/AuthenticationProvider.java similarity index 73% rename from core/src/main/java/hudson/security/NotSerilizableSecurityContext.java rename to core/src/main/java/org/acegisecurity/providers/AuthenticationProvider.java index c816747ef6a9..4dcbeb7da493 100644 --- a/core/src/main/java/hudson/security/NotSerilizableSecurityContext.java +++ b/core/src/main/java/org/acegisecurity/providers/AuthenticationProvider.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright 2013 Jesse Glick. + * Copyright 2020 CloudBees, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -22,21 +22,19 @@ * THE SOFTWARE. */ -package hudson.security; +package org.acegisecurity.providers; -import jenkins.security.NonSerializableSecurityContext; import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationException; /** - * @deprecated use {@link NonSerializableSecurityContext} instead + * @deprecated use {@link org.springframework.security.authentication.AuthenticationProvider} */ @Deprecated -public class NotSerilizableSecurityContext extends NonSerializableSecurityContext { +public interface AuthenticationProvider { - public NotSerilizableSecurityContext() {} + Authentication authenticate(Authentication authentication) throws AuthenticationException; - public NotSerilizableSecurityContext(Authentication authentication) { - super(authentication); - } + boolean supports(Class authentication); } diff --git a/core/src/main/java/org/acegisecurity/providers/ProviderNotFoundException.java b/core/src/main/java/org/acegisecurity/providers/ProviderNotFoundException.java new file mode 100644 index 000000000000..866486829f86 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/ProviderNotFoundException.java @@ -0,0 +1,52 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.providers; + +import org.acegisecurity.AuthenticationException; + +/** + * @deprecated use {@link org.springframework.security.authentication.ProviderNotFoundException} + */ +@Deprecated +public class ProviderNotFoundException extends AuthenticationException { + + public ProviderNotFoundException(String msg) { + super(msg); + } + + public ProviderNotFoundException(String msg, Throwable t) { + super(msg, t); + } + + @Override + public org.springframework.security.core.AuthenticationException toSpring() { + return (org.springframework.security.core.AuthenticationException) new org.springframework.security.authentication.ProviderNotFoundException(toString()).initCause(this); + } + + public static ProviderNotFoundException fromSpring(org.springframework.security.authentication.ProviderNotFoundException x) { + return new ProviderNotFoundException(x.toString(), x); + } + +} diff --git a/core/src/main/java/org/acegisecurity/providers/UsernamePasswordAuthenticationToken.java b/core/src/main/java/org/acegisecurity/providers/UsernamePasswordAuthenticationToken.java new file mode 100644 index 000000000000..4edb89d8b092 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/UsernamePasswordAuthenticationToken.java @@ -0,0 +1,116 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.providers; + +import hudson.security.ACL; +import org.acegisecurity.Authentication; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.userdetails.UserDetails; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * @deprecated use {@link org.springframework.security.authentication.UsernamePasswordAuthenticationToken} + */ +@Deprecated +public class UsernamePasswordAuthenticationToken implements Authentication { + + // TODO perhaps better to extend AbstractAuthenticationToken + private final org.springframework.security.authentication.UsernamePasswordAuthenticationToken delegate; + + @Restricted(NoExternalUse.class) + public UsernamePasswordAuthenticationToken(org.springframework.security.authentication.UsernamePasswordAuthenticationToken delegate) { + this.delegate = delegate; + } + + public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { + this(new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(UserDetails.toSpringPrincipal(principal), credentials)); + } + + public UsernamePasswordAuthenticationToken(Object principal, Object credentials, GrantedAuthority[] authorities) { + this(new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(UserDetails.toSpringPrincipal(principal), credentials, GrantedAuthority.toSpring(authorities))); + } + + @Override + public GrantedAuthority[] getAuthorities() { + return GrantedAuthority.fromSpring(delegate.getAuthorities()); + } + + @Override + public Object getCredentials() { + return delegate.getCredentials(); + } + + @Override + public Object getDetails() { + return delegate.getDetails(); + } + + public void setDetails(Object details) { + delegate.setDetails(details); + } + + @Override + public Object getPrincipal() { + return UserDetails.fromSpringPrincipal(delegate.getPrincipal()); + } + + @Override + public boolean isAuthenticated() { + return delegate.isAuthenticated(); + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + delegate.setAuthenticated(isAuthenticated); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public boolean equals(Object o) { + return o instanceof Authentication && ((Authentication) o).getName().equals(getName()); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public String toString() { + return super.toString() + ": " + getName(); + } + + @Override + public org.springframework.security.core.Authentication toSpring() { + return delegate; + } + + // TODO Serializable? +} diff --git a/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationToken.java b/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationToken.java new file mode 100644 index 000000000000..a60e9865ce2d --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousAuthenticationToken.java @@ -0,0 +1,111 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.providers.anonymous; + +import java.io.Serializable; +import org.acegisecurity.Authentication; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.userdetails.UserDetails; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * @deprecated use {@link org.springframework.security.authentication.AnonymousAuthenticationToken} + */ +@Deprecated +public class AnonymousAuthenticationToken implements Authentication, Serializable { + + // TODO perhaps better to extend AbstractAuthenticationToken + private final org.springframework.security.authentication.AnonymousAuthenticationToken delegate; + + @Restricted(NoExternalUse.class) + public AnonymousAuthenticationToken(org.springframework.security.authentication.AnonymousAuthenticationToken delegate) { + this.delegate = delegate; + } + + public AnonymousAuthenticationToken(String key, Object principal, GrantedAuthority[] authorities) { + this(new org.springframework.security.authentication.AnonymousAuthenticationToken(key, UserDetails.toSpringPrincipal(principal), GrantedAuthority.toSpring(authorities))); + } + + @Override + public GrantedAuthority[] getAuthorities() { + return GrantedAuthority.fromSpring(delegate.getAuthorities()); + } + + @Override + public Object getCredentials() { + return delegate.getCredentials(); + } + + @Override + public Object getDetails() { + return delegate.getDetails(); + } + + public void setDetails(Object details) { + delegate.setDetails(details); + } + + @Override + public Object getPrincipal() { + return UserDetails.fromSpringPrincipal(delegate.getPrincipal()); + } + + @Override + public boolean isAuthenticated() { + return delegate.isAuthenticated(); + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + delegate.setAuthenticated(isAuthenticated); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public boolean equals(Object o) { + return o instanceof Authentication && ((Authentication) o).getName().equals(getName()); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public String toString() { + return super.toString() + ": " + getName(); + } + + @Override + public org.springframework.security.core.Authentication toSpring() { + return delegate; + } + +} diff --git a/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java new file mode 100644 index 000000000000..69720e7b4d45 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java @@ -0,0 +1,79 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.providers.dao; + +import org.acegisecurity.AcegiSecurityException; +import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationException; +import org.acegisecurity.providers.AuthenticationProvider; +import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import org.acegisecurity.userdetails.UserDetails; + +/** + * @deprecated use {@link org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider} + */ +@Deprecated +public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider { + + private final org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider delegate = new org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider() { + @Override + protected void additionalAuthenticationChecks(org.springframework.security.core.userdetails.UserDetails userDetails, org.springframework.security.authentication.UsernamePasswordAuthenticationToken authentication) throws org.springframework.security.core.AuthenticationException { + try { + AbstractUserDetailsAuthenticationProvider.this.additionalAuthenticationChecks(UserDetails.fromSpring(userDetails), new UsernamePasswordAuthenticationToken(authentication)); + } catch (AcegiSecurityException x) { + throw x.toSpring(); + } + } + @Override + protected org.springframework.security.core.userdetails.UserDetails retrieveUser(String username, org.springframework.security.authentication.UsernamePasswordAuthenticationToken authentication) throws org.springframework.security.core.AuthenticationException { + try { + return AbstractUserDetailsAuthenticationProvider.this.retrieveUser(username, new UsernamePasswordAuthenticationToken(authentication)).toSpring(); + } catch (AcegiSecurityException x) { + throw x.toSpring(); + } + } + }; + + protected abstract void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException; + + protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + try { + return Authentication.fromSpring(delegate.authenticate(authentication.toSpring())); + } catch (org.springframework.security.core.AuthenticationException x) { + throw AuthenticationException.fromSpring(x); + } + } + + @Override + public boolean supports(Class authentication) { + return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); + } + + // TODO other methods as needed: createSuccessAuthentication, getUserCache, etc. + +} diff --git a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticator2.java b/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticator2.java deleted file mode 100644 index 49370935bc15..000000000000 --- a/core/src/main/java/org/acegisecurity/providers/ldap/authenticator/BindAuthenticator2.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi - * - * 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 org.acegisecurity.providers.ldap.authenticator; - -import org.acegisecurity.ldap.InitialDirContextFactory; -import org.acegisecurity.userdetails.ldap.LdapUserDetails; - -import java.util.logging.Logger; -import java.util.logging.Level; - -/** - * {@link BindAuthenticator} with improved diagnostics. - * - * @author Kohsuke Kawaguchi - */ -public class BindAuthenticator2 extends BindAuthenticator { - /** - * If we ever had a successful authentication, - */ - private boolean hadSuccessfulAuthentication; - - public BindAuthenticator2(InitialDirContextFactory initialDirContextFactory) { - super(initialDirContextFactory); - } - - @Override - public LdapUserDetails authenticate(String username, String password) { - LdapUserDetails user = super.authenticate(username, password); - hadSuccessfulAuthentication = true; - return user; - } - - @Override - void handleBindException(String userDn, String username, Throwable cause) { - LOGGER.log(hadSuccessfulAuthentication? Level.FINE : Level.WARNING, - "Failed to bind to LDAP: userDn"+userDn+" username="+username,cause); - super.handleBindException(userDn, username, cause); - } - - private static final Logger LOGGER = Logger.getLogger(BindAuthenticator2.class.getName()); -} diff --git a/core/src/main/java/org/acegisecurity/ui/WebAuthenticationDetails.java b/core/src/main/java/org/acegisecurity/ui/WebAuthenticationDetails.java new file mode 100644 index 000000000000..39af9f2e9c8a --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/WebAuthenticationDetails.java @@ -0,0 +1,38 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.ui; + +import java.io.Serializable; +import javax.servlet.http.HttpServletRequest; + +/** + * @deprecated use {@link org.springframework.security.web.authentication.WebAuthenticationDetails} + */ +@Deprecated +public class WebAuthenticationDetails implements Serializable { + + public WebAuthenticationDetails(HttpServletRequest request) {} + +} diff --git a/core/src/main/java/org/acegisecurity/ui/rememberme/RememberMeServices.java b/core/src/main/java/org/acegisecurity/ui/rememberme/RememberMeServices.java new file mode 100644 index 000000000000..36f2635f20c5 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/rememberme/RememberMeServices.java @@ -0,0 +1,68 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.ui.rememberme; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.acegisecurity.Authentication; + +/** + * @deprecated use {@link org.springframework.security.web.authentication.RememberMeServices} + */ +@Deprecated +public interface RememberMeServices { + + Authentication autoLogin(HttpServletRequest request, HttpServletResponse response); + + void loginFail(HttpServletRequest request, HttpServletResponse response); + + void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication); + + static RememberMeServices fromSpring(org.springframework.security.web.authentication.RememberMeServices rms) { + if (rms instanceof RememberMeServicesSpringImpl) { + return ((RememberMeServicesSpringImpl) rms).delegate; + } + return new RememberMeServices() { + @Override + public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { + org.springframework.security.core.Authentication a = rms.autoLogin(request, response); + return a != null ? Authentication.fromSpring(a) : null; + } + @Override + public void loginFail(HttpServletRequest request, HttpServletResponse response) { + rms.loginFail(request, response); + } + @Override + public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication) { + rms.loginSuccess(request, response, successfulAuthentication.toSpring()); + } + }; + } + + default org.springframework.security.web.authentication.RememberMeServices toSpring() { + return new RememberMeServicesSpringImpl(this); + } + +} diff --git a/core/src/main/java/org/acegisecurity/ui/rememberme/RememberMeServicesSpringImpl.java b/core/src/main/java/org/acegisecurity/ui/rememberme/RememberMeServicesSpringImpl.java new file mode 100644 index 000000000000..5a0cd0d63d86 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/rememberme/RememberMeServicesSpringImpl.java @@ -0,0 +1,55 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.ui.rememberme; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.acegisecurity.Authentication; + +final class RememberMeServicesSpringImpl implements org.springframework.security.web.authentication.RememberMeServices { + + final RememberMeServices delegate; + + RememberMeServicesSpringImpl(RememberMeServices delegate) { + this.delegate = delegate; + } + + @Override + public org.springframework.security.core.Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { + Authentication a = delegate.autoLogin(request, response); + return a != null ? a.toSpring() : null; + } + + @Override + public void loginFail(HttpServletRequest request, HttpServletResponse response) { + delegate.loginFail(request, response); + } + + @Override + public void loginSuccess(HttpServletRequest request, HttpServletResponse response, org.springframework.security.core.Authentication successfulAuthentication) { + delegate.loginSuccess(request, response, Authentication.fromSpring(successfulAuthentication)); + } + +} diff --git a/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java b/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java new file mode 100644 index 000000000000..b965585808c7 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/rememberme/TokenBasedRememberMeServices.java @@ -0,0 +1,39 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.ui.rememberme; + +import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; + +/** + * @deprecated use {@link org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices} + */ +@Deprecated +public class TokenBasedRememberMeServices { + + public static final String ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY = AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY; + + private TokenBasedRememberMeServices() {} + +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/User.java b/core/src/main/java/org/acegisecurity/userdetails/User.java new file mode 100644 index 000000000000..3c05da1c7113 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/User.java @@ -0,0 +1,117 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.userdetails; + +import org.acegisecurity.GrantedAuthority; + +/** + * @deprecated use {@link org.springframework.security.core.userdetails.User} + */ +@Deprecated +public class User implements UserDetails { + + private static final long serialVersionUID = 1; + + private final String username; + private final String password; + private final boolean enabled; + private final boolean accountNonExpired; + private final boolean credentialsNonExpired; + private final boolean accountNonLocked; + private GrantedAuthority[] authorities; + + public User(String username, String password, boolean enabled, GrantedAuthority[] authorities) { + this(username, password, true, true, true, true, authorities); + } + + public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, GrantedAuthority[] authorities) { + this(username, password, enabled, accountNonExpired, credentialsNonExpired, true, authorities); + } + + public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, GrantedAuthority[] authorities) { + this.username = username; + this.password = password; + this.enabled = enabled; + this.accountNonExpired = accountNonExpired; + this.credentialsNonExpired = credentialsNonExpired; + this.accountNonLocked = accountNonLocked; + setAuthorities(authorities); + } + + @Override + public GrantedAuthority[] getAuthorities() { + return authorities; + } + + protected void setAuthorities(GrantedAuthority[] authorities) { + this.authorities = authorities; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public boolean isAccountNonExpired() { + return accountNonExpired; + } + + @Override + public boolean isAccountNonLocked() { + return accountNonLocked; + } + + @Override + public boolean isCredentialsNonExpired() { + return credentialsNonExpired; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public boolean equals(Object o) { + return o instanceof UserDetails && ((UserDetails) o).getUsername().equals(getUsername()); + } + + @Override + public int hashCode() { + return getUsername().hashCode(); + } + + @Override + public String toString() { + return getUsername(); + } + +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/UserDetails.java b/core/src/main/java/org/acegisecurity/userdetails/UserDetails.java new file mode 100644 index 000000000000..d03bc6109342 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/UserDetails.java @@ -0,0 +1,109 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.userdetails; + +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.Serializable; +import org.acegisecurity.GrantedAuthority; + +/** + * @deprecated use {@link org.springframework.security.core.userdetails.UserDetails} instead + */ +@Deprecated +public interface UserDetails extends Serializable { + + GrantedAuthority[] getAuthorities(); + + String getPassword(); + + String getUsername(); + + boolean isAccountNonExpired(); + + boolean isAccountNonLocked(); + + boolean isCredentialsNonExpired(); + + boolean isEnabled(); + + default @NonNull org.springframework.security.core.userdetails.UserDetails toSpring() { + return new UserDetailsSpringImpl(this); + } + + static @NonNull UserDetails fromSpring(@NonNull org.springframework.security.core.userdetails.UserDetails ud) { + if (ud instanceof UserDetailsSpringImpl) { + return ((UserDetailsSpringImpl) ud).delegate; + } + return new UserDetails() { + @Override + public GrantedAuthority[] getAuthorities() { + return GrantedAuthority.fromSpring(ud.getAuthorities()); + } + @Override + public String getPassword() { + return ud.getPassword(); + } + @Override + public String getUsername() { + return ud.getUsername(); + } + @Override + public boolean isAccountNonExpired() { + return ud.isAccountNonExpired(); + } + @Override + public boolean isAccountNonLocked() { + return ud.isAccountNonLocked(); + } + @Override + public boolean isCredentialsNonExpired() { + return ud.isCredentialsNonExpired(); + } + @Override + public boolean isEnabled() { + return ud.isEnabled(); + } + }; + } + + static @Nullable Object toSpringPrincipal(@CheckForNull Object acegiPrincipal) { + if (acegiPrincipal instanceof UserDetails) { + return ((UserDetails) acegiPrincipal).toSpring(); + } else { + return acegiPrincipal; + } + } + + static @Nullable Object fromSpringPrincipal(@CheckForNull Object springPrincipal) { + if (springPrincipal instanceof org.springframework.security.core.userdetails.UserDetails) { + return fromSpring((org.springframework.security.core.userdetails.UserDetails) springPrincipal); + } else { + return springPrincipal; + } + } + +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java b/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java new file mode 100644 index 000000000000..72e8248d691e --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.userdetails; + +import org.acegisecurity.AcegiSecurityException; +import org.acegisecurity.AuthenticationException; +import org.springframework.dao.DataAccessException; + +/** + * @deprecated use {@link org.springframework.security.core.userdetails.UserDetailsService} + */ +@Deprecated +public interface UserDetailsService { + + UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException; + + static UserDetailsService fromSpring(org.springframework.security.core.userdetails.UserDetailsService uds) { + return username -> { + try { + return UserDetails.fromSpring(uds.loadUserByUsername(username)); + } catch (org.springframework.security.core.AuthenticationException x) { + throw AuthenticationException.fromSpring(x); + } + }; + } + + default org.springframework.security.core.userdetails.UserDetailsService toSpring() { + return username -> { + try { + return loadUserByUsername(username).toSpring(); + } catch (AcegiSecurityException x) { + throw x.toSpring(); + } catch (DataAccessException x) { + throw x.toSpring(); + } + }; + } + +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/UserDetailsSpringImpl.java b/core/src/main/java/org/acegisecurity/userdetails/UserDetailsSpringImpl.java new file mode 100644 index 000000000000..f90c2571f9ff --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/UserDetailsSpringImpl.java @@ -0,0 +1,74 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.userdetails; + +import java.util.Collection; +import org.acegisecurity.GrantedAuthority; + +@Deprecated +final class UserDetailsSpringImpl implements org.springframework.security.core.userdetails.UserDetails { + + final UserDetails delegate; + + UserDetailsSpringImpl(UserDetails delegate) { + this.delegate = delegate; + } + + @Override + public Collection getAuthorities() { + return GrantedAuthority.toSpring(delegate.getAuthorities()); + } + + @Override + public String getPassword() { + return delegate.getPassword(); + } + + @Override + public String getUsername() { + return delegate.getUsername(); + } + + @Override + public boolean isAccountNonExpired() { + return delegate.isAccountNonExpired(); + } + + @Override + public boolean isAccountNonLocked() { + return delegate.isAccountNonLocked(); + } + + @Override + public boolean isCredentialsNonExpired() { + return delegate.isCredentialsNonExpired(); + } + + @Override + public boolean isEnabled() { + return delegate.isEnabled(); + } + +} diff --git a/core/src/main/java/org/acegisecurity/userdetails/UsernameNotFoundException.java b/core/src/main/java/org/acegisecurity/userdetails/UsernameNotFoundException.java new file mode 100644 index 000000000000..6810a3a6a060 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/userdetails/UsernameNotFoundException.java @@ -0,0 +1,62 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.userdetails; + +import hudson.security.UserMayOrMayNotExistException; +import hudson.security.UserMayOrMayNotExistException2; +import org.acegisecurity.BadCredentialsException; + +/** + * @deprecated use {@link org.springframework.security.core.userdetails.UsernameNotFoundException} + */ +@Deprecated +public class UsernameNotFoundException extends BadCredentialsException { + + public UsernameNotFoundException(String msg) { + super(msg); + } + + public UsernameNotFoundException(String msg, Object extraInformation) { + super(msg, extraInformation); + } + + public UsernameNotFoundException(String msg, Throwable t) { + super(msg, t); + } + + @Override + public org.springframework.security.core.userdetails.UsernameNotFoundException toSpring() { + return new org.springframework.security.core.userdetails.UsernameNotFoundException(toString(), this); + } + + public static UsernameNotFoundException fromSpring(org.springframework.security.core.userdetails.UsernameNotFoundException x) { + if (x instanceof UserMayOrMayNotExistException2) { + return UserMayOrMayNotExistException.fromSpring((UserMayOrMayNotExistException2) x); + } else { + return new UsernameNotFoundException(x.toString(), x); + } + } + +} diff --git a/core/src/main/java/org/acegisecurity/util/FieldUtils.java b/core/src/main/java/org/acegisecurity/util/FieldUtils.java new file mode 100644 index 000000000000..e7fac78c46b9 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/util/FieldUtils.java @@ -0,0 +1,53 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.acegisecurity.util; + +/** + * @deprecated use {@link org.apache.commons.lang.reflect.FieldUtils} + */ +@Deprecated +public final class FieldUtils { + + public static Object getProtectedFieldValue(String protectedField, Object object) { + try { + return org.apache.commons.lang.reflect.FieldUtils.readField(object, protectedField, true); + } catch (IllegalAccessException x) { + throw new RuntimeException(x); + } + } + + public static void setProtectedFieldValue(String protectedField, Object object, Object newValue) { + try { + org.apache.commons.lang.reflect.FieldUtils.writeField(object, protectedField, newValue, true); + } catch (IllegalAccessException x) { + throw new RuntimeException(x); + } + } + + // TODO other methods as needed + + private FieldUtils() {} + +} diff --git a/core/src/main/java/org/springframework/dao/DataAccessException.java b/core/src/main/java/org/springframework/dao/DataAccessException.java new file mode 100644 index 000000000000..d3220ff361c4 --- /dev/null +++ b/core/src/main/java/org/springframework/dao/DataAccessException.java @@ -0,0 +1,49 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.springframework.dao; + +import hudson.security.UserMayOrMayNotExistException2; +import org.springframework.core.NestedRuntimeException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; + +/** + * @deprecated no replacement + */ +@Deprecated +public class DataAccessException extends NestedRuntimeException { + + public DataAccessException(String msg) { + super(msg); + } + + public DataAccessException(String msg, Throwable cause) { + super(msg, cause); + } + + public UserMayOrMayNotExistException2 toSpring() { + return new UserMayOrMayNotExistException2(toString(), this); + } + +} diff --git a/core/src/main/java/org/springframework/dao/DataAccessResourceFailureException.java b/core/src/main/java/org/springframework/dao/DataAccessResourceFailureException.java new file mode 100644 index 000000000000..d978c52669f4 --- /dev/null +++ b/core/src/main/java/org/springframework/dao/DataAccessResourceFailureException.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.springframework.dao; + +/** + * @deprecated no replacement + */ +@Deprecated +public class DataAccessResourceFailureException extends DataAccessException { + + public DataAccessResourceFailureException(String msg) { + super(msg); + } + + public DataAccessResourceFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/core/src/main/java/org/springframework/dao/DataRetrievalFailureException.java b/core/src/main/java/org/springframework/dao/DataRetrievalFailureException.java new file mode 100644 index 000000000000..dbbef60fe7b5 --- /dev/null +++ b/core/src/main/java/org/springframework/dao/DataRetrievalFailureException.java @@ -0,0 +1,41 @@ +/* + * The MIT License + * + * Copyright 2020 CloudBees, Inc. + * + * 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 org.springframework.dao; + +/** + * @deprecated no replacement + */ +@Deprecated +public class DataRetrievalFailureException extends DataAccessException { + + public DataRetrievalFailureException(String msg) { + super(msg); + } + + public DataRetrievalFailureException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/core/src/main/resources/META-INF/upgrade/AccessControlled.hint b/core/src/main/resources/META-INF/upgrade/AccessControlled.hint index e3bafc391dfa..abc521b5f0a7 100644 --- a/core/src/main/resources/META-INF/upgrade/AccessControlled.hint +++ b/core/src/main/resources/META-INF/upgrade/AccessControlled.hint @@ -1 +1 @@ -$ac.getACL().hasPermission($a, $p) :: $ac instanceof hudson.security.AccessControlled && $a instanceof org.acegisecurity.Authentication && $p instanceof hudson.security.Permission => $ac.hasPermission($a, $p);; +$ac.getACL().hasPermission($a, $p) :: $ac instanceof hudson.security.AccessControlled && $p instanceof hudson.security.Permission => $ac.hasPermission($a, $p);; diff --git a/core/src/main/resources/META-INF/upgrade/Authentication.hint b/core/src/main/resources/META-INF/upgrade/Authentication.hint new file mode 100644 index 000000000000..d709884cee99 --- /dev/null +++ b/core/src/main/resources/META-INF/upgrade/Authentication.hint @@ -0,0 +1,4 @@ +$a == hudson.security.ACL.SYSTEM :: $a instanceof org.acegisecurity.Authentication => hudson.security.ACL.SYSTEM.equals($a);; +$a == jenkins.model.Jenkins.ANONYMOUS :: $a instanceof org.acegisecurity.Authentication => jenkins.model.Jenkins.ANONYMOUS.equals($a);; +$a == hudson.security.ACL.SYSTEM2 :: $a instanceof org.springframework.security.core.Authentication => hudson.security.ACL.SYSTEM2.equals($a);; +$a == jenkins.model.Jenkins.ANONYMOUS2 :: $a instanceof org.springframework.security.core.Authentication => jenkins.model.Jenkins.ANONYMOUS2.equals($a);; diff --git a/core/src/main/resources/jenkins/model/Jenkins/login.jelly b/core/src/main/resources/jenkins/model/Jenkins/login.jelly index 0580ed28e796..f5d8557b54ff 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/login.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/login.jelly @@ -114,7 +114,7 @@ THE SOFTWARE. - +