From 0a7b656af6ac5c70a408abb082f95ecd51ee8bd0 Mon Sep 17 00:00:00 2001 From: christ66 Date: Fri, 16 Sep 2016 21:41:12 -0700 Subject: [PATCH 0001/1380] [JENKINS-34855] Make atomic file write more atomic by using JDK 7 apis. --- .../java/hudson/util/AtomicFileWriter.java | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index f49d4b9ff5..e789fb9faa 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -32,6 +32,12 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; /** * Buffered {@link FileWriter} that supports atomic operations. @@ -45,8 +51,8 @@ import java.nio.charset.Charset; public class AtomicFileWriter extends Writer { private final Writer core; - private final File tmpFile; - private final File destFile; + private final Path tmpFile; + private final Path destFile; /** * Writes with UTF-8 encoding. @@ -60,17 +66,17 @@ public class AtomicFileWriter extends Writer { * File encoding to write. If null, platform default encoding is chosen. */ public AtomicFileWriter(File f, String encoding) throws IOException { - File dir = f.getParentFile(); + Path dir = f.toPath().getParent(); try { - dir.mkdirs(); - tmpFile = File.createTempFile("atomic",null, dir); + Files.createDirectories(dir); + tmpFile = Files.createTempFile(dir, "atomic", null, (FileAttribute) null); } catch (IOException e) { throw new IOException("Failed to create a temporary file in "+ dir,e); } - destFile = f; + destFile = f.toPath(); if (encoding==null) encoding = Charset.defaultCharset().name(); - core = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(tmpFile),encoding)); + core = Files.newBufferedWriter(tmpFile, Charset.forName(encoding), StandardOpenOption.SYNC); } @Override @@ -103,34 +109,47 @@ public class AtomicFileWriter extends Writer { */ public void abort() throws IOException { close(); - tmpFile.delete(); + Files.deleteIfExists(tmpFile); } public void commit() throws IOException { close(); - if (destFile.exists()) { + if (Files.exists(destFile)) { try { - Util.deleteFile(destFile); + Files.delete(tmpFile); // First try with NIO. + Util.deleteFile(destFile.toFile()); // Then try with the util method. } catch (IOException x) { - tmpFile.delete(); + Files.delete(tmpFile); throw x; } } - tmpFile.renameTo(destFile); + Files.move(tmpFile, destFile, null); } @Override protected void finalize() throws Throwable { // one way or the other, temporary file should be deleted. close(); - tmpFile.delete(); + Files.deleteIfExists(tmpFile); } /** * Until the data is committed, this file captures * the written content. + * + * @deprecated Use getTemporaryPath() for JDK 7+ */ + @Deprecated public File getTemporaryFile() { + return tmpFile.toFile(); + } + + /** + * Until the data is committed, this file captures + * the written content. + * + */ + public Path getTemporaryPath() { return tmpFile; } } -- GitLab From f9f10adf259851e1d90e5fe4833b871bdefa840f Mon Sep 17 00:00:00 2001 From: christ66 Date: Sat, 17 Sep 2016 00:11:30 -0700 Subject: [PATCH 0002/1380] Perform ATOMIC_MOVE when copying the file. If we are unable to perform the ATOMIC_MOVE operation then we fallback to an operation which is supported by all OSes. Create constructor for charsets. Delete tempdir instead of destdir. --- .../java/hudson/util/AtomicFileWriter.java | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index e789fb9faa..31cb18901c 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -32,9 +32,12 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileAttribute; @@ -64,8 +67,18 @@ public class AtomicFileWriter extends Writer { /** * @param encoding * File encoding to write. If null, platform default encoding is chosen. + * + * @deprecated Use AtomicFileWriter */ public AtomicFileWriter(File f, String encoding) throws IOException { + this(f, Charset.forName(encoding)); + } + + /** + * @param charset + * File charset to write. If null, platform default encoding is chosen. + */ + public AtomicFileWriter(File f, Charset charset) throws IOException { Path dir = f.toPath().getParent(); try { Files.createDirectories(dir); @@ -74,9 +87,9 @@ public class AtomicFileWriter extends Writer { throw new IOException("Failed to create a temporary file in "+ dir,e); } destFile = f.toPath(); - if (encoding==null) - encoding = Charset.defaultCharset().name(); - core = Files.newBufferedWriter(tmpFile, Charset.forName(encoding), StandardOpenOption.SYNC); + if (charset==null) + charset = Charset.defaultCharset(); + core = Files.newBufferedWriter(tmpFile, charset, StandardOpenOption.SYNC); } @Override @@ -116,14 +129,20 @@ public class AtomicFileWriter extends Writer { close(); if (Files.exists(destFile)) { try { - Files.delete(tmpFile); // First try with NIO. - Util.deleteFile(destFile.toFile()); // Then try with the util method. + Util.deleteFile(tmpFile.toFile()); } catch (IOException x) { Files.delete(tmpFile); throw x; } } - Files.move(tmpFile, destFile, null); + try { + // Try to make an atomic move. + Files.move(tmpFile, destFile, StandardCopyOption.ATOMIC_MOVE); + } catch (IOException e) { + // If it falls here that means that Atomic move is not supported by the OS. + // In this case we need to fall-back to a copy option which is supported by all OSes. + Files.move(tmpFile, destFile, StandardCopyOption.REPLACE_EXISTING); + } } @Override -- GitLab From c960c75c86025b778a8a17397d6770f92a6fdf28 Mon Sep 17 00:00:00 2001 From: christ66 Date: Sat, 17 Sep 2016 00:27:29 -0700 Subject: [PATCH 0003/1380] Finish javadoc deprecated statement. --- core/src/main/java/hudson/util/AtomicFileWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 31cb18901c..0e0c6f4cd2 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -68,7 +68,7 @@ public class AtomicFileWriter extends Writer { * @param encoding * File encoding to write. If null, platform default encoding is chosen. * - * @deprecated Use AtomicFileWriter + * @deprecated Use {@link #AtomicFileWriter(File, Charset)} */ public AtomicFileWriter(File f, String encoding) throws IOException { this(f, Charset.forName(encoding)); -- GitLab From 88a6f76c1ba6272ccb169703643b42cebeabe193 Mon Sep 17 00:00:00 2001 From: christ66 Date: Sat, 17 Sep 2016 10:09:22 -0700 Subject: [PATCH 0004/1380] Commit should not perform a delete as it is already performing a move. Add suffix to create temp file. --- core/src/main/java/hudson/util/AtomicFileWriter.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 0e0c6f4cd2..f6d58c5295 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -82,7 +82,7 @@ public class AtomicFileWriter extends Writer { Path dir = f.toPath().getParent(); try { Files.createDirectories(dir); - tmpFile = Files.createTempFile(dir, "atomic", null, (FileAttribute) null); + tmpFile = Files.createTempFile(dir, "atomic", "tmp"); } catch (IOException e) { throw new IOException("Failed to create a temporary file in "+ dir,e); } @@ -127,14 +127,6 @@ public class AtomicFileWriter extends Writer { public void commit() throws IOException { close(); - if (Files.exists(destFile)) { - try { - Util.deleteFile(tmpFile.toFile()); - } catch (IOException x) { - Files.delete(tmpFile); - throw x; - } - } try { // Try to make an atomic move. Files.move(tmpFile, destFile, StandardCopyOption.ATOMIC_MOVE); -- GitLab From 8434afc25036dc9105ac67dcb486181716fca7a8 Mon Sep 17 00:00:00 2001 From: christ66 Date: Sat, 17 Sep 2016 11:56:22 -0700 Subject: [PATCH 0005/1380] Add unit test for atomic file writer. --- .../hudson/util/AtomicFileWriterTest.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 core/src/test/java/hudson/util/AtomicFileWriterTest.java diff --git a/core/src/test/java/hudson/util/AtomicFileWriterTest.java b/core/src/test/java/hudson/util/AtomicFileWriterTest.java new file mode 100644 index 0000000000..ba8e22ae2a --- /dev/null +++ b/core/src/test/java/hudson/util/AtomicFileWriterTest.java @@ -0,0 +1,75 @@ +package hudson.util; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; + +import static org.junit.Assert.*; + +public class AtomicFileWriterTest { + File af; + AtomicFileWriter afw; + String expectedContent = "hello world"; + + + @Before + public void setUp() throws IOException { + af = File.createTempFile("AtomicFileWriter", ".tmp"); + afw = new AtomicFileWriter(af, Charset.defaultCharset()); + } + + @After + public void tearDown() throws IOException { + Files.deleteIfExists(af.toPath()); + Files.deleteIfExists(afw.getTemporaryPath()); + } + + @Test + public void createFile() throws Exception { + // Verify the file we created exists + assertTrue(Files.exists(afw.getTemporaryPath())); + } + + @Test + public void writeToAtomicFile() throws Exception { + // Given + afw.write(expectedContent, 0, expectedContent.length()); + + // When + afw.flush(); + + // Then + assertTrue("File writer did not properly flush to temporary file", + Files.size(afw.getTemporaryPath()) == expectedContent.length()); + } + + @Test + public void commitToFile() throws Exception { + // Given + afw.write(expectedContent, 0, expectedContent.length()); + + // When + afw.commit(); + + // Then + System.err.println(Files.size(af.toPath())); + assertTrue(Files.size(af.toPath()) == expectedContent.length()); + } + + @Test + public void abortDeletesTmpFile() throws Exception { + // Given + afw.write(expectedContent, 0, expectedContent.length()); + + // When + afw.abort(); + + // Then + assertTrue(Files.notExists(afw.getTemporaryPath())); + } +} \ No newline at end of file -- GitLab From f8c9c04ce44b1aff89bd2af09c7a0b3ec1b18ed5 Mon Sep 17 00:00:00 2001 From: christ66 Date: Sun, 18 Sep 2016 09:35:50 -0700 Subject: [PATCH 0006/1380] Fix javadocs. --- core/src/main/java/hudson/util/AtomicFileWriter.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index f6d58c5295..89d5a0b14c 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -65,8 +65,7 @@ public class AtomicFileWriter extends Writer { } /** - * @param encoding - * File encoding to write. If null, platform default encoding is chosen. + * @param encoding File encoding to write. If null, platform default encoding is chosen. * * @deprecated Use {@link #AtomicFileWriter(File, Charset)} */ @@ -75,8 +74,7 @@ public class AtomicFileWriter extends Writer { } /** - * @param charset - * File charset to write. If null, platform default encoding is chosen. + * @param charset File charset to write. If null, platform default encoding is chosen. */ public AtomicFileWriter(File f, Charset charset) throws IOException { Path dir = f.toPath().getParent(); -- GitLab From 8d64ff51a95b53f1a56922906e13aef8f279d512 Mon Sep 17 00:00:00 2001 From: christ66 Date: Sun, 18 Sep 2016 09:42:37 -0700 Subject: [PATCH 0007/1380] Do not create the directory if it already exists. --- core/src/main/java/hudson/util/AtomicFileWriter.java | 4 +++- core/src/test/java/hudson/util/AtomicFileWriterTest.java | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 89d5a0b14c..eeaac35e09 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -79,7 +79,9 @@ public class AtomicFileWriter extends Writer { public AtomicFileWriter(File f, Charset charset) throws IOException { Path dir = f.toPath().getParent(); try { - Files.createDirectories(dir); + if (Files.notExists(dir)) { + Files.createDirectories(dir); + } tmpFile = Files.createTempFile(dir, "atomic", "tmp"); } catch (IOException e) { throw new IOException("Failed to create a temporary file in "+ dir,e); diff --git a/core/src/test/java/hudson/util/AtomicFileWriterTest.java b/core/src/test/java/hudson/util/AtomicFileWriterTest.java index ba8e22ae2a..6eb881d5ab 100644 --- a/core/src/test/java/hudson/util/AtomicFileWriterTest.java +++ b/core/src/test/java/hudson/util/AtomicFileWriterTest.java @@ -57,7 +57,6 @@ public class AtomicFileWriterTest { afw.commit(); // Then - System.err.println(Files.size(af.toPath())); assertTrue(Files.size(af.toPath()) == expectedContent.length()); } -- GitLab From 5d40c78e5f811e35f5aa18c4d611579335955f14 Mon Sep 17 00:00:00 2001 From: christ66 Date: Thu, 22 Sep 2016 14:17:16 -0400 Subject: [PATCH 0008/1380] Move destFile initiation to top. --- core/src/main/java/hudson/util/AtomicFileWriter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index eeaac35e09..6076d43904 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -77,7 +77,8 @@ public class AtomicFileWriter extends Writer { * @param charset File charset to write. If null, platform default encoding is chosen. */ public AtomicFileWriter(File f, Charset charset) throws IOException { - Path dir = f.toPath().getParent(); + destFile = f.toPath(); + Path dir = destFile.getParent(); try { if (Files.notExists(dir)) { Files.createDirectories(dir); @@ -86,7 +87,6 @@ public class AtomicFileWriter extends Writer { } catch (IOException e) { throw new IOException("Failed to create a temporary file in "+ dir,e); } - destFile = f.toPath(); if (charset==null) charset = Charset.defaultCharset(); core = Files.newBufferedWriter(tmpFile, charset, StandardOpenOption.SYNC); -- GitLab From e98d8002d8ce1646d00be54d82d2489cb549d354 Mon Sep 17 00:00:00 2001 From: christ66 Date: Thu, 22 Sep 2016 14:19:43 -0400 Subject: [PATCH 0009/1380] Only catch AtomicMoveNotSupportedException. --- core/src/main/java/hudson/util/AtomicFileWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 6076d43904..5ce2cff032 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -130,7 +130,7 @@ public class AtomicFileWriter extends Writer { try { // Try to make an atomic move. Files.move(tmpFile, destFile, StandardCopyOption.ATOMIC_MOVE); - } catch (IOException e) { + } catch (AtomicMoveNotSupportedException e) { // If it falls here that means that Atomic move is not supported by the OS. // In this case we need to fall-back to a copy option which is supported by all OSes. Files.move(tmpFile, destFile, StandardCopyOption.REPLACE_EXISTING); -- GitLab From 2df07337ed4ca894c85f2bd90da9f1e1290f8439 Mon Sep 17 00:00:00 2001 From: christ66 Date: Thu, 22 Sep 2016 14:30:39 -0400 Subject: [PATCH 0010/1380] Use temporary file, and assertEquals in tests. --- .../java/hudson/util/AtomicFileWriterTest.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/core/src/test/java/hudson/util/AtomicFileWriterTest.java b/core/src/test/java/hudson/util/AtomicFileWriterTest.java index 6eb881d5ab..49fd588a53 100644 --- a/core/src/test/java/hudson/util/AtomicFileWriterTest.java +++ b/core/src/test/java/hudson/util/AtomicFileWriterTest.java @@ -2,7 +2,9 @@ package hudson.util; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; @@ -15,20 +17,15 @@ public class AtomicFileWriterTest { File af; AtomicFileWriter afw; String expectedContent = "hello world"; + @Rule public TemporaryFolder tmp = new TemporaryFolder(); @Before public void setUp() throws IOException { - af = File.createTempFile("AtomicFileWriter", ".tmp"); + af = tmp.newFile(); afw = new AtomicFileWriter(af, Charset.defaultCharset()); } - @After - public void tearDown() throws IOException { - Files.deleteIfExists(af.toPath()); - Files.deleteIfExists(afw.getTemporaryPath()); - } - @Test public void createFile() throws Exception { // Verify the file we created exists @@ -44,8 +41,8 @@ public class AtomicFileWriterTest { afw.flush(); // Then - assertTrue("File writer did not properly flush to temporary file", - Files.size(afw.getTemporaryPath()) == expectedContent.length()); + assertEquals("File writer did not properly flush to temporary file", + expectedContent.length(), Files.size(afw.getTemporaryPath())); } @Test @@ -57,7 +54,7 @@ public class AtomicFileWriterTest { afw.commit(); // Then - assertTrue(Files.size(af.toPath()) == expectedContent.length()); + assertEquals(expectedContent.length(), Files.size(af.toPath())); } @Test -- GitLab From a1fae1cc1fd2670269e8ee94e89e3c6c0e05b580 Mon Sep 17 00:00:00 2001 From: David Rutqvist Date: Sun, 9 Oct 2016 15:29:03 +0200 Subject: [PATCH 0011/1380] Made autocomplete for new job case-insensitive --- .../main/java/hudson/model/AutoCompletionCandidates.java | 8 +++++--- core/src/main/java/hudson/model/ComputerSet.java | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/model/AutoCompletionCandidates.java b/core/src/main/java/hudson/model/AutoCompletionCandidates.java index 0373b552bb..719b050a76 100644 --- a/core/src/main/java/hudson/model/AutoCompletionCandidates.java +++ b/core/src/main/java/hudson/model/AutoCompletionCandidates.java @@ -118,16 +118,18 @@ public class AutoCompletionCandidates implements HttpResponse { @Override public void onItem(Item i) { String n = contextualNameOf(i); - if ((n.startsWith(value) || value.startsWith(n)) + String lowerCaseN = n.toLowerCase(); + String lowerCaseValue = value.toLowerCase(); + if ((lowerCaseN.startsWith(lowerCaseValue) || lowerCaseValue.startsWith(lowerCaseN)) // 'foobar' is a valid candidate if the current value is 'foo'. // Also, we need to visit 'foo' if the current value is 'foo/bar' - && (value.length()>n.length() || !n.substring(value.length()).contains("/")) + && (lowerCaseValue.length()>lowerCaseN.length() || !lowerCaseN.substring(lowerCaseValue.length()).contains("/")) // but 'foobar/zot' isn't if the current value is 'foo' // we'll first show 'foobar' and then wait for the user to type '/' to show the rest && i.hasPermission(Item.READ) // and read permission required ) { - if (type.isInstance(i) && n.startsWith(value)) + if (type.isInstance(i) && lowerCaseN.startsWith(lowerCaseValue)) candidates.add(n); // recurse diff --git a/core/src/main/java/hudson/model/ComputerSet.java b/core/src/main/java/hudson/model/ComputerSet.java index 89ed31f289..6e7e0af9df 100644 --- a/core/src/main/java/hudson/model/ComputerSet.java +++ b/core/src/main/java/hudson/model/ComputerSet.java @@ -388,7 +388,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl final AutoCompletionCandidates r = new AutoCompletionCandidates(); for (Node n : Jenkins.getInstance().getNodes()) { - if (n.getNodeName().startsWith(value)) + if (n.getNodeName().toLowerCase().contains(value.toLowerCase())) r.add(n.getNodeName()); } -- GitLab From c8f5dcf21feae1bc6b1b9f6364480c90bf73a08e Mon Sep 17 00:00:00 2001 From: David Rutqvist Date: Tue, 11 Oct 2016 16:03:10 +0200 Subject: [PATCH 0012/1380] Revert "Made autocomplete for new job case-insensitive" This reverts commit a1fae1cc1fd2670269e8ee94e89e3c6c0e05b580. --- .../main/java/hudson/model/AutoCompletionCandidates.java | 8 +++----- core/src/main/java/hudson/model/ComputerSet.java | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/model/AutoCompletionCandidates.java b/core/src/main/java/hudson/model/AutoCompletionCandidates.java index 719b050a76..0373b552bb 100644 --- a/core/src/main/java/hudson/model/AutoCompletionCandidates.java +++ b/core/src/main/java/hudson/model/AutoCompletionCandidates.java @@ -118,18 +118,16 @@ public class AutoCompletionCandidates implements HttpResponse { @Override public void onItem(Item i) { String n = contextualNameOf(i); - String lowerCaseN = n.toLowerCase(); - String lowerCaseValue = value.toLowerCase(); - if ((lowerCaseN.startsWith(lowerCaseValue) || lowerCaseValue.startsWith(lowerCaseN)) + if ((n.startsWith(value) || value.startsWith(n)) // 'foobar' is a valid candidate if the current value is 'foo'. // Also, we need to visit 'foo' if the current value is 'foo/bar' - && (lowerCaseValue.length()>lowerCaseN.length() || !lowerCaseN.substring(lowerCaseValue.length()).contains("/")) + && (value.length()>n.length() || !n.substring(value.length()).contains("/")) // but 'foobar/zot' isn't if the current value is 'foo' // we'll first show 'foobar' and then wait for the user to type '/' to show the rest && i.hasPermission(Item.READ) // and read permission required ) { - if (type.isInstance(i) && lowerCaseN.startsWith(lowerCaseValue)) + if (type.isInstance(i) && n.startsWith(value)) candidates.add(n); // recurse diff --git a/core/src/main/java/hudson/model/ComputerSet.java b/core/src/main/java/hudson/model/ComputerSet.java index 6e7e0af9df..89ed31f289 100644 --- a/core/src/main/java/hudson/model/ComputerSet.java +++ b/core/src/main/java/hudson/model/ComputerSet.java @@ -388,7 +388,7 @@ public final class ComputerSet extends AbstractModelObject implements Describabl final AutoCompletionCandidates r = new AutoCompletionCandidates(); for (Node n : Jenkins.getInstance().getNodes()) { - if (n.getNodeName().toLowerCase().contains(value.toLowerCase())) + if (n.getNodeName().startsWith(value)) r.add(n.getNodeName()); } -- GitLab From 6b933bd383824e94f672b8e3bd66ed36f111a7a0 Mon Sep 17 00:00:00 2001 From: David Rutqvist Date: Tue, 11 Oct 2016 16:38:23 +0200 Subject: [PATCH 0013/1380] Redid fix so it uses the user setting used by global search instead of defaulting to case-insensitive --- .../hudson/model/AutoCompletionCandidates.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/hudson/model/AutoCompletionCandidates.java b/core/src/main/java/hudson/model/AutoCompletionCandidates.java index 0373b552bb..57f79eae99 100644 --- a/core/src/main/java/hudson/model/AutoCompletionCandidates.java +++ b/core/src/main/java/hudson/model/AutoCompletionCandidates.java @@ -25,6 +25,7 @@ package hudson.model; import hudson.search.Search; +import hudson.search.UserSearchProperty; import jenkins.model.Jenkins; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.StaplerRequest; @@ -118,16 +119,26 @@ public class AutoCompletionCandidates implements HttpResponse { @Override public void onItem(Item i) { String n = contextualNameOf(i); - if ((n.startsWith(value) || value.startsWith(n)) + boolean caseInsensitive = UserSearchProperty.isCaseInsensitive(); + + String hay = n; + String needle = value; + + if(caseInsensitive) { + hay = hay.toLowerCase(); + needle = needle.toLowerCase(); + } + + if ((hay.startsWith(needle) || needle.startsWith(hay)) // 'foobar' is a valid candidate if the current value is 'foo'. // Also, we need to visit 'foo' if the current value is 'foo/bar' - && (value.length()>n.length() || !n.substring(value.length()).contains("/")) + && (needle.length()>hay.length() || !hay.substring(needle.length()).contains("/")) // but 'foobar/zot' isn't if the current value is 'foo' // we'll first show 'foobar' and then wait for the user to type '/' to show the rest && i.hasPermission(Item.READ) // and read permission required ) { - if (type.isInstance(i) && n.startsWith(value)) + if (type.isInstance(i) && hay.startsWith(needle)) candidates.add(n); // recurse -- GitLab From 9fee6cc3f0d2240b8e23b447318fde36b5e0eb8e Mon Sep 17 00:00:00 2001 From: David Rutqvist Date: Tue, 11 Oct 2016 16:54:29 +0200 Subject: [PATCH 0014/1380] [FIXED JENKINS-38812] Commented on the implementation. Uses the same setting as used globally --- .../main/java/hudson/model/AutoCompletionCandidates.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/model/AutoCompletionCandidates.java b/core/src/main/java/hudson/model/AutoCompletionCandidates.java index 57f79eae99..f410633441 100644 --- a/core/src/main/java/hudson/model/AutoCompletionCandidates.java +++ b/core/src/main/java/hudson/model/AutoCompletionCandidates.java @@ -119,7 +119,11 @@ public class AutoCompletionCandidates implements HttpResponse { @Override public void onItem(Item i) { String n = contextualNameOf(i); - boolean caseInsensitive = UserSearchProperty.isCaseInsensitive(); + + //Check user's setting on whether to do case sensitive comparison, configured in user -> configure + //This is the same setting that is used by the global search field, should be consistent throughout + //the whole application. + boolean caseInsensitive = UserSearchProperty.isCaseInsensitive(); String hay = n; String needle = value; -- GitLab From c62f4f753d16210b67ec381b9e5d1d60145594d8 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Mon, 6 Feb 2017 14:25:30 +0000 Subject: [PATCH 0015/1380] [JENKINS-21017] When unmarshalling into an existing object, reset missing fields --- .../util/RobustReflectionConverter.java | 77 +++++++ .../test/java/hudson/util/XStream2Test.java | 218 ++++++++++++++++++ 2 files changed, 295 insertions(+) diff --git a/core/src/main/java/hudson/util/RobustReflectionConverter.java b/core/src/main/java/hudson/util/RobustReflectionConverter.java index 3ec65ec994..a65c7f4e17 100644 --- a/core/src/main/java/hudson/util/RobustReflectionConverter.java +++ b/core/src/main/java/hudson/util/RobustReflectionConverter.java @@ -271,8 +271,71 @@ public class RobustReflectionConverter implements Converter { return serializationMethodInvoker.callReadResolve(result); } + private static final class FieldExpectation { + private final Class definingClass; + private final String name; + + public FieldExpectation(Class definingClass, String name) { + this.definingClass = definingClass; + this.name = name; + } + + public Class getDefiningClass() { + return definingClass; + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FieldExpectation that = (FieldExpectation) o; + + if (definingClass != null ? !definingClass.equals(that.definingClass) : that.definingClass != null) { + return false; + } + return name.equals(that.name); + } + + @Override + public int hashCode() { + int result = definingClass != null ? definingClass.hashCode() : 0; + result = 31 * result + name.hashCode(); + return result; + } + + @Override + public String toString() { + return "FieldExpectation{" + + "definingClass=" + definingClass + + ", name='" + name + '\'' + + '}'; + } + + + } + public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) { final SeenFields seenFields = new SeenFields(); + final boolean existingObject = context.currentObject() != null; + final Map expectedFields = existingObject ? new HashMap() : null; + final Object cleanInstance = existingObject ? reflectionProvider.newInstance(result.getClass()) : null; + if (existingObject) { + reflectionProvider.visitSerializableFields(cleanInstance, new ReflectionProvider.Visitor() { + @Override + public void visit(String name, Class type, Class definedIn, Object value) { + expectedFields.put(new FieldExpectation(definedIn, name), value); + } + }); + } Iterator it = reader.getAttributeNames(); // Remember outermost Saveable encountered, for reporting below if (result instanceof Saveable && context.get("Saveable") == null) @@ -301,6 +364,10 @@ public class RobustReflectionConverter implements Converter { } reflectionProvider.writeField(result, attrName, value, classDefiningField); seenFields.add(classDefiningField, attrName); + if (existingObject) { + expectedFields.remove(new FieldExpectation( + classDefiningField == null ? result.getClass() : classDefiningField, attrName)); + } } } } @@ -342,6 +409,10 @@ public class RobustReflectionConverter implements Converter { LOGGER.warning("Cannot convert type " + value.getClass().getName() + " to type " + type.getName()); // behave as if we didn't see this element } else { + if (existingObject) { + expectedFields.remove(new FieldExpectation( + classDefiningField == null ? result.getClass() : classDefiningField, fieldName)); + } if (fieldExistsInClass) { reflectionProvider.writeField(result, fieldName, value, classDefiningField); seenFields.add(classDefiningField, fieldName); @@ -371,6 +442,12 @@ public class RobustReflectionConverter implements Converter { OldDataMonitor.report((Saveable)result, (ArrayList)context.get("ReadError")); context.put("ReadError", null); } + if (existingObject) { + for (Map.Entry entry : expectedFields.entrySet()) { + reflectionProvider.writeField(result, entry.getKey().getName(), entry.getValue(), + entry.getKey().getDefiningClass()); + } + } return result; } diff --git a/core/src/test/java/hudson/util/XStream2Test.java b/core/src/test/java/hudson/util/XStream2Test.java index aeb781f672..5f65fac491 100644 --- a/core/src/test/java/hudson/util/XStream2Test.java +++ b/core/src/test/java/hudson/util/XStream2Test.java @@ -23,6 +23,7 @@ */ package hudson.util; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; import com.google.common.collect.ImmutableList; @@ -32,11 +33,14 @@ import hudson.XmlFile; import hudson.model.Result; import hudson.model.Run; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; +import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -296,4 +300,218 @@ public class XStream2Test { assertEquals("3.2.1", XStream2.trimVersion("3.2.1")); assertEquals("3.2-SNAPSHOT", XStream2.trimVersion("3.2-SNAPSHOT (private-09/23/2012 12:26-jhacker)")); } + + @Issue("JENKINS-21017") + @Test + public void unmarshalToDefault_populated() { + String populatedXml = "\n" + + " my string\n" + + " not null\n" + + " \n" + + " 1\n" + + " 2\n" + + " 3\n" + + " \n" + + " \n" + + " 1\n" + + " 2\n" + + " 3\n" + + " \n" + + " \n" + + " 1\n" + + " 2\n" + + " 3\n" + + " \n" + + " \n" + + " 1\n" + + " 2\n" + + " 3\n" + + " \n" + + " \n" + + " 1\n" + + " 2\n" + + " 3\n" + + " \n" + + " \n" + + " 1\n" + + " 2\n" + + " 3\n" + + " \n" + + ""; + + WithDefaults existingInstance = new WithDefaults("foobar", + "foobar", + new String[]{"foobar", "barfoo", "fumanchu"}, + new String[]{"foobar", "barfoo", "fumanchu"}, + new String[]{"foobar", "barfoo", "fumanchu"}, + Arrays.asList("foobar", "barfoo", "fumanchu"), + Arrays.asList("foobar", "barfoo", "fumanchu"), + Arrays.asList("foobar", "barfoo", "fumanchu") + ); + + WithDefaults newInstance = new WithDefaults(); + + String xmlA = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(populatedXml, existingInstance)); + String xmlB = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(populatedXml, newInstance)); + String xmlC = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(populatedXml, null)); + + assertThat("Deserializing over an existing instance is the same as with no root", xmlA, is(xmlC)); + assertThat("Deserializing over an new instance is the same as with no root", xmlB, is(xmlC)); + } + + + @Issue("JENKINS-21017") + @Test + public void unmarshalToDefault_default() { + String defaultXml = "\n" + + " defaultValue\n" + + " \n" + + " first\n" + + " second\n" + + " \n" + + " \n" + + " \n" + + " first\n" + + " second\n" + + " \n" + + " \n" + + ""; + + WithDefaults existingInstance = new WithDefaults("foobar", + "foobar", + new String[]{"foobar", "barfoo", "fumanchu"}, + new String[]{"foobar", "barfoo", "fumanchu"}, + new String[]{"foobar", "barfoo", "fumanchu"}, + Arrays.asList("foobar", "barfoo", "fumanchu"), + Arrays.asList("foobar", "barfoo", "fumanchu"), + Arrays.asList("foobar", "barfoo", "fumanchu") + ); + + WithDefaults newInstance = new WithDefaults(); + + String xmlA = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(defaultXml, existingInstance)); + String xmlB = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(defaultXml, newInstance)); + String xmlC = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(defaultXml, null)); + + assertThat("Deserializing over an existing instance is the same as with no root", xmlA, is(xmlC)); + assertThat("Deserializing over an new instance is the same as with no root", xmlB, is(xmlC)); + } + + + @Issue("JENKINS-21017") + @Test + public void unmarshalToDefault_empty() { + String emptyXml = ""; + + WithDefaults existingInstance = new WithDefaults("foobar", + "foobar", + new String[]{"foobar", "barfoo", "fumanchu"}, + new String[]{"foobar", "barfoo", "fumanchu"}, + new String[]{"foobar", "barfoo", "fumanchu"}, + Arrays.asList("foobar", "barfoo", "fumanchu"), + Arrays.asList("foobar", "barfoo", "fumanchu"), + Arrays.asList("foobar", "barfoo", "fumanchu") + ); + + WithDefaults newInstance = new WithDefaults(); + + String xmlA = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(emptyXml, existingInstance)); + String xmlB = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(emptyXml, newInstance)); + String xmlC = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(emptyXml, null)); + + assertThat("Deserializing over an existing instance is the same as with no root", xmlA, is(xmlC)); + assertThat("Deserializing over an new instance is the same as with no root", xmlB, is(xmlC)); + } + + public static class WithDefaults { + private String stringDefaultValue = "defaultValue"; + private String stringDefaultNull; + private String[] arrayDefaultValue = { "first", "second" }; + private String[] arrayDefaultEmpty = new String[0]; + private String[] arrayDefaultNull; + private List listDefaultValue = new ArrayList<>(Arrays.asList("first", "second")); + private List listDefaultEmpty = new ArrayList<>(); + private List listDefaultNull; + + public WithDefaults() { + } + + public WithDefaults(String stringDefaultValue, String stringDefaultNull, String[] arrayDefaultValue, + String[] arrayDefaultEmpty, String[] arrayDefaultNull, + List listDefaultValue, List listDefaultEmpty, + List listDefaultNull) { + this.stringDefaultValue = stringDefaultValue; + this.stringDefaultNull = stringDefaultNull; + this.arrayDefaultValue = arrayDefaultValue == null ? null : arrayDefaultValue.clone(); + this.arrayDefaultEmpty = arrayDefaultEmpty == null ? null : arrayDefaultEmpty.clone(); + this.arrayDefaultNull = arrayDefaultNull == null ? null : arrayDefaultNull.clone(); + this.listDefaultValue = listDefaultValue == null ? null : new ArrayList<>(listDefaultValue); + this.listDefaultEmpty = listDefaultEmpty == null ? null : new ArrayList<>(listDefaultEmpty); + this.listDefaultNull = listDefaultNull == null ? null : new ArrayList<>(listDefaultNull); + } + + public String getStringDefaultValue() { + return stringDefaultValue; + } + + public void setStringDefaultValue(String stringDefaultValue) { + this.stringDefaultValue = stringDefaultValue; + } + + public String getStringDefaultNull() { + return stringDefaultNull; + } + + public void setStringDefaultNull(String stringDefaultNull) { + this.stringDefaultNull = stringDefaultNull; + } + + public String[] getArrayDefaultValue() { + return arrayDefaultValue; + } + + public void setArrayDefaultValue(String[] arrayDefaultValue) { + this.arrayDefaultValue = arrayDefaultValue; + } + + public String[] getArrayDefaultEmpty() { + return arrayDefaultEmpty; + } + + public void setArrayDefaultEmpty(String[] arrayDefaultEmpty) { + this.arrayDefaultEmpty = arrayDefaultEmpty; + } + + public String[] getArrayDefaultNull() { + return arrayDefaultNull; + } + + public void setArrayDefaultNull(String[] arrayDefaultNull) { + this.arrayDefaultNull = arrayDefaultNull; + } + + public List getListDefaultValue() { + return listDefaultValue; + } + + public void setListDefaultValue(List listDefaultValue) { + this.listDefaultValue = listDefaultValue; + } + + public List getListDefaultEmpty() { + return listDefaultEmpty; + } + + public void setListDefaultEmpty(List listDefaultEmpty) { + this.listDefaultEmpty = listDefaultEmpty; + } + + public List getListDefaultNull() { + return listDefaultNull; + } + + public void setListDefaultNull(List listDefaultNull) { + this.listDefaultNull = listDefaultNull; + } + } } -- GitLab From 7dc9143ce20883b073c45385109c5b9bd73bf7ec Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 27 Oct 2017 00:43:47 +0200 Subject: [PATCH 0016/1380] [JENKINS-27026] - Update SSHD Module from 2.0 to 2.3 to pick the fix --- war/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/war/pom.xml b/war/pom.xml index 1c4625ec0f..c1fb6b7ada 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -134,7 +134,7 @@ THE SOFTWARE. org.jenkins-ci.modules sshd - 2.0 + 2.3 org.jenkins-ci.ui -- GitLab From 903b4461d37170ccda49ce6637adf7cf4a261b93 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 30 Oct 2017 14:20:57 -0400 Subject: [PATCH 0017/1380] [JENKINS-47736] Switch Remoting/XStream blacklist to a whitelist. --- core/src/main/java/hudson/util/XStream2.java | 12 +- core/src/main/java/jenkins/model/Jenkins.java | 10 +- .../jenkins/security/ClassFilterImpl.java | 257 ++++++++++++++++++ .../jenkins/security/CustomClassFilter.java | 174 ++++++++++++ .../jenkins/security/whitelisted-classes.txt | 83 ++++++ pom.xml | 2 +- test/pom.xml | 2 +- .../groovy/hudson/cli/BuildCommandTest.groovy | 25 -- .../java/hudson/cli/BuildCommand2Test.java | 30 ++ .../hudson/util/XStream2Security383Test.java | 10 +- .../java/jenkins/install/InstallUtilTest.java | 2 + .../java/jenkins/install/SetupWizardTest.java | 13 +- .../jenkins/security/ClassFilterImplTest.java | 163 +++++++++++ .../security/CustomClassFilterTest.java | 75 +++++ .../jenkins/security/Security218CliTest.java | 3 +- .../jenkins/security/Security218Test.java | 35 ++- .../resources/plugins/custom-class-filter.jpi | Bin 0 -> 3955 bytes 17 files changed, 831 insertions(+), 65 deletions(-) create mode 100644 core/src/main/java/jenkins/security/ClassFilterImpl.java create mode 100644 core/src/main/java/jenkins/security/CustomClassFilter.java create mode 100644 core/src/main/resources/jenkins/security/whitelisted-classes.txt create mode 100644 test/src/test/java/jenkins/security/ClassFilterImplTest.java create mode 100644 test/src/test/java/jenkins/security/CustomClassFilterTest.java create mode 100644 test/src/test/resources/plugins/custom-class-filter.jpi diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 6b17761cb6..4e5e61e2c5 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -68,8 +68,6 @@ import java.nio.charset.Charset; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.CheckForNull; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; /** * {@link XStream} enhanced for additional Java5 support and improved robustness. @@ -462,14 +460,8 @@ public class XStream2 extends XStream { if (type == null) { return false; } - try { - ClassFilter.DEFAULT.check(type); - ClassFilter.DEFAULT.check(type.getName()); - } catch (SecurityException se) { - // claim we can convert all the scary stuff so we can throw exceptions when attempting to do so - return true; - } - return false; + // claim we can convert all the scary stuff so we can throw exceptions when attempting to do so + return ClassFilter.DEFAULT.isBlacklisted(type.getName()) || ClassFilter.DEFAULT.isBlacklisted(type); } } } diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index e3e890151e..b356f282ec 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -106,7 +106,6 @@ import hudson.model.listeners.ItemListener; import hudson.model.listeners.SCMListener; import hudson.model.listeners.SaveableListener; import hudson.remoting.Callable; -import hudson.remoting.ClassFilter; import hudson.remoting.LocalChannel; import hudson.remoting.VirtualChannel; import hudson.scm.RepositoryBrowser; @@ -182,6 +181,7 @@ import jenkins.install.InstallState; import jenkins.install.InstallUtil; import jenkins.install.SetupWizard; import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy; +import jenkins.security.ClassFilterImpl; import jenkins.security.ConfidentialKey; import jenkins.security.ConfidentialStore; import jenkins.security.SecurityListener; @@ -284,7 +284,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; -import java.util.regex.Pattern; import java.util.stream.Collectors; import static hudson.Util.*; @@ -894,11 +893,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve adjuncts = new AdjunctManager(servletContext, pluginManager.uberClassLoader,"adjuncts/"+SESSION_HASH, TimeUnit.DAYS.toMillis(365)); - try { - ClassFilter.appendDefaultFilter(Pattern.compile("java[.]security[.]SignedObject")); // TODO move to standard blacklist - } catch (ClassFilter.ClassFilterException ex) { - throw new IOException("Remoting library rejected the java[.]security[.]SignedObject blacklist pattern", ex); - } + ClassFilterImpl.register(); // initialization consists of ... executeReactor( is, @@ -3278,6 +3273,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve if (JenkinsJVM.isJenkinsJVM()) { JenkinsJVMAccess._setJenkinsJVM(oldJenkinsJVM); } + ClassFilterImpl.unregister(); } } diff --git a/core/src/main/java/jenkins/security/ClassFilterImpl.java b/core/src/main/java/jenkins/security/ClassFilterImpl.java new file mode 100644 index 0000000000..639b99d5f6 --- /dev/null +++ b/core/src/main/java/jenkins/security/ClassFilterImpl.java @@ -0,0 +1,257 @@ +/* + * The MIT License + * + * Copyright 2017 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 com.google.common.collect.ImmutableSet; +import hudson.ExtensionList; +import hudson.Main; +import hudson.remoting.ClassFilter; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.CodeSource; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import jenkins.model.Jenkins; +import org.apache.commons.io.IOUtils; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * Customized version of {@link ClassFilter#DEFAULT}. + * First of all, {@link CustomClassFilter}s are given the first right of decision. + * Then delegates to {@link ClassFilter#STANDARD} for its blacklist. + * A class not mentioned in the blacklist is permitted unless it is defined in some third-party library + * (as opposed to {@code jenkins-core.jar}, a plugin JAR, or test code during {@link Main#isUnitTest}) + * yet is not mentioned in {@code whitelisted-classes.txt}. + */ +@Restricted(NoExternalUse.class) +public class ClassFilterImpl extends ClassFilter { + + private static final Logger LOGGER = Logger.getLogger(ClassFilterImpl.class.getName()); + + /** + * Register this implementation as the default in the system. + */ + public static void register() { + if (Main.isUnitTest && Jenkins.class.getProtectionDomain().getCodeSource().getLocation() == null) { + mockOff(); + return; + } + ClassFilter.setDefault(new ClassFilterImpl()); + } + + /** + * Undo {@link #register}. + */ + public static void unregister() { + ClassFilter.setDefault(ClassFilter.STANDARD); + } + + private static void mockOff() { + LOGGER.warning("Disabling class filtering since we appear to be in a special test environment, perhaps Mockito/PowerMock"); + ClassFilter.setDefault(ClassFilter.NONE); // even Method on the standard blacklist is going to explode + } + + private ClassFilterImpl() {} + + /** Whether a given class is blacklisted. */ + private final Map, Boolean> cache = Collections.synchronizedMap(new WeakHashMap<>()); + /** Whether a given code source location is whitelisted. */ + private final Map codeSourceCache = Collections.synchronizedMap(new HashMap<>()); + /** Names of classes outside Jenkins core or plugins which have a special serial form but are considered safe. */ + static final Set WHITELISTED_CLASSES; + static { + try (InputStream is = ClassFilterImpl.class.getResourceAsStream("whitelisted-classes.txt")) { + WHITELISTED_CLASSES = ImmutableSet.copyOf(IOUtils.readLines(is, StandardCharsets.UTF_8)); + } catch (IOException x) { + throw new ExceptionInInitializerError(x); + } + } + + @SuppressWarnings("rawtypes") + @Override + public boolean isBlacklisted(Class _c) { + for (CustomClassFilter f : ExtensionList.lookup(CustomClassFilter.class)) { + Boolean r = f.permits(_c); + if (r != null) { + LOGGER.log(Level.FINER, "{0} specifies a policy for {1}: {2}", new Object[] {f, _c.getName(), r}); + return !r; + } + } + return cache.computeIfAbsent(_c, c -> { + if (ClassFilter.STANDARD.isBlacklisted(c)) { // currently never true: only the name overload is overridden + return true; + } + String name = c.getName(); + if (Main.isUnitTest && name.contains("$$EnhancerByMockitoWithCGLIB$$")) { + mockOff(); + return false; + } + if (c.isArray()) { + LOGGER.log(Level.FINE, "permitting {0} since it is an array", name); + return false; + } + if (Throwable.class.isAssignableFrom(c)) { + LOGGER.log(Level.FINE, "permitting {0} since it is a throwable", name); + return false; + } + if (Enum.class.isAssignableFrom(c)) { // Class.isEnum seems to be false for, e.g., java.util.concurrent.TimeUnit$6 + LOGGER.log(Level.FINE, "permitting {0} since it is an enum", name); + return false; + } + CodeSource codeSource = c.getProtectionDomain().getCodeSource(); + URL location = codeSource != null ? codeSource.getLocation() : null; + if (location != null) { + if (isLocationWhitelisted(location.toString())) { + LOGGER.log(Level.FINE, "permitting {0} due to its location in {1}", new Object[] {name, location}); + return false; + } + } else { + ClassLoader loader = c.getClassLoader(); + if (loader != null && loader.getClass().getName().equals("hudson.remoting.RemoteClassLoader")) { + LOGGER.log(Level.FINE, "permitting {0} since it was loaded by a remote class loader", name); + return false; + } + } + if (WHITELISTED_CLASSES.contains(name)) { + LOGGER.log(Level.FINE, "tolerating {0} by whitelist", name); + return false; + } + LOGGER.log(Level.WARNING, "{0} in {1} might be dangerous, so rejecting; see https://jenkins.io/redirect/class-filter/", new Object[] {name, location != null ? location : "JRE"}); + return true; + }); + } + + private static final Pattern CLASSES_JAR = Pattern.compile("(file:/.+/)WEB-INF/lib/classes[.]jar"); + private boolean isLocationWhitelisted(String _loc) { + return codeSourceCache.computeIfAbsent(_loc, loc -> { + if (loc.equals(Jenkins.class.getProtectionDomain().getCodeSource().getLocation().toString())) { + LOGGER.log(Level.FINE, "{0} seems to be the location of Jenkins core, OK", loc); + return true; + } + if (loc.equals(ClassFilter.class.getProtectionDomain().getCodeSource().getLocation().toString())) { + LOGGER.log(Level.FINE, "{0} seems to be the location of Remoting, OK", loc); + return true; + } + if (loc.matches("file:/.+[.]jar")) { + try (JarFile jf = new JarFile(new File(new URI(loc)), false)) { + Manifest mf = jf.getManifest(); + if (mf != null) { + if (isPluginManifest(mf)) { + LOGGER.log(Level.FINE, "{0} seems to be a Jenkins plugin, OK", loc); + return true; + } else { + LOGGER.log(Level.FINE, "{0} does not look like a Jenkins plugin", loc); + } + } else { + LOGGER.log(Level.FINE, "ignoring {0} with no manifest", loc); + } + } catch (Exception x) { + LOGGER.log(Level.WARNING, "problem checking " + loc, x); + } + } + Matcher m = CLASSES_JAR.matcher(loc); + if (m.matches()) { + // Cf. ClassicPluginStrategy.createClassJarFromWebInfClasses: handle legacy plugin format with unpacked WEB-INF/classes/ + try { + File manifestFile = new File(new URI(m.group(1) + "META-INF/MANIFEST.MF")); + if (manifestFile.isFile()) { + try (InputStream is = new FileInputStream(manifestFile)) { + if (isPluginManifest(new Manifest(is))) { + LOGGER.log(Level.FINE, "{0} looks like a Jenkins plugin based on {1}, OK", new Object[] {loc, manifestFile}); + return true; + } else { + LOGGER.log(Level.FINE, "{0} does not look like a Jenkins plugin", manifestFile); + } + } + } else { + LOGGER.log(Level.FINE, "{0} has no matching {1}", new Object[] {loc, manifestFile}); + } + } catch (Exception x) { + LOGGER.log(Level.WARNING, "problem checking " + loc, x); + } + } + if (Main.isUnitTest) { + if (loc.endsWith("/target/classes/")) { + LOGGER.log(Level.FINE, "{0} seems to be current plugin classes, OK", loc); + return true; + } + if (loc.endsWith("/target/test-classes/") || loc.endsWith("-tests.jar")) { + LOGGER.log(Level.FINE, "{0} seems to be test classes, OK", loc); + return true; + } + if (loc.matches(".+/jenkins-test-harness-.+[.]jar")) { + LOGGER.log(Level.FINE, "{0} seems to be jenkins-test-harness, OK", loc); + return true; + } + } + LOGGER.log(Level.FINE, "{0} is not recognized; rejecting", loc); + return false; + }); + } + + private static boolean isPluginManifest(Manifest mf) { + Attributes attr = mf.getMainAttributes(); + return attr.getValue("Short-Name") != null && (attr.getValue("Plugin-Version") != null || attr.getValue("Jenkins-Version") != null); + } + + @Override + public boolean isBlacklisted(String name) { + if (Main.isUnitTest && name.contains("$$EnhancerByMockitoWithCGLIB$$")) { + mockOff(); + return false; + } + for (CustomClassFilter f : ExtensionList.lookup(CustomClassFilter.class)) { + Boolean r = f.permits(name); + if (r != null) { + LOGGER.log(Level.FINER, "{0} specifies a policy for {1}: {2}", new Object[] {f, name, r}); + return !r; + } + } + // could apply a cache if the pattern search turns out to be slow + if (ClassFilter.STANDARD.isBlacklisted(name)) { + LOGGER.log(Level.WARNING, "rejecting {0} according to standard blacklist; see https://jenkins.io/redirect/class-filter/", name); + return true; + } else { + return false; + } + } + +} diff --git a/core/src/main/java/jenkins/security/CustomClassFilter.java b/core/src/main/java/jenkins/security/CustomClassFilter.java new file mode 100644 index 0000000000..e0cd7186aa --- /dev/null +++ b/core/src/main/java/jenkins/security/CustomClassFilter.java @@ -0,0 +1,174 @@ +/* + * The MIT License + * + * Copyright 2017 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 hudson.Extension; +import hudson.ExtensionList; +import hudson.ExtensionPoint; +import hudson.init.InitMilestone; +import hudson.init.Initializer; +import hudson.remoting.ClassFilter; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.CheckForNull; +import jenkins.model.Jenkins; +import jenkins.util.SystemProperties; +import org.apache.commons.io.IOUtils; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +/** + * Allows extensions to adjust the behavior of {@link ClassFilter#DEFAULT}. + * Custom filters can be called frequently, and return values are uncached, so implementations should be fast. + * @see ClassFilterImpl + */ +@Restricted(NoExternalUse.class) // until a use case is identified for a _dynamic_ extension from some plugin +public interface CustomClassFilter extends ExtensionPoint { + + /** + * Determine whether a class should be permitted by {@link ClassFilter#isBlacklisted(Class)} of {@link ClassFilter#DEFAULT}. + * @param c the class + * @return true to permit it when it would normally be rejected (for example due to having a custom serialization method and being from a third-party library); + * false to reject it when it would normally be permitted; + * null to express no opinion (the default) + */ + default @CheckForNull Boolean permits(Class c) { + return null; + } + + /** + * Determine whether a class should be permitted by {@link ClassFilter#isBlacklisted(String)} of {@link ClassFilter#DEFAULT}. + * @param name a class name + * @return true to permit it when it would normally be rejected (currently useless); + * false to reject it when it would normally be permitted (currently due to {@link ClassFilter#STANDARD}; + * null to express no opinion (the default) + */ + default @CheckForNull Boolean permits(String name) { + return null; + } + + /** + * Standard filter which pays attention to a system property. + * To use, specify a system property {@code hudson.remoting.ClassFilter} containing a comma-separated list of {@link Class#getName} to whitelist. + * Entries may also be preceded by {@code !} to blacklist. + * Example: {@code -Dhudson.remoting.ClassFilter=com.google.common.collect.LinkedListMultimap,!com.acme.illadvised.YoloReflectionFactory$Handle} + */ + @Restricted(DoNotUse.class) + @Extension + public class Static implements CustomClassFilter { + + /** + * Map from {@link Class#getName} to true to permit, false to reject. + * Unmentioned classes are not treated specially. + * Intentionally {@code public} for possible mutation without restart by Groovy scripting. + */ + public final Map overrides = new HashMap<>(); + + public Static() { + String entries = SystemProperties.getString("hudson.remoting.ClassFilter"); + if (entries != null) { + for (String entry : entries.split(",")) { + if (entry.startsWith("!")) { + overrides.put(entry.substring(1), false); + } else { + overrides.put(entry, true); + } + } + Logger.getLogger(Static.class.getName()).log(Level.FINE, "user-defined entries: {0}", overrides); + } + } + + @Override + public Boolean permits(Class c) { + return permits(c.getName()); + } + + @Override + public Boolean permits(String name) { + return overrides.get(name); + } + + } + + /** + * Standard filter which can load whitelists and blacklists from plugins. + * To use, add a resource {@code META-INF/hudson.remoting.ClassFilter} to your plugin. + * Each line should be the {@link Class#getName} of a class to whitelist. + * Or you may blacklist a class by preceding its name with {@code !}. + * Example: + *
+     * com.google.common.collect.LinkedListMultimap
+     * !com.acme.illadvised.YoloReflectionFactory$Handle
+     * 
+ */ + @Restricted(DoNotUse.class) + @Extension + public class Contributed implements CustomClassFilter { + + /** + * Map from {@link Class#getName} to true to permit, false to reject. + * Unmentioned classes are not treated specially. + */ + private final Map overrides = new HashMap<>(); + + @Override + public Boolean permits(Class c) { + return permits(c.getName()); + } + + @Override + public Boolean permits(String name) { + return overrides.get(name); + } + + @Initializer(after = InitMilestone.PLUGINS_PREPARED, before = InitMilestone.PLUGINS_STARTED, fatal = false) + public static void load() throws IOException { + Map overrides = ExtensionList.lookup(CustomClassFilter.class).get(Contributed.class).overrides; + Enumeration resources = Jenkins.getInstance().getPluginManager().uberClassLoader.getResources("META-INF/hudson.remoting.ClassFilter"); + while (resources.hasMoreElements()) { + try (InputStream is = resources.nextElement().openStream()) { + for (String entry : IOUtils.readLines(is, StandardCharsets.UTF_8)) { + if (entry.startsWith("!")) { + overrides.put(entry.substring(1), false); + } else { + overrides.put(entry, true); + } + } + } + } + Logger.getLogger(Contributed.class.getName()).log(Level.FINE, "plugin-defined entries: {0}", overrides); + } + + } + +} diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt new file mode 100644 index 0000000000..238008d0e7 --- /dev/null +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -0,0 +1,83 @@ +com.google.common.collect.AbstractSetMultimap +com.google.common.collect.EmptyImmutableSet +com.google.common.collect.EmptyImmutableSortedSet +com.google.common.collect.HashMultimap +com.google.common.collect.ImmutableList +com.google.common.collect.ImmutableSet$SerializedForm +com.google.common.collect.ImmutableSortedSet +com.google.common.collect.RegularImmutableList +com.google.common.collect.RegularImmutableSet +com.google.common.collect.RegularImmutableSortedSet +com.google.common.collect.SingletonImmutableList +com.google.common.collect.SingletonImmutableSet +hudson.maven.MavenInformation +java.io.File +java.lang.Boolean +java.lang.Class +java.lang.Double +java.lang.Enum +java.lang.Float +java.lang.Integer +java.lang.Long +java.lang.Number +java.lang.Object +java.lang.StackTraceElement +java.lang.String +java.lang.reflect.Proxy +java.net.URL +java.util.ArrayDeque +java.util.ArrayList +java.util.Arrays$ArrayList +java.util.Collections$EmptyList +java.util.Collections$EmptySet +java.util.Collections$SetFromMap +java.util.Collections$SingletonList +java.util.Collections$SingletonMap +java.util.Collections$SingletonSet +java.util.Collections$UnmodifiableCollection +java.util.Collections$UnmodifiableList +java.util.Collections$UnmodifiableRandomAccessList +java.util.Collections$UnmodifiableSet +java.util.Date +java.util.EnumMap +java.util.GregorianCalendar +java.util.HashMap +java.util.HashSet +java.util.Hashtable +java.util.LinkedHashMap +java.util.LinkedHashSet +java.util.LinkedList +java.util.Locale +java.util.Properties +java.util.RegularEnumSet +java.util.Stack +java.util.TreeMap +java.util.TreeMap$KeySet +java.util.TreeSet +java.util.UUID +java.util.Vector +java.util.concurrent.ConcurrentHashMap +java.util.concurrent.ConcurrentSkipListMap +java.util.concurrent.CopyOnWriteArrayList +java.util.concurrent.CopyOnWriteArraySet +java.util.concurrent.CountDownLatch +java.util.concurrent.CountDownLatch$Sync +java.util.concurrent.atomic.AtomicBoolean +java.util.logging.Level +java.util.logging.LogRecord +java.util.regex.Pattern +javaposse.jobdsl.dsl.GeneratedJob +org.acegisecurity.userdetails.User +org.apache.commons.fileupload.disk.DiskFileItem +org.apache.commons.fileupload.util.FileItemHeadersImpl +org.eclipse.jgit.lib.ObjectId +org.eclipse.jgit.lib.ObjectIdOwnerMap$Entry +org.eclipse.jgit.revwalk.RevCommit +org.eclipse.jgit.revwalk.RevObject +org.eclipse.jgit.revwalk.RevTree +org.eclipse.jgit.transport.URIish +org.jboss.marshalling.TraceInformation$FieldInfo +org.jboss.marshalling.TraceInformation$ObjectInfo +org.jvnet.hudson.MemoryUsage +org.jvnet.localizer.Localizable +org.jvnet.localizer.ResourceBundleHolder diff --git a/pom.xml b/pom.xml index 52677858e3..34ed6de12e 100644 --- a/pom.xml +++ b/pom.xml @@ -170,7 +170,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.13 + 3.14-20171030.181318-3 diff --git a/test/pom.xml b/test/pom.xml index 5cb6cdf7d8..09f49cba36 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -53,7 +53,7 @@ THE SOFTWARE. ${project.groupId} jenkins-test-harness - 2.31 + 2.32-20171030.181811-1 test diff --git a/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy b/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy index 86ea1ecc83..eb1582ff59 100644 --- a/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy +++ b/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy @@ -72,31 +72,6 @@ public class BuildCommandTest { @Rule public JenkinsRule j = new JenkinsRule(); - /** - * Just schedules a build and return. - */ - @Test void async() { - def p = j.createFreeStyleProject(); - def started = new OneShotEvent(); - def completed = new OneShotEvent(); - p.buildersList.add([perform: {AbstractBuild build, Launcher launcher, BuildListener listener -> - started.signal(); - completed.block(); - return true; - }] as TestBuilder); - - // this should be asynchronous - def cli = new CLI(j.URL) - try { - assertEquals(0,cli.execute(["build", p.name])) - started.block() - assertTrue(p.getBuildByNumber(1).isBuilding()) - completed.signal() - } finally { - cli.close(); - } - } - /** * Tests synchronous execution. */ diff --git a/test/src/test/java/hudson/cli/BuildCommand2Test.java b/test/src/test/java/hudson/cli/BuildCommand2Test.java index 1f5366e1bb..6b6a03743e 100644 --- a/test/src/test/java/hudson/cli/BuildCommand2Test.java +++ b/test/src/test/java/hudson/cli/BuildCommand2Test.java @@ -31,6 +31,7 @@ import hudson.model.FileParameterDefinition; import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; import hudson.model.ParametersDefinitionProperty; +import hudson.util.OneShotEvent; import java.io.ByteArrayInputStream; import java.io.IOException; import org.junit.ClassRule; @@ -71,4 +72,33 @@ public class BuildCommand2Test { r.assertLogContains("uploaded content here", b); } + /** + * Just schedules a build and return. + */ + @Test + public void async() throws Exception { + FreeStyleProject p = r.createFreeStyleProject(); + OneShotEvent started = new OneShotEvent(); + OneShotEvent completed = new OneShotEvent(); + p.getBuildersList().add(new TestBuilder() { + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + started.signal(); + completed.block(); + return true; + } + }); + + // this should be asynchronous + CLI cli = new CLI(r.getURL()); + try { + assertEquals(0, cli.execute("build", p.getName())); + started.block(); + assertTrue(p.getBuildByNumber(1).isBuilding()); + completed.signal(); + } finally { + cli.close(); + } + } + } diff --git a/test/src/test/java/hudson/util/XStream2Security383Test.java b/test/src/test/java/hudson/util/XStream2Security383Test.java index 7e089e4cc4..46862eb8b1 100644 --- a/test/src/test/java/hudson/util/XStream2Security383Test.java +++ b/test/src/test/java/hudson/util/XStream2Security383Test.java @@ -19,8 +19,11 @@ import javax.servlet.ServletInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.util.logging.Level; +import jenkins.security.ClassFilterImpl; import static org.junit.Assert.assertFalse; +import org.jvnet.hudson.test.LoggerRule; import static org.mockito.Mockito.when; public class XStream2Security383Test { @@ -31,6 +34,9 @@ public class XStream2Security383Test { @Rule public TemporaryFolder f = new TemporaryFolder(); + @Rule + public LoggerRule logging = new LoggerRule().record(ClassFilterImpl.class, Level.FINE); + @Mock private StaplerRequest req; @@ -64,7 +70,7 @@ public class XStream2Security383Test { try { Items.load(j.jenkins, tempJobDir); } catch (Exception e) { - // ignore + e.printStackTrace(); } assertFalse("no file should be created here", exploitFile.exists()); } finally { @@ -97,7 +103,7 @@ public class XStream2Security383Test { try { j.jenkins.doCreateItem(req, rsp); } catch (Exception e) { - // don't care + e.printStackTrace(); } assertFalse("no file should be created here", exploitFile.exists()); diff --git a/test/src/test/java/jenkins/install/InstallUtilTest.java b/test/src/test/java/jenkins/install/InstallUtilTest.java index d57584838c..d6c8ed2126 100644 --- a/test/src/test/java/jenkins/install/InstallUtilTest.java +++ b/test/src/test/java/jenkins/install/InstallUtilTest.java @@ -117,6 +117,8 @@ public class InstallUtilTest { */ @Test public void test_getLastExecVersion() throws Exception { + Main.isUnitTest = true; + // Delete the config file, forcing getLastExecVersion to return // the default/unset version value. InstallUtil.getConfigFile().delete(); diff --git a/test/src/test/java/jenkins/install/SetupWizardTest.java b/test/src/test/java/jenkins/install/SetupWizardTest.java index 03dbc0df74..dfc5b3ec96 100644 --- a/test/src/test/java/jenkins/install/SetupWizardTest.java +++ b/test/src/test/java/jenkins/install/SetupWizardTest.java @@ -32,9 +32,7 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; -import java.util.Set; import jenkins.AgentProtocolTest; -import jenkins.slaves.DeprecatedAgentProtocolMonitor; import org.apache.commons.io.FileUtils; import static org.hamcrest.Matchers.*; import org.junit.Before; @@ -42,7 +40,6 @@ import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertFalse; import org.junit.rules.TemporaryFolder; import org.jvnet.hudson.test.Issue; @@ -88,7 +85,7 @@ public class SetupWizardTest { @Issue("JENKINS-34833") public void shouldReturnUpdateSiteJSONIfSpecified() throws Exception { // Init the update site - CustomUpdateSite us = new CustomUpdateSite(tmpdir); + CustomUpdateSite us = new CustomUpdateSite(tmpdir.getRoot()); us.init(); j.jenkins.getUpdateCenter().getSites().add(us); @@ -141,15 +138,15 @@ public class SetupWizardTest { private static final class CustomUpdateSite extends UpdateSite { - private final TemporaryFolder tmpdir; + private final File tmpdir; - public CustomUpdateSite(TemporaryFolder tmpdir) throws MalformedURLException { - super("custom-uc", tmpdir.getRoot().toURI().toURL().toString() + "update-center.json"); + CustomUpdateSite(File tmpdir) throws MalformedURLException { + super("custom-uc", tmpdir.toURI().toURL().toString() + "update-center.json"); this.tmpdir = tmpdir; } public void init() throws IOException { - File newFile = tmpdir.newFile("platform-plugins.json"); + File newFile = new File(tmpdir, "platform-plugins.json"); FileUtils.write(newFile, "[ { " + "\"category\":\"Organization and Administration\", " + "\"plugins\": [ { \"name\": \"dashboard-view\"}, { \"name\": \"antisamy-markup-formatter\" } ]" diff --git a/test/src/test/java/jenkins/security/ClassFilterImplTest.java b/test/src/test/java/jenkins/security/ClassFilterImplTest.java new file mode 100644 index 0000000000..bed5648b6f --- /dev/null +++ b/test/src/test/java/jenkins/security/ClassFilterImplTest.java @@ -0,0 +1,163 @@ +/* + * The MIT License + * + * Copyright 2017 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 com.google.common.collect.LinkedListMultimap; +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.model.FreeStyleProject; +import hudson.model.Result; +import hudson.tasks.BuildStepDescriptor; +import hudson.tasks.Builder; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.TreeSet; +import java.util.logging.Level; +import jenkins.model.GlobalConfiguration; +import org.apache.commons.io.IOUtils; +import org.junit.Test; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.junit.Assume.*; +import org.junit.ClassRule; +import org.junit.Rule; +import org.jvnet.hudson.test.BuildWatcher; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.LoggerRule; +import org.jvnet.hudson.test.TestExtension; +import org.jvnet.hudson.test.WithoutJenkins; + +public class ClassFilterImplTest { + + @ClassRule + public static BuildWatcher buildWatcher = new BuildWatcher(); + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Rule + public LoggerRule logging = new LoggerRule().record(ClassFilterImpl.class, Level.FINE); + + @WithoutJenkins + @Test + public void whitelistSanity() throws Exception { + try (InputStream is = ClassFilterImpl.class.getResourceAsStream("whitelisted-classes.txt")) { + List lines = IOUtils.readLines(is); + assertThat("whitelist is ordered", new TreeSet<>(lines), contains(lines.toArray(new String[0]))); + for (String line : lines) { + try { + Class.forName(line); + } catch (ClassNotFoundException x) { + System.err.println("skipping checks of unknown class " + line); + } + } + } + } + + @Test + public void masterToSlaveBypassesWhitelist() throws Exception { + assumeThat(ClassFilterImpl.WHITELISTED_CLASSES, not(contains(LinkedListMultimap.class.getName()))); + FreeStyleProject p = r.createFreeStyleProject(); + p.setAssignedNode(r.createSlave()); + p.getBuildersList().add(new M2SBuilder()); + r.assertLogContains("sent {}", r.buildAndAssertSuccess(p)); + } + public static class M2SBuilder extends Builder { + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + listener.getLogger().println("sent " + launcher.getChannel().call(new M2S())); + return true; + } + @TestExtension("masterToSlaveBypassesWhitelist") + public static class DescriptorImpl extends BuildStepDescriptor { + @SuppressWarnings("rawtypes") + @Override + public boolean isApplicable(Class jobType) { + return true; + } + } + } + private static class M2S extends MasterToSlaveCallable { + private final LinkedListMultimap obj = LinkedListMultimap.create(); + @Override + public String call() throws RuntimeException { + return obj.toString(); + } + } + + // Note that currently even M2S callables are rejected when using classes blacklisted in ClassFilter.STANDARD, such as JSONObject. + + @Test + public void slaveToMasterRequiresWhitelist() throws Exception { + assumeThat(ClassFilterImpl.WHITELISTED_CLASSES, not(contains(LinkedListMultimap.class.getName()))); + FreeStyleProject p = r.createFreeStyleProject(); + p.setAssignedNode(r.createSlave()); + p.getBuildersList().add(new S2MBuilder()); + r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0)); + } + public static class S2MBuilder extends Builder { + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + listener.getLogger().println("received " + launcher.getChannel().call(new S2M())); + return true; + } + @TestExtension("slaveToMasterRequiresWhitelist") + public static class DescriptorImpl extends BuildStepDescriptor { + @SuppressWarnings("rawtypes") + @Override + public boolean isApplicable(Class jobType) { + return true; + } + } + } + private static class S2M extends MasterToSlaveCallable, RuntimeException> { + @Override + public LinkedListMultimap call() throws RuntimeException { + return LinkedListMultimap.create(); + } + } + + @Test + public void xstreamRequiresWhitelist() throws Exception { + assumeThat(ClassFilterImpl.WHITELISTED_CLASSES, not(contains(LinkedListMultimap.class.getName()))); + Config config = GlobalConfiguration.all().get(Config.class); + config.obj = LinkedListMultimap.create(); + try { + config.save(); + fail("should not have been accepted"); + } catch (Exception x) { + x.printStackTrace(); + // OK + } + } + @TestExtension("xstreamRequiresWhitelist") + public static class Config extends GlobalConfiguration { + LinkedListMultimap obj; + } + +} diff --git a/test/src/test/java/jenkins/security/CustomClassFilterTest.java b/test/src/test/java/jenkins/security/CustomClassFilterTest.java new file mode 100644 index 0000000000..a8797f4ebc --- /dev/null +++ b/test/src/test/java/jenkins/security/CustomClassFilterTest.java @@ -0,0 +1,75 @@ +/* + * The MIT License + * + * Copyright 2017 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 hudson.remoting.ClassFilter; +import java.util.logging.Level; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import javax.script.SimpleBindings; +import jenkins.util.BuildListenerAdapter; +import jenkins.util.TreeString; +import jenkins.util.TreeStringBuilder; +import org.junit.Test; +import static org.hamcrest.Matchers.*; +import org.junit.Rule; +import org.junit.rules.ErrorCollector; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.LoggerRule; +import org.jvnet.hudson.test.recipes.WithPlugin; + +public class CustomClassFilterTest { + + static { + System.setProperty("hudson.remoting.ClassFilter", "javax.script.SimpleBindings,!jenkins.util.TreeString"); + } + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Rule + public ErrorCollector errors = new ErrorCollector(); + + @Rule + public LoggerRule logging = new LoggerRule().record("jenkins.security", Level.FINER); + + @WithPlugin("custom-class-filter.jpi") + @Test + public void smokes() throws Exception { + assertBlacklisted("enabled via system property", SimpleBindings.class, false); + assertBlacklisted("enabled via plugin", ScriptException.class, false); + assertBlacklisted("disabled by ClassFilter.STANDARD", ScriptEngineManager.class, true); + assertBlacklisted("part of Jenkins core, so why not?", BuildListenerAdapter.class, false); + // As an aside, the following appear totally unused anyway! + assertBlacklisted("disabled via system property", TreeString.class, true); + assertBlacklisted("disabled via plugin", TreeStringBuilder.class, true); + } + + private void assertBlacklisted(String message, Class c, boolean blacklisted) { + String name = c.getName(); + errors.checkThat(name + ": " + message, ClassFilter.DEFAULT.isBlacklisted(c) || ClassFilter.DEFAULT.isBlacklisted(name), is(blacklisted)); + } + +} diff --git a/test/src/test/java/jenkins/security/Security218CliTest.java b/test/src/test/java/jenkins/security/Security218CliTest.java index 40c5acc8ff..a6717ae46e 100644 --- a/test/src/test/java/jenkins/security/Security218CliTest.java +++ b/test/src/test/java/jenkins/security/Security218CliTest.java @@ -186,8 +186,8 @@ public class Security218CliTest { try (CLI cli = new CLI(r.getURL())) { int exitCode = cli.execute("send-payload", payload.toString(), "mv " + file.getAbsolutePath() + " " + moved.getAbsolutePath()); - assertEquals("Unexpected result code.", expectedResultCode, exitCode); assertTrue("Payload should not invoke the move operation " + file, !moved.exists()); + assertEquals("Unexpected result code.", expectedResultCode, exitCode); file.delete(); } } @@ -254,6 +254,7 @@ public class Security218CliTest { } }); } catch (Exception ex) { + ex.printStackTrace(); Throwable cause = ex; while (cause.getCause() != null) { cause = cause.getCause(); diff --git a/test/src/test/java/jenkins/security/Security218Test.java b/test/src/test/java/jenkins/security/Security218Test.java index 9892aebe7d..fa1fb1853f 100644 --- a/test/src/test/java/jenkins/security/Security218Test.java +++ b/test/src/test/java/jenkins/security/Security218Test.java @@ -3,12 +3,11 @@ package jenkins.security; import hudson.model.Node.Mode; import hudson.model.Slave; import hudson.remoting.Channel; -import hudson.remoting.Which; import hudson.slaves.DumbSlave; import hudson.slaves.JNLPLauncher; import hudson.slaves.RetentionStrategy; +import java.io.File; import org.apache.tools.ant.util.JavaEnvUtils; -import org.codehaus.groovy.runtime.Security218; import org.junit.After; import org.junit.Rule; import org.junit.Test; @@ -17,8 +16,14 @@ import org.jvnet.hudson.test.JenkinsRule; import java.io.Serializable; import java.util.Collections; +import java.util.logging.Level; +import org.apache.commons.io.FileUtils; +import org.codehaus.groovy.runtime.MethodClosure; +import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.*; +import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.LoggerRule; /** * @author Kohsuke Kawaguchi @@ -28,6 +33,12 @@ public class Security218Test implements Serializable { @Rule public transient JenkinsRule j = new JenkinsRule(); + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + @Rule + public LoggerRule logging = new LoggerRule().record(ClassFilterImpl.class, Level.FINE); + /** * JNLP slave. */ @@ -62,14 +73,16 @@ public class Security218Test implements Serializable { @SuppressWarnings("ConstantConditions") private void check(DumbSlave s) throws Exception { try { - s.getComputer().getChannel().call(new MasterToSlaveCallable() { - public Object call() { - return new Security218(); - } - }); - fail("Expected the connection to die"); + Object o = s.getComputer().getChannel().call(new EvilReturnValue()); + fail("Expected the connection to die: " + o); } catch (SecurityException e) { - assertTrue(e.getMessage().contains(Security218.class.getName())); + assertThat(e.getMessage(), containsString(MethodClosure.class.getName())); + } + } + private static class EvilReturnValue extends MasterToSlaveCallable { + @Override + public Object call() { + return new MethodClosure("oops", "trim"); } } @@ -92,9 +105,11 @@ public class Security218Test implements Serializable { public Channel launchJnlpSlave(Slave slave) throws Exception { j.createWebClient().goTo("computer/"+slave.getNodeName()+"/slave-agent.jnlp?encrypt=true", "application/octet-stream"); String secret = slave.getComputer().getJnlpMac(); + File slaveJar = tmp.newFile(); + FileUtils.copyURLToFile(new Slave.JnlpJar("slave.jar").getURL(), slaveJar); // To watch it fail: secret = secret.replace('1', '2'); ProcessBuilder pb = new ProcessBuilder(JavaEnvUtils.getJreExecutable("java"), - "-jar", Which.jarFile(hudson.remoting.Launcher.class).getAbsolutePath(), + "-jar", slaveJar.getAbsolutePath(), "-jnlpUrl", j.getURL() + "computer/"+slave.getNodeName()+"/slave-agent.jnlp", "-secret", secret); pb.inheritIO(); diff --git a/test/src/test/resources/plugins/custom-class-filter.jpi b/test/src/test/resources/plugins/custom-class-filter.jpi new file mode 100644 index 0000000000000000000000000000000000000000..c7ae4a9330ab5fcb427f1d13f7d6b58ce18e430f GIT binary patch literal 3955 zcmbtXc{tST7aw;bg(zEDN|Gh}kYyst7P4>2GL~i-%wR@j%NC`GWE(SlE6##JVHvoX;M=>2`T}^d0BV#cgb$@+4^0@>{ zt;Hv$)xqOjN7D4CXtK`m#9vCY7Vhxgr($Pe&AYk*zLlSK5hv0WK@idAZ$4Czk!n+* z4?3;MVlNHy6L*rRcQbvCxA>%D^92?t-$q=;#XNxdFzfNH<~2W3g{-WcGWr z^o7|xtE}w|^x(4)*Mz;Is%&yJB_7EK5H_F9f7V;~I#g{QS=!}M*F4)K_?Rf77Y%G!msS(Pi#TK<%-m?Xm)Z{d{Jp@1E6{DQPDWDQV-;azcH&U{fUaqPW zm6&~|su)oeGHP2C&z$?aF}PRgfLiSvojAOCsg_%{z6g8irEsa^63=Hjcl^9J9JFLV zo2gy=OEmUM(f=Uj0=N4iZj|sw>#BwKkR=|FV@HYG4xS6#9*RUm(PB3cE@jb;x-TT? zRX_TLzShKkVtCGeR$SiV>=jtADuwJD7Bd%G`h8KM^a-O%I z=@El*kNdF>#|G`}5XQC7{31#H*HpgblP*YRB%U~EB>4X8((ngYuR7vQhkD?~f$ha@ zb}A9Wq?~8MJ53Q@^Mp7M)&VHl=hGF zt?LC!GouCoNMvuG`ileX#O<%6JzNo@_Aa()v?vVj;sHg8IoYD<7Gbb=c7UKLE2{s| zUkYTt<8P(HbzO05Gb^U3~`!+D}83(l+z9L zF*>L%Wmb=*glxmpiqhU_FE`ee&TQda6XU!Dca z3Y%l=CEcoIn-*&!RGz3U;l82+hn8{rvSNRc#DW~|Fv-x7wP{Of!#aSpZPHyKTwaRGF{{bP=2WyM|Z@a646&#&T5%zDHI5*BlD>LFKx z_M>xp<5jqUdeOo2LzDB}b=5TDq^dzuf%7wLB{FI8ZsL570b`bJ|NUXv&JF- z_ma->dzLzFpJ^ zr8*-p2Yb~}@IZKqB|abg*#UalY_h!_DzOp28 z04unp@JpvGQm|LzlX9XgN;tB|H`6_AW+$Nz)2#FS4s)!NvDrY(jm~1;;fTq;@t94^ z(QEp&v)Mr*03jm<>ZG@8g_(71i9)&{72XIxG~Flk@);DGM3YK0%@d@UssRYU$p0`~>4Av*Ts_I(99dlwqP3KC^Dzr{{8XWr@r2Gp z>YfN{b;h`B7KE7-$G zt*ix+tpHCMDDIGJxLtjAGq3;2(oLGCnwEf4oxB!AkB%NeatUN4{!nqu#Vcf{C0?zN zGjd&O#0T8uBD3*gJH-LL(l?%w!#Ln4o!4D7B6>r)!p(j$rC3p2#{JEQ!`6o0+NsIm z=61egH`l3cu_5UL(CIEyL@9o9GG#-;dAXzvGI~Yje%OhAk0BoE$FfVL?iP8id-=Bp zTnV*Xs{UJzBiqAX!yTZnElS|ls5=(=N#P}lU$dy%oT=>??@pAb3D)zJ0DC9cGt zGl^eDNAY?GEsEuH00st1RJju6DQ4+3CgsSZjqi zFN66i`-ae}Q0K4=;zKOm5?uJHI5PTm?Q#QHq9EI;d$Xeo4HLr^Z>^j*&B*Bsh}s61 zm<;%>o3}zaWK|&Xi=8^tcOkMB&g1LO`DX}&z}jON)kOD%Z6=a>bi1_A;kfv*7z+Ir_vj z&^uo4(2uTBFa70OxHF%QNH_s=*kCM0g(fPrXhRUy9bS*Fz*j=Jo@_V3c>X=JLn~pa zuWCjQy2v=*dbeeU)ZEe>SXbf;yeVgtdUmsz2ab$}}c zdpcMFet<6doztV1!ew=8<7TrKA=Tm|DUr5stAcR6=|0SlE1p!e1q3uAwuMJ-dpd4G z#=9AJ4(T`YU@4dIC6FA`Q1X50KgZS$deXWQs)f8w>E)LjZZJ^jkpxPKS4NGlK|!(cC4+yS9JMCwCFX$fMED zA5(1qq;yc&%|Nb*Of0of7%~2+yzvuF Date: Mon, 6 Nov 2017 11:16:25 -0800 Subject: [PATCH 0018/1380] [maven-release-plugin] prepare release jenkins-2.73.3 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 3c50776345..1daec1d3b8 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.73.3-SNAPSHOT + 2.73.3 cli diff --git a/core/pom.xml b/core/pom.xml index a20c7ff2ef..216e374357 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.73.3-SNAPSHOT + 2.73.3 jenkins-core diff --git a/pom.xml b/pom.xml index f04af10b32..d29d5db560 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.73.3-SNAPSHOT + 2.73.3 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.73.3 diff --git a/test/pom.xml b/test/pom.xml index 6882fefe91..f3d7dc4f7e 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.73.3-SNAPSHOT + 2.73.3 test diff --git a/war/pom.xml b/war/pom.xml index 5c84682c70..a1377daa03 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.73.3-SNAPSHOT + 2.73.3 jenkins-war -- GitLab From 34b3a43d0969c7d48d66676b19a692647260c7a3 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 6 Nov 2017 11:16:25 -0800 Subject: [PATCH 0019/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 1daec1d3b8..d5aa1608e7 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.73.3 + 2.73.4-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 216e374357..9a582a5af4 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.73.3 + 2.73.4-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index d29d5db560..4b9d54ce7f 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.73.3 + 2.73.4-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.73.3 + HEAD diff --git a/test/pom.xml b/test/pom.xml index f3d7dc4f7e..bfde49ef1d 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.73.3 + 2.73.4-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index a1377daa03..e476822fe7 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.73.3 + 2.73.4-SNAPSHOT jenkins-war -- GitLab From d3bb74334b95cfe4a78823d8db987fdc4938355a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Gond=C5=BEa?= Date: Fri, 10 Nov 2017 10:48:19 +0100 Subject: [PATCH 0020/1380] Towards 2.89.1 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 2aa1057cff..c37054f397 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.89 + 2.89.1-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 1baacd133b..05c8d0244b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89 + 2.89.1-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index 482259c7de..be96e24162 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89 + 2.89.1-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.89 + HEAD diff --git a/test/pom.xml b/test/pom.xml index ef480ad414..da7682aef1 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89 + 2.89.1-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index b5e99f8104..29df2b41ce 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89 + 2.89.1-SNAPSHOT jenkins-war -- GitLab From 3a0a575ecf4f68ecb1e91a249c304d683e5c273c Mon Sep 17 00:00:00 2001 From: benru Date: Thu, 23 Oct 2014 20:27:19 +0100 Subject: [PATCH 0021/1380] JENKINS-25286: Export assigned labels for slaves and the label expression for jobs in JSON and XML APIs Adds @Export attribute to assignedLabels for Computer and AbstractProject Also adjusts visibility on Label "name" parameter so it appears by default --- core/src/main/java/hudson/model/AbstractProject.java | 1 + core/src/main/java/hudson/model/Computer.java | 6 ++++++ core/src/main/java/hudson/model/Label.java | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index ffee6413d8..b6be75139f 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -407,6 +407,7 @@ public abstract class AbstractProject

,R extends A /** * Gets the textual representation of the assigned label as it was entered by the user. */ + @Exported(name="labelExpression") public String getAssignedLabelString() { if (canRoam || assignedNode==null) return null; try { diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index f950cce781..3a968f54a9 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -776,6 +776,12 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces return "computer/" + Util.rawEncode(getName()) + "/"; } + @Exported + public Set getAssignedLabels() { + Node node = getNode(); + return (node != null) ? node.getAssignedLabels() : Collections.EMPTY_SET; + } + /** * Returns projects that are tied on this node. */ diff --git a/core/src/main/java/hudson/model/Label.java b/core/src/main/java/hudson/model/Label.java index cf6fdbff8b..0c26057765 100644 --- a/core/src/main/java/hudson/model/Label.java +++ b/core/src/main/java/hudson/model/Label.java @@ -127,7 +127,7 @@ public abstract class Label extends Actionable implements Comparable

For a platform-independent tool (such as Ant), configuring multiple installers - for a single tool makes not much sense, but for a platform dependent tool, + for a single tool does not make much sense, but for a platform dependent tool, multiple installer configurations allow you to run a different set up script depending on the agent environment. -- GitLab From 5c767c324aaa1866fa4bd661064c4851a17ca5bb Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Tue, 21 Nov 2017 00:44:19 +0100 Subject: [PATCH 0043/1380] Delete the temp file even if close() throws an exception --- .../main/java/hudson/util/AtomicFileWriter.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index dcbc1042bb..9992b836d7 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -119,8 +119,7 @@ public class AtomicFileWriter extends Writer { * the {@link #commit()} is called, to simplify coding. */ public void abort() throws IOException { - close(); - Files.deleteIfExists(tmpPath); + closeAndDeleteTempFile(); } public void commit() throws IOException { @@ -157,9 +156,16 @@ public class AtomicFileWriter extends Writer { @Override protected void finalize() throws Throwable { + closeAndDeleteTempFile(); + } + + private void closeAndDeleteTempFile() throws IOException { // one way or the other, temporary file should be deleted. - close(); - Files.deleteIfExists(tmpPath); + try { + close(); + } finally { + Files.deleteIfExists(tmpPath); + } } /** -- GitLab From 60085a0f10409265aa6d028ed5ae9a3b7b9e30f2 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 21 Nov 2017 10:45:40 +0100 Subject: [PATCH 0044/1380] [JENKINS-48116] - Restrore AbstractTaskListener binary compatibility in the core. Since we have the confirmed regression due to the binary compatibility change, I think we need to restore the compatibility. OTOH, I restricted the class, so all users will be forced to stop using it when they updgrade the core. --- core/src/main/java/hudson/util/AbstractTaskListener.java | 5 +++++ core/src/main/java/hudson/util/LogTaskListener.java | 5 ++++- core/src/main/java/hudson/util/StreamTaskListener.java | 5 ++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/AbstractTaskListener.java b/core/src/main/java/hudson/util/AbstractTaskListener.java index bf5b13a1b8..b40cfde838 100644 --- a/core/src/main/java/hudson/util/AbstractTaskListener.java +++ b/core/src/main/java/hudson/util/AbstractTaskListener.java @@ -1,11 +1,16 @@ package hudson.util; +import hudson.RestrictedSince; import hudson.model.TaskListener; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; /** * @deprecated implement {@link TaskListener} directly */ @Deprecated +@Restricted(NoExternalUse.class) +@RestrictedSince("2.91") public abstract class AbstractTaskListener implements TaskListener { } diff --git a/core/src/main/java/hudson/util/LogTaskListener.java b/core/src/main/java/hudson/util/LogTaskListener.java index 2bcdff09b9..bb3afc41e1 100644 --- a/core/src/main/java/hudson/util/LogTaskListener.java +++ b/core/src/main/java/hudson/util/LogTaskListener.java @@ -35,10 +35,13 @@ import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +// TODO: AbstractTaskListener is empty now, but there are dependencies on that e.g. Ruby Runtime - JENKINS-48116) +// The change needs API deprecation policy or external usages cleanup. + /** * {@link TaskListener} which sends messages to a {@link Logger}. */ -public class LogTaskListener implements TaskListener, Closeable { +public class LogTaskListener extends AbstractTaskListener implements TaskListener, Closeable { // would be simpler to delegate to the LogOutputStream but this would incompatibly change the serial form private final TaskListener delegate; diff --git a/core/src/main/java/hudson/util/StreamTaskListener.java b/core/src/main/java/hudson/util/StreamTaskListener.java index d0614f0d30..bd2b6dc87e 100644 --- a/core/src/main/java/hudson/util/StreamTaskListener.java +++ b/core/src/main/java/hudson/util/StreamTaskListener.java @@ -44,6 +44,9 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.kohsuke.stapler.framework.io.WriterOutputStream; +// TODO: AbstractTaskListener is empty now, but there are dependencies on that e.g. Ruby Runtime - JENKINS-48116) +// The change needs API deprecation policy or external usages cleanup. + /** * {@link TaskListener} that generates output into a single stream. * @@ -52,7 +55,7 @@ import org.kohsuke.stapler.framework.io.WriterOutputStream; * * @author Kohsuke Kawaguchi */ -public class StreamTaskListener implements TaskListener, Closeable { +public class StreamTaskListener extends AbstractTaskListener implements TaskListener, Closeable { private PrintStream out; private Charset charset; -- GitLab From af633651496b49ca0a967c37e4b4f1f9a7438013 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Tue, 21 Nov 2017 11:50:04 +0100 Subject: [PATCH 0045/1380] Fix wrong log parameter index --- core/src/main/java/hudson/util/AtomicFileWriter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 9992b836d7..7efc608ae6 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -139,7 +139,7 @@ public class AtomicFileWriter extends Writer { Files.move(tmpPath, destPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e1) { e1.addSuppressed(e); - LOGGER.log(Level.SEVERE, "Unable to move {0} to {1}. Attempting to delete {1} and abandoning.", + LOGGER.log(Level.SEVERE, "Unable to move {0} to {1}. Attempting to delete {0} and abandoning.", new Path[]{tmpPath, destPath}); try { Files.deleteIfExists(tmpPath); -- GitLab From ece2e39c125e2e04e1821feaee3504689138afed Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Tue, 21 Nov 2017 12:04:24 +0100 Subject: [PATCH 0046/1380] Wraps InvalidPathException in IOException if thrown To keep backward compatibility and avoid potentially killing threads that would be using this code. --- .../java/hudson/util/AtomicFileWriter.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 7efc608ae6..a643e61938 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -24,12 +24,14 @@ package hudson.util; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; @@ -66,8 +68,24 @@ public class AtomicFileWriter extends Writer { * @deprecated Use {@link #AtomicFileWriter(Path, Charset)} */ @Deprecated - public AtomicFileWriter(@Nonnull File f, String encoding) throws IOException { - this(f.toPath(), encoding == null ? Charset.defaultCharset() : Charset.forName(encoding)); + public AtomicFileWriter(@Nonnull File f, @Nullable String encoding) throws IOException { + this(toPath(f), encoding == null ? Charset.defaultCharset() : Charset.forName(encoding)); + } + + /** + * Wraps potential {@link java.nio.file.InvalidPathException} thrown by {@link File#toPath()} in an + * {@link IOException} for backward compatibility. + * + * @param file + * @return the path for that file + * @see File#toPath() + */ + private static Path toPath(@Nonnull File file) throws IOException { + try { + return file.toPath(); + } catch (InvalidPathException e) { + throw new IOException(e); + } } /** -- GitLab From ce58dd057257ce155187c8ae32a31a5de35f1b61 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Tue, 21 Nov 2017 12:08:23 +0100 Subject: [PATCH 0047/1380] Moar diagnostics in case atomic move fails --- core/src/main/java/hudson/util/AtomicFileWriter.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index a643e61938..ea741941d2 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -30,6 +30,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.Writer; import java.nio.charset.Charset; +import java.nio.file.AtomicMoveNotSupportedException; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; @@ -148,7 +149,12 @@ public class AtomicFileWriter extends Writer { } catch (IOException e) { // If it falls here that can mean many things. Either that the atomic move is not supported, // or something wrong happened. Anyway, let's try to be over-diagnosing - LOGGER.log(Level.WARNING, "Unable to move atomically, falling back to non-atomic move."); + if (e instanceof AtomicMoveNotSupportedException) { + LOGGER.log(Level.WARNING, "Atomic move not supported. falling back to non-atomic move."); + } else { + LOGGER.log(Level.WARNING, "Unable to move atomically, falling back to non-atomic move."); + } + if (destPath.toFile().exists()) { LOGGER.log(Level.WARNING, "The target file {0} was already existing?!?", destPath); } -- GitLab From b2c40cb9e0db72c978b3a50be0d4c467cb33eb20 Mon Sep 17 00:00:00 2001 From: Alexander Akbashev Date: Tue, 21 Nov 2017 14:18:01 +0100 Subject: [PATCH 0048/1380] Cache permission id to avoid allocating of new strings Every request that comes from Jelly is checked against Permissions. As result it leads to a call of `getId` method that produces the new string. Usually it's not a problem, but in case of stop-the-world pause user requests are accumulated. So, once pause is finished, we forcibly allocated tons of strings for every request. That leads to new stop-the-world pause. (And this cycle can repeat multiple times) --- core/src/main/java/hudson/security/Permission.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/security/Permission.java b/core/src/main/java/hudson/security/Permission.java index 431ad0fd1d..31f6a944e2 100644 --- a/core/src/main/java/hudson/security/Permission.java +++ b/core/src/main/java/hudson/security/Permission.java @@ -68,6 +68,9 @@ public final class Permission { public final @Nonnull PermissionGroup group; + // if some plugin serialized old version of this class using XStream, `id` can be null + private final @CheckForNull String id; + /** * Human readable ID of the permission. * @@ -158,6 +161,7 @@ public final class Permission { this.impliedBy = impliedBy; this.enabled = enable; this.scopes = ImmutableSet.copyOf(scopes); + this.id = owner.getName() + '.' + name; group.add(this); ALL.add(this); @@ -222,7 +226,10 @@ public final class Permission { * @see #fromId(String) */ public @Nonnull String getId() { - return owner.getName()+'.'+name; + if (id == null) { + return owner.getName() + '.' + name; + } + return id; } @Override public boolean equals(Object o) { -- GitLab From 7db8e7f0ffa087506295e26e7ac8f9e51dff1601 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 22 Nov 2017 10:42:52 +0100 Subject: [PATCH 0049/1380] Disable SetupWizardTest#shouldDisableUnencrypterProtocolsByDefault() I cannot reproduce the issue locally, but it happens in CI (due to the parallel tests?). I I would prefer to just disable it for now. --- test/src/test/java/jenkins/AgentProtocolTest.java | 5 +++-- .../test/java/jenkins/install/SetupWizardTest.java | 11 +++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/src/test/java/jenkins/AgentProtocolTest.java b/test/src/test/java/jenkins/AgentProtocolTest.java index 55e0f95fb4..7dcfa0b5e1 100644 --- a/test/src/test/java/jenkins/AgentProtocolTest.java +++ b/test/src/test/java/jenkins/AgentProtocolTest.java @@ -132,9 +132,10 @@ public class AgentProtocolTest { } } - public static void assertMonitorNotActive() { + public static void assertMonitorNotActive(JenkinsRule j) { DeprecatedAgentProtocolMonitor monitor = new DeprecatedAgentProtocolMonitor(); - assertFalse("Deprecated Agent Protocol Monitor should not be activated", monitor.isActivated()); + assertFalse("Deprecated Agent Protocol Monitor should not be activated. Current protocols: " + + StringUtils.join(j.jenkins.getAgentProtocols(), ","), monitor.isActivated()); } public static void assertMonitorTriggered(String ... expectedProtocols) { diff --git a/test/src/test/java/jenkins/install/SetupWizardTest.java b/test/src/test/java/jenkins/install/SetupWizardTest.java index d492ee1903..7b57dd3bf7 100644 --- a/test/src/test/java/jenkins/install/SetupWizardTest.java +++ b/test/src/test/java/jenkins/install/SetupWizardTest.java @@ -32,12 +32,15 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; +import java.util.Arrays; +import java.util.List; import java.util.Set; import jenkins.AgentProtocolTest; import jenkins.slaves.DeprecatedAgentProtocolMonitor; import org.apache.commons.io.FileUtils; import static org.hamcrest.Matchers.*; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -114,16 +117,20 @@ public class SetupWizardTest { wc.assertFails("setupWizard/createAdminUser", 403); wc.assertFails("setupWizard/completeInstall", 403); } - + + //TODO: The test randomly fails on Jenkins CI + // Oleg Nenashev: I am not able to reproduce it @Test @Issue("JENKINS-45841") + @Ignore public void shouldDisableUnencryptedProtocolsByDefault() throws Exception { AgentProtocolTest.assertProtocols(j.jenkins, true, "Encrypted JNLP4-protocols protocol should be enabled", "JNLP4-connect"); AgentProtocolTest.assertProtocols(j.jenkins, false, "Non-encrypted JNLP protocols should be disabled by default", "JNLP-connect", "JNLP2-connect", "CLI-connect"); - AgentProtocolTest.assertMonitorNotActive(); + // The CI test fails here, presumably due to the CLI protocols. + AgentProtocolTest.assertMonitorNotActive(j); } private String jsonRequest(JenkinsRule.WebClient wc, String path) throws Exception { -- GitLab From 823b51f0baf35de539886f4259a482e33684b19a Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Sun, 12 Nov 2017 09:45:02 -0500 Subject: [PATCH 0050/1380] [JENKINS-47448] Make JDKInstaller work for old login site as well (#3136) * Fix JDKInstaller to work with old and new Oracle login flow * Update Javadoc to explain that both login flows are supported (cherry picked from commit 5c1fd7d60351dfbade6f76e8811c636b3d92f055) --- core/src/main/java/hudson/tools/JDKInstaller.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/tools/JDKInstaller.java b/core/src/main/java/hudson/tools/JDKInstaller.java index 6b4eaa98a3..ce4f9f2eeb 100644 --- a/core/src/main/java/hudson/tools/JDKInstaller.java +++ b/core/src/main/java/hudson/tools/JDKInstaller.java @@ -477,7 +477,12 @@ public class JDKInstaller extends ToolInstaller { throw new IOException("Failed to request " + m.getURI() +" exit code="+r); if (m.getURI().getHost().equals("login.oracle.com")) { - /* Login flow: + /* Oracle switched from old to new, and then back to old. This code should work for either. + * Old Login flow: + * 1. /mysso/signon.jsp: Form for username + password: Submit actions is: + * 2. /oam/server/sso/auth_cred_submit: Returns a 302 to: + * 3. https://edelivery.oracle.com/osso_login_success: Returns a 302 to the download. + * New Login flow: * 1. /oaam_server/oamLoginPage.jsp: Form for username + password. Submit action is: * 2. /oaam_server/login.do: Returns a 302 to: * 3. /oaam_server/loginAuth.do: After 2 seconds, JS sets window.location to: @@ -485,9 +490,9 @@ public class JDKInstaller extends ToolInstaller { * 5. /oam/server/dap/cred_submit: Returns a 302 to: * 6. https://edelivery.oracle.com/osso_login_success: Returns a 302 to the download. */ - if (m.getURI().getPath().contains("/loginAuth.do")) { // You are redirected to this page immediately after logging in. + if (m.getURI().getPath().contains("/loginAuth.do")) { try { - Thread.sleep(2000); // Oracle website waits 2 seconds after logging in before redirecting. + Thread.sleep(2000); m.releaseConnection(); m = new GetMethod(new URI(m.getURI(), "/oaam_server/authJump.do?jump=false", true).toString()); continue; @@ -523,9 +528,9 @@ public class JDKInstaller extends ToolInstaller { String n = extractAttribute(fragment,"name"); String v = extractAttribute(fragment,"value"); if (n==null || v==null) continue; - if (n.equals("userid")) + if (n.equals("userid") || n.equals("ssousername")) v = u; - if (n.equals("pass")) { + if (n.equals("pass") || n.equals("password")) { v = p.getPlainText(); if (authCount++ > 3) { log.hyperlink(getCredentialPageUrl(),"Your Oracle account doesn't appear valid. Please specify a valid username/password\n"); -- GitLab From 179ac8df0803375e4e600b7c44547419636d44fe Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Fri, 10 Nov 2017 17:42:39 +0100 Subject: [PATCH 0051/1380] [JENKINS-43852] add caching options for captcha (#3126) * JENKINS-42852 add caching options for captcha * - remove whitespace * - correct unit tests * - copy/paste is bad * - second edit... (cherry picked from commit 80d17f5a68739eafc27d5a3fd003b7eee4cfe8f3) --- .../java/hudson/security/SecurityRealm.java | 5 +- .../hudson/security/SecurityRealmTest.java | 82 +++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/hudson/security/SecurityRealmTest.java diff --git a/core/src/main/java/hudson/security/SecurityRealm.java b/core/src/main/java/hudson/security/SecurityRealm.java index 6cb69906e0..d2d637911a 100644 --- a/core/src/main/java/hudson/security/SecurityRealm.java +++ b/core/src/main/java/hudson/security/SecurityRealm.java @@ -402,7 +402,10 @@ public abstract class SecurityRealm extends AbstractDescribableImpl Date: Fri, 10 Nov 2017 19:06:16 +0100 Subject: [PATCH 0052/1380] Update Remoting from 3.13. to 3.14 Fixes JENKINS-45294, JENKINS-47425, JENKINS-47901, JENKINS-47942 + about 50 reported FindBugs issues (JENKINS-37566). There are 13 FindBugs issues left, work in progress. (cherry picked from commit bcd0b2c72bf8c55b7d4997e142954c149b7d4faa) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index be96e24162..4beccc327a 100644 --- a/pom.xml +++ b/pom.xml @@ -170,7 +170,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.13 + 3.14 -- GitLab From 1396c7f774e765f743ab0ffce7bc39bfac76d357 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Wed, 22 Nov 2017 21:22:16 +0100 Subject: [PATCH 0053/1380] Add an if to avoid crashing on symlinks to dirs Files.createDirectories(dir) is said to not fail if directory already exists. *But* this will still fail if `dir` is actually a symlink to a directory... This is documented in the Javadoc indeed, even if probably not really one would expect... See https://bugs.openjdk.java.net/browse/JDK-8130464 Without this fix: ``` [INFO] Running hudson.util.SecretRewriterTest Cycle detected: /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/c/symlink Scanning /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/c/foo.xml Rewritten /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/c/foo.xml Rewritten /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/c/d/foo.xml Rewritten /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/c/d/e/foo.xml ERROR: Failed to rewrite /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/a/symlink/foo.xml java.nio.file.FileAlreadyExistsException: /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/a/symlink at sun.nio.fs.UnixException.translateToIOException(UnixException.java:88) at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102) at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107) at sun.nio.fs.UnixFileSystemProvider.createDirectory(UnixFileSystemProvider.java:384) at java.nio.file.Files.createDirectory(Files.java:674) at java.nio.file.Files.createAndCheckIsDirectory(Files.java:781) at java.nio.file.Files.createDirectories(Files.java:727) at hudson.util.AtomicFileWriter.(AtomicFileWriter.java:103) Caused: java.io.IOException: Failed to create a temporary file in /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/a/symlink at hudson.util.AtomicFileWriter.(AtomicFileWriter.java:106) at hudson.util.AtomicFileWriter.(AtomicFileWriter.java:73) at hudson.util.SecretRewriter.rewrite(SecretRewriter.java:85) at hudson.util.SecretRewriter.rewriteRecursive(SecretRewriter.java:166) at hudson.util.SecretRewriter.rewriteRecursive(SecretRewriter.java:176) at hudson.util.SecretRewriter.rewriteRecursive(SecretRewriter.java:176) at hudson.util.SecretRewriter.rewriteRecursive(SecretRewriter.java:141) at hudson.util.SecretRewriter$rewriteRecursive$0.call(Unknown Source) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133) at hudson.util.SecretRewriterTest.recursionDetection(SecretRewriterTest.groovy:110) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48) at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48) at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48) at org.junit.rules.RunRules.evaluate(RunRules.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:365) at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:272) at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:236) at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:159) at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:386) at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:323) at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:143) Rewritten /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/a/foo.xml Cycle detected: /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/b/symlink Rewritten /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t/b/foo.xml [ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 1.097 s <<< FAILURE! - in hudson.util.SecretRewriterTest [ERROR] recursionDetection(hudson.util.SecretRewriterTest) Time elapsed: 0.176 s <<< FAILURE! Assertion failed: assert 6==sw.rewriteRecursive(t, st) | | | | | | | 5 | hudson.util.StreamTaskListener@34a2d6e0 | | /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t | hudson.util.SecretRewriter@2525a5b8 false at org.codehaus.groovy.runtime.InvokerHelper.assertFailed(InvokerHelper.java:404) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.assertFailed(ScriptBytecodeAdapter.java:650) at hudson.util.SecretRewriterTest.recursionDetection(SecretRewriterTest.groovy:110) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48) at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48) at org.junit.rules.ExternalResource$1.evaluate(ExternalResource.java:48) at org.junit.rules.RunRules.evaluate(RunRules.java:20) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:365) at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:272) at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:236) at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:159) at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:386) at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:323) at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:143) [INFO] [INFO] Results: [INFO] [ERROR] Failures: [ERROR] SecretRewriterTest.recursionDetection:110 assert 6==sw.rewriteRecursive(t, st) | | | | | | | 5 | hudson.util.StreamTaskListener@34a2d6e0 | | /home/tiste/dev/JENKINS/jenkins/core/target/junit7537647214741745549/t | hudson.util.SecretRewriter@2525a5b8 false [INFO] [ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0 ``` --- core/src/main/java/hudson/util/AtomicFileWriter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index ea741941d2..be3f1fca92 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -100,7 +100,9 @@ public class AtomicFileWriter extends Writer { this.destPath = destinationPath; Path dir = this.destPath.getParent(); try { - Files.createDirectories(dir); + if (Files.notExists(dir)) { // This is required for dir symlink handling, see JDK-8130464 + Files.createDirectories(dir); + } tmpPath = Files.createTempFile(dir, "atomic", "tmp"); } catch (IOException e) { throw new IOException("Failed to create a temporary file in "+ dir,e); -- GitLab From 00ccd23f6441a55bfd625660911d4bc79c7578ba Mon Sep 17 00:00:00 2001 From: Alexander Akbashev Date: Wed, 22 Nov 2017 09:34:57 +0100 Subject: [PATCH 0054/1380] If task can be run only on master, use shortcut Currently Jenkins to fire WorkflowJob re-calculates the ConstistentHash for entire cluster even if there is no nodes were updated. If cluster is big enough (>100 nodes), it becomes a problem, because MD5 is quite expensive itself plus all this logic comes with high memory footprint. Using the knowledge that WorkflowJob can be executed only on Jenkins master, we can create a shortcut that does not do expensive calculation and just returns Runnable or `null`. --- core/src/main/java/hudson/model/Queue.java | 34 +++++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index ece3da76cf..bdec3a3f15 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -1691,6 +1691,17 @@ public class Queue extends ResourceController implements Saveable { //we double check if this is a flyweight task if (p.task instanceof FlyweightTask) { Jenkins h = Jenkins.getInstance(); + + Label lbl = p.getAssignedLabel(); + + if (lbl != null && lbl.equals(h.getSelfLabel())) { + if (h.canTake(p) == null) { + return createFlyWeightTaskRunnable(p, h.toComputer()); + } else { + return null; + } + } + Map hashSource = new HashMap(h.getNodes().size()); // Even if master is configured with zero executors, we may need to run a flyweight task like MatrixProject on it. @@ -1703,7 +1714,6 @@ public class Queue extends ResourceController implements Saveable { ConsistentHash hash = new ConsistentHash(NODE_HASH); hash.addAll(hashSource); - Label lbl = p.getAssignedLabel(); String fullDisplayName = p.task.getFullDisplayName(); for (Node n : hash.list(fullDisplayName)) { final Computer c = n.toComputer(); @@ -1717,18 +1727,25 @@ public class Queue extends ResourceController implements Saveable { continue; } - LOGGER.log(Level.FINEST, "Creating flyweight task {0} for computer {1}", new Object[]{fullDisplayName, c.getName()}); - return new Runnable() { - @Override public void run() { - c.startFlyWeightTask(new WorkUnitContext(p).createWorkUnit(p.task)); - makePending(p); - } - }; + return createFlyWeightTaskRunnable(p, c); } } return null; } + private Runnable createFlyWeightTaskRunnable(final BuildableItem p, final Computer c) { + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.log(Level.FINEST, "Creating flyweight task {0} for computer {1}", + new Object[]{p.task.getFullDisplayName(), c.getName()}); + } + return new Runnable() { + @Override public void run() { + c.startFlyWeightTask(new WorkUnitContext(p).createWorkUnit(p.task)); + makePending(p); + } + }; + } + private static Hash NODE_HASH = new Hash() { public String hash(Node node) { return node.getNodeName(); @@ -2104,6 +2121,7 @@ public class Queue extends ResourceController implements Saveable { *

* This code takes {@link LabelAssignmentAction} into account, then fall back to {@link SubTask#getAssignedLabel()} */ + @CheckForNull public Label getAssignedLabel() { for (LabelAssignmentAction laa : getActions(LabelAssignmentAction.class)) { Label l = laa.getAssignedLabel(task); -- GitLab From e50d4d71c387d53bd2e6dfbac99fbf6c2818360b Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Thu, 23 Nov 2017 11:17:13 +0100 Subject: [PATCH 0055/1380] Retry assertion for 10 seconds before failing Also reduce the time 250 ms (instead of 1 second previously) between retries to hopefully reduce the test duration on quick enough setups, and still degrade nicely on less performing ones. --- test/pom.xml | 6 ++++ .../java/hudson/cli/QuietDownCommandTest.java | 32 +++++++++++++------ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/test/pom.xml b/test/pom.xml index 50b119e317..f4e16bb6d8 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -198,6 +198,12 @@ THE SOFTWARE. 4.0 test + + org.awaitility + awaitility + 3.0.0 + test + diff --git a/test/src/test/java/hudson/cli/QuietDownCommandTest.java b/test/src/test/java/hudson/cli/QuietDownCommandTest.java index 840031b5da..723f3ee660 100644 --- a/test/src/test/java/hudson/cli/QuietDownCommandTest.java +++ b/test/src/test/java/hudson/cli/QuietDownCommandTest.java @@ -50,6 +50,7 @@ import java.util.concurrent.atomic.AtomicInteger; import static hudson.cli.CLICommandInvoker.Matcher.failedWith; import static hudson.cli.CLICommandInvoker.Matcher.hasNoStandardOutput; import static hudson.cli.CLICommandInvoker.Matcher.succeededSilently; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -213,7 +214,6 @@ public class QuietDownCommandTest { try { threadPool.submit(exec_task); beforeCli.block(); - Thread.sleep(1000); // Left a room for calling CLI assertJenkinsInQuietMode(); exec_task.get(10, TimeUnit.SECONDS); } catch (TimeoutException e) { @@ -259,7 +259,6 @@ public class QuietDownCommandTest { try { threadPool.submit(exec_task); beforeCli.block(); - Thread.sleep(1000); // Left a room for calling CLI assertJenkinsInQuietMode(); exec_task.get(10, TimeUnit.SECONDS); } catch (TimeoutException e) { @@ -307,7 +306,6 @@ public class QuietDownCommandTest { }); threadPool.submit(exec_task); beforeCli.block(); - Thread.sleep(1000); // Left a room for calling CLI assertJenkinsInQuietMode(); try { exec_task.get(2*TIMEOUT, TimeUnit.MILLISECONDS); @@ -351,7 +349,6 @@ public class QuietDownCommandTest { }); threadPool.submit(exec_task); beforeCli.block(); - Thread.sleep(1000); // Left a room for calling CLI assertJenkinsInQuietMode(); final boolean timeout_occured = false; try { @@ -400,7 +397,6 @@ public class QuietDownCommandTest { }); threadPool.submit(exec_task); beforeCli.block(); - Thread.sleep(1000); // Left a room for calling CLI assertJenkinsInQuietMode(); finish.signal(); @@ -427,7 +423,6 @@ public class QuietDownCommandTest { final Future build = OnlineNodeCommandTest.startBlockingAndFinishingBuild(project, finish); assertThat(((FreeStyleProject) j.jenkins.getItem("aProject")).getBuilds(), hasSize(1)); - boolean timeoutOccurred = false; final FutureTask exec_task = new FutureTask(new Callable() { public Object call() { assertJenkinsNotInQuietMode(); @@ -445,7 +440,6 @@ public class QuietDownCommandTest { }); threadPool.submit(exec_task); beforeCli.block(); - Thread.sleep(1000); // Left a room for calling CLI assertJenkinsInQuietMode(); finish.signal(); @@ -457,19 +451,39 @@ public class QuietDownCommandTest { exec_task.get(1, TimeUnit.SECONDS); } + /** + * Asserts if Jenkins is in quiet mode. + * Will retry for some time before actually failing. + */ private final void assertJenkinsInQuietMode() { assertJenkinsInQuietMode(j); } + /** + * Asserts if Jenkins is not in quiet mode. + * Will retry for some time before actually failing. + */ private final void assertJenkinsNotInQuietMode() { assertJenkinsNotInQuietMode(j); } + /** + * Asserts if Jenkins is in quiet mode, retrying for some time before failing. + * @throws TimeoutException + */ public static final void assertJenkinsInQuietMode(final JenkinsRule j) { - assertThat(j.jenkins.getActiveInstance().getQueue().isBlockedByShutdown(task), equalTo(true)); + await().pollInterval(250, TimeUnit.MILLISECONDS) + .atMost(10, TimeUnit.SECONDS) + .until(() -> j.jenkins.getActiveInstance().getQueue().isBlockedByShutdown(task)); } + /** + * Asserts if Jenkins is not in quiet mode, retrying for some time before failing. + * @throws TimeoutException + */ public static final void assertJenkinsNotInQuietMode(final JenkinsRule j) { - assertThat(j.jenkins.getActiveInstance().getQueue().isBlockedByShutdown(task), equalTo(false)); + await().pollInterval(250, TimeUnit.MILLISECONDS) + .atMost(10, TimeUnit.SECONDS) + .until(() -> !j.jenkins.getActiveInstance().getQueue().isBlockedByShutdown(task)); } } -- GitLab From fed7f2e35cf0edb661661f16689ce492be50cea0 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Thu, 23 Nov 2017 14:34:37 +0100 Subject: [PATCH 0056/1380] Fix RequireUpperBoundDeps issue ``` WARNING] Rule 3: org.apache.maven.plugins.enforcer.RequireUpperBoundDeps fai Failed while enforcing RequireUpperBoundDeps. The error(s) are [ Require upper bound dependencies error for org.objenesis:objenesis:2.1 paths +-org.jenkins-ci.main:test:2.92-SNAPSHOT +-org.mockito:mockito-core:1.10.19 +-org.objenesis:objenesis:2.1 and +-org.jenkins-ci.main:test:2.92-SNAPSHOT +-org.awaitility:awaitility:3.0.0 +-org.objenesis:objenesis:2.5.1 ``` --- test/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/pom.xml b/test/pom.xml index f4e16bb6d8..66b6e520cb 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -204,6 +204,12 @@ THE SOFTWARE. 3.0.0 test + + org.objenesis + objenesis + 2.5.1 + test + -- GitLab From 9b482009214b083bd70fe23bf0bf99ddd5dbe874 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Thu, 23 Nov 2017 15:10:36 +0100 Subject: [PATCH 0057/1380] Try to get the future for 10 seconds before timing out Should make the test more robust on variously performing setups. --- .../test/java/hudson/cli/QuietDownCommandTest.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/test/src/test/java/hudson/cli/QuietDownCommandTest.java b/test/src/test/java/hudson/cli/QuietDownCommandTest.java index 723f3ee660..c7837abc9d 100644 --- a/test/src/test/java/hudson/cli/QuietDownCommandTest.java +++ b/test/src/test/java/hudson/cli/QuietDownCommandTest.java @@ -405,7 +405,9 @@ public class QuietDownCommandTest { assertThat(project.isBuilding(), equalTo(false)); j.assertBuildStatusSuccess(build); assertJenkinsInQuietMode(); - exec_task.get(1, TimeUnit.SECONDS); + + get(exec_task); + assertJenkinsInQuietMode(); } @@ -448,7 +450,15 @@ public class QuietDownCommandTest { assertThat(project.isBuilding(), equalTo(false)); j.assertBuildStatusSuccess(build); assertJenkinsInQuietMode(); - exec_task.get(1, TimeUnit.SECONDS); + get(exec_task); + } + + /** + * Will try to get the result and retry for some time before failing. + */ + private static void get(FutureTask exec_task) { + await().atMost(10, TimeUnit.SECONDS) + .until(exec_task::isDone); } /** -- GitLab From 4c50c7a9ae3035d94a818fd2e171e1b01b908566 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Thu, 23 Nov 2017 17:36:20 +0100 Subject: [PATCH 0058/1380] Log exception in case of failure I see we enter that catch, and the `else` in a failing test. But I'm somewhat stuck since I miss both the stack, and even at least the exception being thrown and not being an `AtomicMoveNotSupportedException`. It's possibly an `IOException` or some subclass. But still having the stack could help understand why the test fails, and provide more data for diagnosis in the future in production use. ``` === Starting hudson.cli.RunRangeCommandTest 0.007 [id=96] WARNING o.jvnet.hudson.test.JenkinsRule#before: Jenkins.theInstance was not cleared by a previous test, doing that now 0.018 [id=96] INFO o.jvnet.hudson.test.JenkinsRule#createWebServer: Running on http://localhost:45833/jenkins/ 0.037 [id=15] WARNING jenkins.model.Jenkins#cleanUp: This instance is no longer the singleton, ignoring cleanUp() 0.345 [id=96] WARNING hudson.util.AtomicFileWriter#commit: Unable to move atomically, falling back to non-atomic move. 0.345 [id=96] SEVERE hudson.util.AtomicFileWriter#commit: Unable to move /home/jenkins/workspace/Core_jenkins_PR-2548-5VRJ4BBBSEGVOZEA7OVAL2YLTLCBL55RB4LDQIZN56WJZD4M6ZKA/test/target/jenkinsTests.tmp/jenkins333129509690162870test/atomic9184049448662376708tmp to /home/jenkins/workspace/Core_jenkins_PR-2548-5VRJ4BBBSEGVOZEA7OVAL2YLTLCBL55RB4LDQIZN56WJZD4M6ZKA/test/target/jenkinsTests.tmp/jenkins333129509690162870test/secret.key. Attempting to delete /home/jenkins/workspace/Core_jenkins_PR-2548-5VRJ4BBBSEGVOZEA7OVAL2YLTLCBL55RB4LDQIZN56WJZD4M6ZKA/test/target/jenkinsTests.tmp/jenkins333129509690162870test/atomic9184049448662376708tmp and abandoning. ``` --- core/src/main/java/hudson/util/AtomicFileWriter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index be3f1fca92..dca4b374e3 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -152,9 +152,9 @@ public class AtomicFileWriter extends Writer { // If it falls here that can mean many things. Either that the atomic move is not supported, // or something wrong happened. Anyway, let's try to be over-diagnosing if (e instanceof AtomicMoveNotSupportedException) { - LOGGER.log(Level.WARNING, "Atomic move not supported. falling back to non-atomic move."); + LOGGER.log(Level.WARNING, "Atomic move not supported. falling back to non-atomic move.", e); } else { - LOGGER.log(Level.WARNING, "Unable to move atomically, falling back to non-atomic move."); + LOGGER.log(Level.WARNING, "Unable to move atomically, falling back to non-atomic move.", e); } if (destPath.toFile().exists()) { -- GitLab From 125cf9d8f1c1203959951f4435bf47a5da1ea947 Mon Sep 17 00:00:00 2001 From: Gentle Yang Date: Sat, 25 Nov 2017 22:07:19 +0800 Subject: [PATCH 0059/1380] Create Messages_zh_CN.properties --- .../cli/client/Messages_zh_CN.properties | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties diff --git a/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties b/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties new file mode 100644 index 0000000000..d0ed19e420 --- /dev/null +++ b/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties @@ -0,0 +1,35 @@ +# The MIT License +# +# Copyright (c) 2013, Sun Microsystems, Inc., Kohsuke Kawaguchi, Pei-Tang Huang, +# Chunghwa Telecom Co., Ltd., and a number of other of contributers +# +# 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. + +CLI.NoURL=\u6c92\u6709\u6307\u5b9a -s \u53c2\u6570\u6216\u8005 JENKINS_URL \u73af\u5883\u53d8\u91cf\u3002 +CLI.VersionMismatch=\u7248\u672c\u4e0d\u5339\u914d\u3002cli \u65e0\u6cd5\u5728\u6b64 jenkins \u670d\u52a1\u5668\u4e0a\u8fd0\u884c\u3002 +CLI.Usage=Jenkins CLI\n\ + \u4f7f\u7528\u65b9\u5f0f: java -jar jenkins-cli.jar [-s URL] \u6307\u4ee4 [\u9078\u9805...] \u53c2\u6570...\n\ + \u9009\u9879:\n\ + -s URL : \u670d\u52a1\u5668 url %28\u9ed8\u8ba4\u503c\u4e3a JENKINS_URL \u73af\u5883\u53d8\u91cf%29\n\ + -i KEY : \u9a8c\u8bc1\u7528\u7684 SSH \u79c1\u94a5\n\ + -p HOST:PORT : \u5efa\u7acb HTTPS Proxy Tunnel \u7684 HTTP \u4ee3\u7406\u670d\u52a1\u5668\u4e3b\u673a\u53ca\u7aef\u53e3\u3002\u8bf7\u53c2\u8003 https://jenkins.io/redirect/cli-https-proxy-tunnel\n\ + -noCertificateCheck : \u5b8c\u5168\u5ffd\u7565 HTTPS \u8bc1\u4e66\u9a8c\u8bc1\u3002\u8bf7\u8c28\u614e\u4f7f\u7528\n\ + \n\ + \u53ef\u7528\u7684\u547d\u4ee4\u53d6\u51b3\u4e8e\u670d\u52a1\u5668\u3002\u6267\u884c 'help' \u547d\u4ee4\u53ef\u4ee5\u67e5\u770b\u5b8c\u6574\u6e05\u5355\u3002 +CLI.NoSuchFileExists=\u6587\u4ef6\u4e0d\u5b58\u5728: {0} -- GitLab From 211b57e8b002b4da92743dfac3a1b902835e7652 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sun, 26 Nov 2017 15:51:46 +0300 Subject: [PATCH 0060/1380] [JENKINS-48157] - Risk of NPE when migrating MyViewProperty without PrimaryView (#3156) * [JENKINS-48157] - Reproduce the issue in test * [JENKINS-48157] - Annotate and document nullness conditions in MyViewsProperty and ViewGroupMixIn * [FIXED JENKINS-48157] - Prevent NPEs when using public API and when using null primaryViewName * [JENKINS-48157] - Fix typo in Javadoc --- .../java/hudson/model/MyViewsProperty.java | 25 ++++++++++++++--- .../java/hudson/model/ViewGroupMixIn.java | 27 +++++++++++++++---- .../hudson/model/MyViewsPropertyTest.java | 16 +++++++++++ 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/hudson/model/MyViewsProperty.java b/core/src/main/java/hudson/model/MyViewsProperty.java index 68ff6e2513..379b37d15d 100644 --- a/core/src/main/java/hudson/model/MyViewsProperty.java +++ b/core/src/main/java/hudson/model/MyViewsProperty.java @@ -39,6 +39,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import javax.annotation.CheckForNull; import javax.servlet.ServletException; import jenkins.model.Jenkins; @@ -61,6 +62,12 @@ import org.kohsuke.stapler.interceptor.RequirePOST; * @author Tom Huybrechts */ public class MyViewsProperty extends UserProperty implements ModifiableViewGroup, Action, StaplerFallback { + + /** + * Name of the primary view defined by the user. + * {@code null} means that the View is not defined. + */ + @CheckForNull private String primaryViewName; /** @@ -71,12 +78,13 @@ public class MyViewsProperty extends UserProperty implements ModifiableViewGroup private transient ViewGroupMixIn viewGroupMixIn; @DataBoundConstructor - public MyViewsProperty(String primaryViewName) { + public MyViewsProperty(@CheckForNull String primaryViewName) { this.primaryViewName = primaryViewName; + readResolve(); // initialize fields } private MyViewsProperty() { - readResolve(); + this(null); } public Object readResolve() { @@ -88,7 +96,10 @@ public class MyViewsProperty extends UserProperty implements ModifiableViewGroup // preserve the non-empty invariant views.add(new AllView(AllView.DEFAULT_VIEW_NAME, this)); } - primaryViewName = AllView.migrateLegacyPrimaryAllViewLocalizedName(views, primaryViewName); + if (primaryViewName != null) { + // It may happen when the default constructor is invoked + primaryViewName = AllView.migrateLegacyPrimaryAllViewLocalizedName(views, primaryViewName); + } viewGroupMixIn = new ViewGroupMixIn(this) { protected List views() { return views; } @@ -99,11 +110,17 @@ public class MyViewsProperty extends UserProperty implements ModifiableViewGroup return this; } + @CheckForNull public String getPrimaryViewName() { return primaryViewName; } - public void setPrimaryViewName(String primaryViewName) { + /** + * Sets the primary view. + * @param primaryViewName Name of the primary view to be set. + * {@code null} to make the primary view undefined. + */ + public void setPrimaryViewName(@CheckForNull String primaryViewName) { this.primaryViewName = primaryViewName; } diff --git a/core/src/main/java/hudson/model/ViewGroupMixIn.java b/core/src/main/java/hudson/model/ViewGroupMixIn.java index 359f68f1c5..313e07eceb 100644 --- a/core/src/main/java/hudson/model/ViewGroupMixIn.java +++ b/core/src/main/java/hudson/model/ViewGroupMixIn.java @@ -36,6 +36,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; /** * Implements {@link ViewGroup} to be used as a "mix-in". @@ -66,27 +67,40 @@ public abstract class ViewGroupMixIn { private final ViewGroup owner; /** - * Returns all the views. This list must be concurrently iterable. + * Returns all views in the group. This list must be modifiable and concurrently iterable. */ + @Nonnull protected abstract List views(); + + /** + * Gets primary view of the mix-in. + * @return Name of the primary view, {@code null} if there is no primary one defined. + */ + @CheckForNull protected abstract String primaryView(); + + /** + * Sets the primary view. + * @param newName Name of the primary view to be set. + * {@code null} to make the primary view undefined. + */ protected abstract void primaryView(String newName); protected ViewGroupMixIn(ViewGroup owner) { this.owner = owner; } - public void addView(View v) throws IOException { + public void addView(@Nonnull View v) throws IOException { v.owner = owner; views().add(v); owner.save(); } - public boolean canDelete(View view) { + public boolean canDelete(@Nonnull View view) { return !view.isDefault(); // Cannot delete primary view } - public synchronized void deleteView(View view) throws IOException { + public synchronized void deleteView(@Nonnull View view) throws IOException { if (views().size() <= 1) throw new IllegalStateException("Cannot delete last view"); views().remove(view); @@ -101,11 +115,14 @@ public abstract class ViewGroupMixIn { */ @CheckForNull public View getView(@CheckForNull String name) { + if (name == null) { + return null; + } for (View v : views()) { if(v.getViewName().equals(name)) return v; } - if (name != null && !name.equals(primaryView())) { + if (!name.equals(primaryView())) { // Fallback to subview of primary view if it is a ViewGroup View pv = getPrimaryView(); if (pv instanceof ViewGroup) diff --git a/test/src/test/java/hudson/model/MyViewsPropertyTest.java b/test/src/test/java/hudson/model/MyViewsPropertyTest.java index 84d798aa67..eab0579941 100644 --- a/test/src/test/java/hudson/model/MyViewsPropertyTest.java +++ b/test/src/test/java/hudson/model/MyViewsPropertyTest.java @@ -33,6 +33,7 @@ import java.io.IOException; import jenkins.model.Jenkins; import org.acegisecurity.AccessDeniedException; import org.acegisecurity.context.SecurityContextHolder; +import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import static org.junit.Assert.*; /** @@ -288,5 +289,20 @@ public class MyViewsPropertyTest { auth.add(Jenkins.ADMINISTER, "User2"); assertTrue("User User2 should configure permission for user User",property.hasPermission(Permission.CONFIGURE)); } + + @Test + @Issue("JENKINS-48157") + public void shouldNotFailWhenMigratingLegacyViewsWithoutPrimaryOne() throws IOException { + rule.jenkins.setSecurityRealm(rule.createDummySecurityRealm()); + User user = User.get("User"); + + // Emulates creation of a new object with Reflection in User#load() does. + MyViewsProperty property = new MyViewsProperty(null); + user.addProperty(property); + + // At AllView with non-default to invoke NPE path in AllView.migrateLegacyPrimaryAllViewLocalizedName() + property.addView(new AllView("foobar")); + property.readResolve(); + } } -- GitLab From 465f85e34d23addf1a7f90bcd28e39ae14e509ff Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Sun, 26 Nov 2017 18:37:16 +0100 Subject: [PATCH 0061/1380] Do not force SYNC It might be preferrable from a consistency standpoint, but many CLI tests start failing when you force this. So, as the previous behaviour was not forcing the sync, this seems to show there could be unintended behavioural changes in effect. Possibly, we'll want to address this later, but as the main goal here was to make commit() atomic as much as possible, fixing this is probably another story anyway. Note: I was *never* able to reproduce those failures on my machine in a normal env dev. It was only visible in CI. I finally managed to make it reproducible (but still a bit randomly) by using Docker resource constraints, using something like this: * cd to jenkins local clone * `docker run -ti -v m2repo:/root/.m2/repository -v $PWD:/work -v ~/.m2/settings.xml:/root/.m2/settings.xml:ro -v ~/.m2/settings-security.xml:/root/.m2/settings-security.xml:ro --device-write-iops /dev/mapper/fedora-home:40 --device-read-iops /dev/mapper/fedora-home:40 --device-write-bps /dev/mapper/fedora-home:10m --device-read-bps /dev/mapper/fedora-home:10m maven:3.5.2-jdk-8 bash` * cd /work * `mvn clean install -Dtest=WaitNodeOfflineCommandTest,RunRangeCommandTest -Dfindbugs.skip=true -DfailIfNoTests=false -Dskip.npm=true` Failure example: ``` ERROR] Tests run: 6, Failures: 2, Errors: 0, Skipped: 0, Time elapsed: 55.68OfflineCommandTest [ERROR] waitNodeOfflineShouldSucceedOnDisconnectingNode(hudson.cli.WaitNodeOf <<< FAILURE! java.lang.AssertionError: Expected: but: was at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20) at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:8) at hudson.cli.WaitNodeOfflineCommandTest.waitNodeOfflineShouldSucceedndTest.java:128) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorIm at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAc at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(Framew at org.junit.internal.runners.model.ReflectiveCallable.run(Reflective at org.junit.runners.model.FrameworkMethod.invokeExplosively(Framewor at org.junit.internal.runners.statements.InvokeMethod.evaluate(Invoke at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefor at org.jvnet.hudson.test.JenkinsRule$1.evaluate(JenkinsRule.java:542) at org.junit.internal.runners.statements.FailOnTimeout$CallableStatem at org.junit.internal.runners.statements.FailOnTimeout$CallableStatem at java.util.concurrent.FutureTask.run(FutureTask.java:266) at java.lang.Thread.run(Thread.java:748) [ERROR] waitNodeOfflineShouldSucceedOnDisconnectedNode(hudson.cli.WaitNodeOff <<< FAILURE! java.lang.AssertionError ``` --- core/src/main/java/hudson/util/AtomicFileWriter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index dca4b374e3..937ffddefe 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -35,7 +35,6 @@ import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; import java.util.logging.Level; import java.util.logging.Logger; @@ -108,7 +107,7 @@ public class AtomicFileWriter extends Writer { throw new IOException("Failed to create a temporary file in "+ dir,e); } - core = Files.newBufferedWriter(tmpPath, charset, StandardOpenOption.SYNC); + core = Files.newBufferedWriter(tmpPath, charset); } @Override -- GitLab From 35b621324933b782a4c6bec5e110fe09554e8432 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 26 Nov 2017 19:04:19 -0800 Subject: [PATCH 0062/1380] [maven-release-plugin] prepare release jenkins-2.92 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 6b00f47b5f..e430ad0aac 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.92-SNAPSHOT + 2.92 cli diff --git a/core/pom.xml b/core/pom.xml index 86fe7180d7..5aa8e89baa 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.92-SNAPSHOT + 2.92 jenkins-core diff --git a/pom.xml b/pom.xml index c543a8a317..5de9acaa58 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.92-SNAPSHOT + 2.92 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.92 diff --git a/test/pom.xml b/test/pom.xml index 50b119e317..bc04ecda53 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.92-SNAPSHOT + 2.92 test diff --git a/war/pom.xml b/war/pom.xml index a2c8bc18fe..d457b0adc5 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.92-SNAPSHOT + 2.92 jenkins-war -- GitLab From 2e920c7d5ae7e082e2333818822c9731dc42f24a Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 26 Nov 2017 19:04:19 -0800 Subject: [PATCH 0063/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index e430ad0aac..be7db1f9a1 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.92 + 2.93-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 5aa8e89baa..9ce61b96c8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.92 + 2.93-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index 5de9acaa58..4a9ed89841 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.92 + 2.93-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.92 + HEAD diff --git a/test/pom.xml b/test/pom.xml index bc04ecda53..aeefcba5ff 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.92 + 2.93-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index d457b0adc5..94f80d2274 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.92 + 2.93-SNAPSHOT jenkins-war -- GitLab From 8bb8057e8d6ee66217baf9d014bb15b12e2d8258 Mon Sep 17 00:00:00 2001 From: Gentle Yang Date: Mon, 27 Nov 2017 21:33:23 +0800 Subject: [PATCH 0064/1380] update to the newest version --- .../cli/client/Messages_zh_CN.properties | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties b/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties index d0ed19e420..16fa41c48c 100644 --- a/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties +++ b/cli/src/main/resources/hudson/cli/client/Messages_zh_CN.properties @@ -21,15 +21,24 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -CLI.NoURL=\u6c92\u6709\u6307\u5b9a -s \u53c2\u6570\u6216\u8005 JENKINS_URL \u73af\u5883\u53d8\u91cf\u3002 -CLI.VersionMismatch=\u7248\u672c\u4e0d\u5339\u914d\u3002cli \u65e0\u6cd5\u5728\u6b64 jenkins \u670d\u52a1\u5668\u4e0a\u8fd0\u884c\u3002 CLI.Usage=Jenkins CLI\n\ - \u4f7f\u7528\u65b9\u5f0f: java -jar jenkins-cli.jar [-s URL] \u6307\u4ee4 [\u9078\u9805...] \u53c2\u6570...\n\ + \u7528\u6cd5: java -jar jenkins-cli.jar [-s url] \u547d\u4ee4 [\u9009\u9879...] \u53c2\u6570...\n\ \u9009\u9879:\n\ - -s URL : \u670d\u52a1\u5668 url %28\u9ed8\u8ba4\u503c\u4e3a JENKINS_URL \u73af\u5883\u53d8\u91cf%29\n\ - -i KEY : \u9a8c\u8bc1\u7528\u7684 SSH \u79c1\u94a5\n\ - -p HOST:PORT : \u5efa\u7acb HTTPS Proxy Tunnel \u7684 HTTP \u4ee3\u7406\u670d\u52a1\u5668\u4e3b\u673a\u53ca\u7aef\u53e3\u3002\u8bf7\u53c2\u8003 https://jenkins.io/redirect/cli-https-proxy-tunnel\n\ - -noCertificateCheck : \u5b8c\u5168\u5ffd\u7565 HTTPS \u8bc1\u4e66\u9a8c\u8bc1\u3002\u8bf7\u8c28\u614e\u4f7f\u7528\n\ + -s URL : \u670d\u52a1\u5668url \uff08\u9ed8\u8ba4\u662fjenkins_url\u73af\u5883\u53d8\u91cf\uff09\n\ + -http : \u5728http%28s%29\u4e0a\u4f7f\u7528\u539f\u59cb\u7684cli\u534f\u8bae\uff08\u9ed8\u8ba4\u503c%3b \u4e0e -ssh \u548c -remoting \u4e92\u65a5\uff09\n\ + -ssh : \u4f7f\u7528ssh\u534f\u8bae\uff08\u9700\u8981 -user%3b \u670d\u52a1\u5668\u7684ssh\u7aef\u53e3\u5fc5\u987b\u6253\u5f00\uff0c\u4e14\u7528\u6237\u5fc5\u987b\u5df2\u6ce8\u518c\u516c\u94a5\u3002\uff09\n\ + -remoting : \u4f7f\u7528\u4e0d\u63a8\u8350\u7684\u8fdc\u7a0b\u4fe1\u9053\u534f\u8bae \uff08\u5982\u679c\u670d\u52a1\u5668\u4e0a\u662f\u6253\u5f00\u7684\uff1b\u4ec5\u7528\u4e8e\u517c\u5bb9\u9057\u7559\u547d\u4ee4\u6216\u547d\u4ee4\u6a21\u5f0f\uff09\n\ + -i KEY : \u7528\u4e8e\u8ba4\u8bc1\u7684ssh\u79c1\u94a5\u6587\u4ef6\uff08\u4e0e -ssh \u6216 -remoting \u4e00\u8d77\u4f7f\u7528\uff09\n\ + -p HOST:PORT : \u7528\u6237https\u4ee3\u7406\u96a7\u9053\u7684http\u4ee3\u7406\u4e3b\u673a\u548c\u7aef\u53e3\u3002\u53c2\u89c1 https://jenkins.io/redirect/cli-https-proxy-tunnel\n\ + -noCertificateCheck : \u5b8c\u5168\u5ffd\u7565https\u8bc1\u4e66\u8ba4\u8bc1\u3002\u8c28\u614e\u4f7f\u7528\n\ + -noKeyAuth : \u65e0\u9700\u5c1d\u8bd5\u52a0\u8f7dssh\u8ba4\u8bc1\u79c1\u94a5\u3002\u4e0e -i \u76f8\u53cd\n\ + -user : \u6307\u5b9a\u7528\u6237\uff08\u4e0e -ssh \u4e00\u8d77\u4f7f\u7528\uff09\n\ + -strictHostKey : \u8981\u6c42\u9a8c\u8bc1\u4e3b\u673akey\u68c0\u67e5\uff08\u4e0e -ssh \u4e00\u8d77\u4f7f\u7528\uff09\n\ + -logger FINE : \u5141\u8bb8\u5ba2\u6237\u7aef\u8be6\u7ec6\u65e5\u5fd7\u8bb0\u5f55\n\ + -auth [ USER:SECRET | @FILE ] : \u6307\u5b9a\u7528\u6237\u540d\u4e0e\u5bc6\u7801\u6216\u7528\u6237\u540d\u4e0eapi token\uff08\u6216\u8005\u4ece\u6587\u4ef6\u52a0\u8f7d\uff09\uff1b\n\ + \u4e0e -http \u4e00\u8d77\u4f7f\u7528\uff0c\u6216\u8005\u53ea\u5728jnlp\u4ee3\u7406\u7aef\u53e3\u7981\u7528\u65f6\u4e0e -remoting \u4e00\u8d77\u4f7f\u7528\n\ \n\ \u53ef\u7528\u7684\u547d\u4ee4\u53d6\u51b3\u4e8e\u670d\u52a1\u5668\u3002\u6267\u884c 'help' \u547d\u4ee4\u53ef\u4ee5\u67e5\u770b\u5b8c\u6574\u6e05\u5355\u3002 +CLI.NoURL=\u6c92\u6709\u6307\u5b9a -s \u53c2\u6570\u6216\u8005 jenkins_url \u73af\u5883\u53d8\u91cf\u3002 +CLI.VersionMismatch=\u7248\u672c\u4e0d\u5339\u914d\u3002cli \u65e0\u6cd5\u5728\u6b64 jenkins \u670d\u52a1\u5668\u4e0a\u8fd0\u884c\u3002 CLI.NoSuchFileExists=\u6587\u4ef6\u4e0d\u5b58\u5728: {0} -- GitLab From 36cfe89a90b123466f66883381f093036d6459a1 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 27 Nov 2017 13:59:58 -0500 Subject: [PATCH 0065/1380] Updated remoting. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 005debc353..cbbdc1e0df 100644 --- a/pom.xml +++ b/pom.xml @@ -170,7 +170,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.15-20171113.150057-1 + 3.15-20171127.185115-3 -- GitLab From 5f8f42624eb58c475d39e5b0cea9507597f42546 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 28 Nov 2017 04:28:50 -0500 Subject: [PATCH 0066/1380] Introducing ItemGroup.allItems and similar default methods (#3148) * Introducing ItemGroup.allItems and similar default methods. * Do not get me started. --- core/src/main/java/hudson/Functions.java | 4 +- .../main/java/hudson/cli/ListJobsCommand.java | 2 +- .../src/main/java/hudson/model/ItemGroup.java | 39 +++++++++++++++++++ core/src/main/java/hudson/model/ListView.java | 4 +- core/src/main/java/jenkins/model/Jenkins.java | 36 ----------------- .../resources/META-INF/upgrade/Items.hint | 2 + .../java/hudson/cli/ListJobsCommandTest.java | 2 + .../test/java/hudson/model/ListViewTest.java | 2 + .../src/test/java/hudson/model/ItemsTest.java | 8 ++-- 9 files changed, 54 insertions(+), 45 deletions(-) create mode 100644 core/src/main/resources/META-INF/upgrade/Items.hint diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 99e76891f0..9203efd03c 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -1135,7 +1135,7 @@ public class Functions { * @since 1.512 */ public static List getAllTopLevelItems(ItemGroup root) { - return Items.getAllItems(root, TopLevelItem.class); + return root.getAllItems(TopLevelItem.class); } /** @@ -2046,7 +2046,7 @@ public class Functions { @Restricted(NoExternalUse.class) // for cc.xml.jelly public static Collection getCCItems(View v) { if (Stapler.getCurrentRequest().getParameter("recursive") != null) { - return Items.getAllItems(v.getOwner().getItemGroup(), TopLevelItem.class); + return v.getOwner().getItemGroup().getAllItems(TopLevelItem.class); } else { return v.getItems(); } diff --git a/core/src/main/java/hudson/cli/ListJobsCommand.java b/core/src/main/java/hudson/cli/ListJobsCommand.java index 45b9661e28..68c30dd015 100644 --- a/core/src/main/java/hudson/cli/ListJobsCommand.java +++ b/core/src/main/java/hudson/cli/ListJobsCommand.java @@ -66,7 +66,7 @@ public class ListJobsCommand extends CLICommand { // If item group was found use it's jobs. if (item instanceof ModifiableTopLevelItemGroup) { - jobs = Items.getAllItems((ModifiableTopLevelItemGroup) item, TopLevelItem.class); + jobs = ((ModifiableTopLevelItemGroup) item).getAllItems(TopLevelItem.class); } // No view and no item group with the given name found. else { diff --git a/core/src/main/java/hudson/model/ItemGroup.java b/core/src/main/java/hudson/model/ItemGroup.java index 546c9bb712..12e4c19ee4 100644 --- a/core/src/main/java/hudson/model/ItemGroup.java +++ b/core/src/main/java/hudson/model/ItemGroup.java @@ -27,6 +27,7 @@ import hudson.model.listeners.ItemListener; import java.io.IOException; import java.util.Collection; import java.io.File; +import java.util.List; import javax.annotation.CheckForNull; import org.acegisecurity.AccessDeniedException; @@ -88,4 +89,42 @@ public interface ItemGroup extends PersistenceRoot, ModelObject * Internal method. Called by {@link Item}s when they are deleted by users. */ void onDeleted(T item) throws IOException; + + /** + * Gets all the {@link Item}s recursively in the {@link ItemGroup} tree + * and filter them by the given type. + * @since FIXME + */ + default List getAllItems(Class type) { + return Items.getAllItems(this, type); + } + + /** + * Gets all the {@link Item}s unordered, lazily and recursively in the {@link ItemGroup} tree + * and filter them by the given type. + * @since FIXME + */ + default Iterable allItems(Class type) { + return Items.allItems(this, type); + } + + /** + * Gets all the items recursively. + * @since FIXME + */ + default List getAllItems() { + return getAllItems(Item.class); + } + + /** + * Gets all the items unordered, lazily and recursively. + * @since FIXME + */ + default Iterable allItems() { + return allItems(Item.class); + } + + // TODO could delegate to allItems overload taking Authentication, but perhaps more useful to introduce a variant to perform preauth filtering using Predicate and check Item.READ afterwards + // or return a Stream and provide a Predicate public static Items.readable(), and see https://stackoverflow.com/q/22694884/12916 if you are looking for just one result + } diff --git a/core/src/main/java/hudson/model/ListView.java b/core/src/main/java/hudson/model/ListView.java index 229c01dcce..002646e10d 100644 --- a/core/src/main/java/hudson/model/ListView.java +++ b/core/src/main/java/hudson/model/ListView.java @@ -209,7 +209,7 @@ public class ListView extends View implements DirectlyModifiableView { Boolean statusFilter = this.statusFilter; // capture the value to isolate us from concurrent update Iterable candidates; if (recurse) { - candidates = Items.getAllItems(parent, TopLevelItem.class); + candidates = parent.getAllItems(TopLevelItem.class); } else { candidates = parent.getItems(); } @@ -444,7 +444,7 @@ public class ListView extends View implements DirectlyModifiableView { jobNames.clear(); Iterable items; if (recurse) { - items = Items.getAllItems(getOwner().getItemGroup(), TopLevelItem.class); + items = getOwner().getItemGroup().getAllItems(TopLevelItem.class); } else { items = getOwner().getItemGroup().getItems(); } diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 2de6f4ce15..1e61a80d0a 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -1726,42 +1726,6 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve return r; } - /** - * Gets all the {@link Item}s recursively in the {@link ItemGroup} tree - * and filter them by the given type. - */ - public List getAllItems(Class type) { - return Items.getAllItems(this, type); - } - - /** - * Gets all the {@link Item}s unordered, lazily and recursively in the {@link ItemGroup} tree - * and filter them by the given type. - * - * @since 2.37 - */ - public Iterable allItems(Class type) { - return Items.allItems(this, type); - } - - /** - * Gets all the items recursively. - * - * @since 1.402 - */ - public List getAllItems() { - return getAllItems(Item.class); - } - - /** - * Gets all the items unordered, lazily and recursively. - * - * @since 2.37 - */ - public Iterable allItems() { - return allItems(Item.class); - } - /** * Gets a list of simple top-level projects. * @deprecated This method will ignore Maven and matrix projects, as well as projects inside containers such as folders. diff --git a/core/src/main/resources/META-INF/upgrade/Items.hint b/core/src/main/resources/META-INF/upgrade/Items.hint new file mode 100644 index 0000000000..096e970106 --- /dev/null +++ b/core/src/main/resources/META-INF/upgrade/Items.hint @@ -0,0 +1,2 @@ +hudson.model.Items.getAllItems($root, $type) :: $root instanceof hudson.model.ItemGroup && $type instanceof java.lang.Class => $root.getAllItems($type);; +hudson.model.Items.allItems($root, $type) :: $root instanceof hudson.model.ItemGroup && $type instanceof java.lang.Class => $root.allItems($type);; diff --git a/core/src/test/java/hudson/cli/ListJobsCommandTest.java b/core/src/test/java/hudson/cli/ListJobsCommandTest.java index a08935c855..c42eab40ec 100644 --- a/core/src/test/java/hudson/cli/ListJobsCommandTest.java +++ b/core/src/test/java/hudson/cli/ListJobsCommandTest.java @@ -28,6 +28,7 @@ import jenkins.model.ModifiableTopLevelItemGroup; import org.hamcrest.Description; import org.hamcrest.TypeSafeMatcher; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; @@ -90,6 +91,7 @@ public class ListJobsCommandTest { } */ + @Ignore("TODO enable when you figure out why ListJobsCommandTest$1Folder$$EnhancerByMockitoWithCGLIB$$f124784a calls ReturnsEmptyValues, or just use MockFolder and move to the test module with JenkinsRule") @Test public void getAllJobsFromFolders() throws Exception { diff --git a/core/src/test/java/hudson/model/ListViewTest.java b/core/src/test/java/hudson/model/ListViewTest.java index f184aac9a2..aa9ed29a02 100644 --- a/core/src/test/java/hudson/model/ListViewTest.java +++ b/core/src/test/java/hudson/model/ListViewTest.java @@ -12,6 +12,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -24,6 +25,7 @@ public class ListViewTest { private interface ItemGroupOfNonTopLevelItem extends TopLevelItem, ItemGroup {} + @Ignore("TODO I am not smart enough to figure out what PowerMock is actually doing; whatever this was testing, better move to the test module and use JenkinsRule") @Test @PrepareForTest({ListViewColumn.class,Items.class}) public void listItemRecurseWorksWithNonTopLevelItems() throws IOException{ diff --git a/test/src/test/java/hudson/model/ItemsTest.java b/test/src/test/java/hudson/model/ItemsTest.java index 0fd0a0772a..32ec8e6e47 100644 --- a/test/src/test/java/hudson/model/ItemsTest.java +++ b/test/src/test/java/hudson/model/ItemsTest.java @@ -78,8 +78,8 @@ public class ItemsTest { FreeStyleProject sub2alpha = sub2.createProject(FreeStyleProject.class, "alpha"); FreeStyleProject sub2BRAVO = sub2.createProject(FreeStyleProject.class, "BRAVO"); FreeStyleProject sub2charlie = sub2.createProject(FreeStyleProject.class, "charlie"); - assertEquals(Arrays.asList(dp, sub1p, sub1q, sub2ap, sub2alpha, sub2bp, sub2BRAVO, sub2cp, sub2charlie), Items.getAllItems(d, FreeStyleProject.class)); - assertEquals(Arrays.asList(sub2a, sub2ap, sub2alpha, sub2b, sub2bp, sub2BRAVO, sub2c, sub2cp, sub2charlie), Items.getAllItems(sub2, Item.class)); + assertEquals(Arrays.asList(dp, sub1p, sub1q, sub2ap, sub2alpha, sub2bp, sub2BRAVO, sub2cp, sub2charlie), d.getAllItems(FreeStyleProject.class)); + assertEquals(Arrays.asList(sub2a, sub2ap, sub2alpha, sub2b, sub2bp, sub2BRAVO, sub2c, sub2cp, sub2charlie), sub2.getAllItems(Item.class)); } @Issue("JENKINS-40252") @@ -101,9 +101,9 @@ public class ItemsTest { FreeStyleProject sub2alpha = sub2.createProject(FreeStyleProject.class, "alpha"); FreeStyleProject sub2BRAVO = sub2.createProject(FreeStyleProject.class, "BRAVO"); FreeStyleProject sub2charlie = sub2.createProject(FreeStyleProject.class, "charlie"); - assertThat(Items.allItems(d, FreeStyleProject.class), containsInAnyOrder(dp, sub1p, sub1q, sub2ap, sub2alpha, + assertThat(d.allItems(FreeStyleProject.class), containsInAnyOrder(dp, sub1p, sub1q, sub2ap, sub2alpha, sub2bp, sub2BRAVO, sub2cp, sub2charlie)); - assertThat(Items.allItems(sub2, Item.class), containsInAnyOrder((Item)sub2a, sub2ap, sub2alpha, sub2b, sub2bp, + assertThat(sub2.allItems(Item.class), containsInAnyOrder((Item)sub2a, sub2ap, sub2alpha, sub2b, sub2bp, sub2BRAVO, sub2c, sub2cp, sub2charlie)); } -- GitLab From 927192fc00de4b9cbfed9d1008137af2d9f509aa Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Tue, 28 Nov 2017 14:09:48 +0100 Subject: [PATCH 0067/1380] Poor man regression testing --- .../hudson/util/AtomicFileWriterPerfTest.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 test/src/test/java/hudson/util/AtomicFileWriterPerfTest.java diff --git a/test/src/test/java/hudson/util/AtomicFileWriterPerfTest.java b/test/src/test/java/hudson/util/AtomicFileWriterPerfTest.java new file mode 100644 index 0000000000..94d5e6c687 --- /dev/null +++ b/test/src/test/java/hudson/util/AtomicFileWriterPerfTest.java @@ -0,0 +1,34 @@ +package hudson.util; + +import org.junit.ClassRule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +public class AtomicFileWriterPerfTest { + + @ClassRule + public static final JenkinsRule rule = new JenkinsRule(); + + /** + * This test is meant to catch huge regressions in terms of serialization performance. + *

+ *

Some data points to explain the timeout value below:

+ *
    + *
  • On a modern SSD in 2017, it takes less than a second to run.
  • + *
  • Using Docker resource constraints, and setting in read&write to IOPS=40 and BPS=10m (i.e. roughly worse than + * an old 5400 RPM hard disk), this test takes 25 seconds
  • + *
+ *

+ * So using slightly more than the worse value obtained above should avoid making this flaky and still catch + * really bad performance regressions. + */ + @Issue("JENKINS-34855") + @Test(timeout = 30 * 1000L) + public void poorManPerformanceTestBed() throws Exception { + int count = 1000; + while (count-- > 0) { + rule.jenkins.save(); + } + } +} -- GitLab From b0f2c335647893828f8e1ff1e61b140fcfb632f3 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 28 Nov 2017 09:18:12 -0500 Subject: [PATCH 0068/1380] jenkins-test-harness 2.32 --- test/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pom.xml b/test/pom.xml index 88fbcdc45c..50d8df225a 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -53,7 +53,7 @@ THE SOFTWARE. ${project.groupId} jenkins-test-harness - 2.32-20171030.181811-1 + 2.32 test -- GitLab From 04641a3da158cb8b1bd3ac64b9d48eed2aa13084 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Tue, 28 Nov 2017 16:44:10 +0100 Subject: [PATCH 0069/1380] Reduce Log levels Not that critical, but still can keep them as we should generally be on FS that support atomic moves. Hence not enter that catch. If proved otherwise, we'll reconsider adjusting this verbosity. --- core/src/main/java/hudson/util/AtomicFileWriter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 937ffddefe..2f2f8e70e7 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -157,20 +157,20 @@ public class AtomicFileWriter extends Writer { } if (destPath.toFile().exists()) { - LOGGER.log(Level.WARNING, "The target file {0} was already existing?!?", destPath); + LOGGER.log(Level.INFO, "The target file {0} was already existing", destPath); } try { Files.move(tmpPath, destPath, StandardCopyOption.REPLACE_EXISTING); } catch (IOException e1) { e1.addSuppressed(e); - LOGGER.log(Level.SEVERE, "Unable to move {0} to {1}. Attempting to delete {0} and abandoning.", + LOGGER.log(Level.WARNING, "Unable to move {0} to {1}. Attempting to delete {0} and abandoning.", new Path[]{tmpPath, destPath}); try { Files.deleteIfExists(tmpPath); } catch (IOException e2) { e2.addSuppressed(e1); - LOGGER.log(Level.SEVERE, "Unable to delete {0}, good bye then!", tmpPath); + LOGGER.log(Level.WARNING, "Unable to delete {0}, good bye then!", tmpPath); throw e2; } -- GitLab From 4f2e261505557fffbf9982def7f7cf2aa941ef3d Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Tue, 28 Nov 2017 18:08:32 +0100 Subject: [PATCH 0070/1380] Be more defensive if provided destPath is wrong The path could already exist, *but* not be a directory. Nice catch James. Associated tests. --- .../java/hudson/util/AtomicFileWriter.java | 15 +++++-- .../hudson/util/AtomicFileWriterTest.java | 45 +++++++++++++++++-- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 2f2f8e70e7..d63be2648c 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -98,10 +98,19 @@ public class AtomicFileWriter extends Writer { } this.destPath = destinationPath; Path dir = this.destPath.getParent(); - try { - if (Files.notExists(dir)) { // This is required for dir symlink handling, see JDK-8130464 - Files.createDirectories(dir); + + if (Files.exists(dir) && !Files.isDirectory(dir)) { + throw new IOException(dir + " exists and is neither a directory nor a symlink to a directory"); + } + else { + if (Files.isSymbolicLink(dir)) { + LOGGER.log(Level.CONFIG, "{0} is a symlink to a directory", dir); + } else { + Files.createDirectories(dir); // Cannot be called on symlink, so we are pretty defensive... } + } + + try { tmpPath = Files.createTempFile(dir, "atomic", "tmp"); } catch (IOException e) { throw new IOException("Failed to create a temporary file in "+ dir,e); diff --git a/core/src/test/java/hudson/util/AtomicFileWriterTest.java b/core/src/test/java/hudson/util/AtomicFileWriterTest.java index 1a6a6d97cf..716805bd31 100644 --- a/core/src/test/java/hudson/util/AtomicFileWriterTest.java +++ b/core/src/test/java/hudson/util/AtomicFileWriterTest.java @@ -1,6 +1,6 @@ package hudson.util; -import org.junit.After; +import org.hamcrest.core.StringContains; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -10,15 +10,22 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; -import static org.junit.Assert.*; +import static org.hamcrest.core.StringContains.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; public class AtomicFileWriterTest { + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); File af; AtomicFileWriter afw; String expectedContent = "hello world"; - @Rule public TemporaryFolder tmp = new TemporaryFolder(); - @Before public void setUp() throws IOException { @@ -26,6 +33,19 @@ public class AtomicFileWriterTest { afw = new AtomicFileWriter(af.toPath(), Charset.defaultCharset()); } + @Test + public void symlinkToDirectory() throws Exception { + final File folder = tmp.newFolder(); + final File containingSymlink = tmp.newFolder(); + final Path zeSymlink = Files.createSymbolicLink(Paths.get(containingSymlink.getAbsolutePath(), "ze_symlink"), + folder.toPath()); + + + final Path childFileInSymlinkToDir = Paths.get(zeSymlink.toString(), "childFileInSymlinkToDir"); + + new AtomicFileWriter(childFileInSymlinkToDir, Charset.forName("UTF-8")); + } + @Test public void createFile() throws Exception { // Verify the file we created exists @@ -68,4 +88,21 @@ public class AtomicFileWriterTest { // Then assertTrue(Files.notExists(afw.getTemporaryPath())); } + + @Test + public void badPath() throws Exception { + final File newFile = tmp.newFile(); + File parentExistsAndIsAFile = new File(newFile, "badChild"); + + assertTrue(newFile.exists()); + assertFalse(parentExistsAndIsAFile.exists()); + + try { + new AtomicFileWriter(parentExistsAndIsAFile.toPath(), Charset.forName("UTF-8")); + fail("Expected a failure"); + } catch (IOException e) { + assertThat(e.getMessage(), + containsString("exists and is neither a directory nor a symlink to a directory")); + } + } } -- GitLab From 9b4e831c139a32cd53dd42419f7780d8cd7616ac Mon Sep 17 00:00:00 2001 From: Francisco Javier Fernandez Gonzalez Date: Wed, 29 Nov 2017 13:58:02 +0100 Subject: [PATCH 0071/1380] [JENKINS-48080] Shows errors --- war/src/main/js/pluginSetupWizardGui.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/war/src/main/js/pluginSetupWizardGui.js b/war/src/main/js/pluginSetupWizardGui.js index 923f6a3f27..2aac81ed33 100644 --- a/war/src/main/js/pluginSetupWizardGui.js +++ b/war/src/main/js/pluginSetupWizardGui.js @@ -889,16 +889,17 @@ var createPluginSetupWizard = function(appendTarget) { // ignore JSON parsing issues, this may be HTML } // we get 200 OK - var $page = $(data); + var responseText = data.responseText; + var $page = $(responseText); var $errors = $page.find('.error'); if($errors.length > 0) { var $main = $page.find('#main-panel').detach(); if($main.length > 0) { - data = data.replace(/body([^>]*)[>](.|[\r\n])+[<][/]body/,'body$1>'+$main.html()+']*)[>](.|[\r\n])+[<][/]body/,'body$1>'+$main.html()+' Date: Wed, 29 Nov 2017 15:04:52 +0100 Subject: [PATCH 0072/1380] More coverage around AtomicFileWriter behaviour --- .../hudson/util/AtomicFileWriterTest.java | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/hudson/util/AtomicFileWriterTest.java b/core/src/test/java/hudson/util/AtomicFileWriterTest.java index 716805bd31..51bd205cb4 100644 --- a/core/src/test/java/hudson/util/AtomicFileWriterTest.java +++ b/core/src/test/java/hudson/util/AtomicFileWriterTest.java @@ -1,5 +1,6 @@ package hudson.util; +import org.apache.commons.io.FileUtils; import org.hamcrest.core.StringContains; import org.junit.Before; import org.junit.Rule; @@ -21,6 +22,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class AtomicFileWriterTest { + private static final String PREVIOUS = "previous value \n blah"; @Rule public TemporaryFolder tmp = new TemporaryFolder(); File af; @@ -30,6 +32,7 @@ public class AtomicFileWriterTest { @Before public void setUp() throws IOException { af = tmp.newFile(); + FileUtils.writeStringToFile(af, PREVIOUS); afw = new AtomicFileWriter(af.toPath(), Charset.defaultCharset()); } @@ -56,25 +59,29 @@ public class AtomicFileWriterTest { public void writeToAtomicFile() throws Exception { // Given afw.write(expectedContent, 0, expectedContent.length()); + afw.write(expectedContent); + afw.write(' '); // When afw.flush(); // Then assertEquals("File writer did not properly flush to temporary file", - expectedContent.length(), Files.size(afw.getTemporaryPath())); + expectedContent.length()*2+1, Files.size(afw.getTemporaryPath())); } @Test public void commitToFile() throws Exception { // Given afw.write(expectedContent, 0, expectedContent.length()); + afw.write(new char[]{'h', 'e', 'y'}, 0, 3); // When afw.commit(); // Then - assertEquals(expectedContent.length(), Files.size(af.toPath())); + assertEquals(expectedContent.length()+3, Files.size(af.toPath())); + assertEquals(expectedContent+"hey", FileUtils.readFileToString(af)); } @Test @@ -87,8 +94,20 @@ public class AtomicFileWriterTest { // Then assertTrue(Files.notExists(afw.getTemporaryPath())); + assertEquals(PREVIOUS, FileUtils.readFileToString(af)); } + @Test + public void indexOutOfBoundsLeavesOriginalUntouched() throws Exception { + // Given + try { + afw.write(expectedContent, 0, expectedContent.length() + 10); + fail("exception expected"); + } catch (IndexOutOfBoundsException e) { + } + + assertEquals(PREVIOUS, FileUtils.readFileToString(af)); + } @Test public void badPath() throws Exception { final File newFile = tmp.newFile(); -- GitLab From c79683bdd805fb6145fe25b48ba3849a90c243f6 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 29 Nov 2017 14:48:24 -0500 Subject: [PATCH 0073/1380] Rethrow any kind of RuntimeException, such as errors from RobustReflectionConverter. --- core/src/main/java/hudson/XmlFile.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/XmlFile.java b/core/src/main/java/hudson/XmlFile.java index d5077b34db..16644262b9 100644 --- a/core/src/main/java/hudson/XmlFile.java +++ b/core/src/main/java/hudson/XmlFile.java @@ -24,10 +24,8 @@ package hudson; import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.XStreamException; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.io.StreamException; import com.thoughtworks.xstream.io.xml.Xpp3Driver; import hudson.diagnosis.OldDataMonitor; import hudson.model.Descriptor; @@ -148,7 +146,7 @@ public final class XmlFile { } try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()))) { return xs.fromXML(in); - } catch (XStreamException | Error | InvalidPathException e) { + } catch (RuntimeException | Error e) { throw new IOException("Unable to read "+file,e); } } @@ -165,7 +163,7 @@ public final class XmlFile { try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()))) { // TODO: expose XStream the driver from XStream return xs.unmarshal(DEFAULT_DRIVER.createReader(in), o); - } catch (XStreamException | Error | InvalidPathException e) { + } catch (RuntimeException | Error e) { throw new IOException("Unable to read "+file,e); } } @@ -184,7 +182,7 @@ public final class XmlFile { writing.set(null); } w.commit(); - } catch(StreamException e) { + } catch(RuntimeException e) { throw new IOException(e); } finally { w.abort(); -- GitLab From 8058675317d84b51a73e53a9167c6d1660b3314f Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Sun, 12 Nov 2017 16:02:15 +0100 Subject: [PATCH 0074/1380] Merge pull request #3134 from jglick/IdStrategy-NFE-JENKINS-47909 [JENKINS-47909] Handle false hex escapes (cherry picked from commit 7c06a9ba946800151c5d9fc38793ac4bbd3fea5f) --- core/src/main/java/hudson/model/User.java | 5 ++++- .../main/java/jenkins/model/IdStrategy.java | 12 +++++++++-- .../java/jenkins/model/IdStrategyTest.java | 1 + test/src/test/java/hudson/model/UserTest.java | 19 ++++++++++++++++++ .../model/UserTest/shellyUsernameMigrated.zip | Bin 0 -> 2881 bytes 5 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 test/src/test/resources/hudson/model/UserTest/shellyUsernameMigrated.zip diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index e8895d4baf..6b6bc612eb 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -453,7 +453,9 @@ public class User extends AbstractModelObject implements AccessControlled, Descr if (o instanceof User) { if (idStrategy().equals(id, legacyUserDir.getName()) && !idStrategy().filenameOf(legacyUserDir.getName()) .equals(legacyUserDir.getName())) { - if (!legacyUserDir.renameTo(configFile.getParentFile())) { + if (legacyUserDir.renameTo(configFile.getParentFile())) { + LOGGER.log(Level.INFO, "Migrated user record from {0} to {1}", new Object[] {legacyUserDir, configFile.getParentFile()}); + } else { LOGGER.log(Level.WARNING, "Failed to migrate user record from {0} to {1}", new Object[]{legacyUserDir, configFile.getParentFile()}); } @@ -478,6 +480,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr try { Files.createDirectory(configFile.getParentFile().toPath()); Files.move(unsanitizedLegacyConfigFile.toPath(), configFile.toPath()); + LOGGER.log(Level.INFO, "Migrated unsafe user record from {0} to {1}", new Object[] {unsanitizedLegacyConfigFile, configFile}); } catch (IOException | InvalidPathException e) { LOGGER.log( Level.WARNING, diff --git a/core/src/main/java/jenkins/model/IdStrategy.java b/core/src/main/java/jenkins/model/IdStrategy.java index a9acf97420..f90e892e0b 100644 --- a/core/src/main/java/jenkins/model/IdStrategy.java +++ b/core/src/main/java/jenkins/model/IdStrategy.java @@ -303,7 +303,11 @@ public abstract class IdStrategy extends AbstractDescribableImpl imp } else { break; } - buf.append(Character.valueOf((char)Integer.parseInt(hex.toString(), 16))); + try { + buf.append(Character.valueOf((char)Integer.parseInt(hex.toString(), 16))); + } catch (NumberFormatException x) { + buf.append('$').append(hex); + } } } return buf.toString(); @@ -509,7 +513,11 @@ public abstract class IdStrategy extends AbstractDescribableImpl imp } else { break; } - buf.append(Character.valueOf((char)Integer.parseInt(hex.toString(), 16))); + try { + buf.append(Character.valueOf((char)Integer.parseInt(hex.toString(), 16))); + } catch (NumberFormatException x) { + buf.append('$').append(hex); + } } } return buf.toString(); diff --git a/core/src/test/java/jenkins/model/IdStrategyTest.java b/core/src/test/java/jenkins/model/IdStrategyTest.java index e44cb0466b..70b1599edb 100644 --- a/core/src/test/java/jenkins/model/IdStrategyTest.java +++ b/core/src/test/java/jenkins/model/IdStrategyTest.java @@ -29,6 +29,7 @@ public class IdStrategyTest { assertCaseInsensitiveRoundTrip("NUL", "$006eul"); assertEquals("foo", idStrategy.idFromFilename("~foo")); assertEquals("0123 _-@a", idStrategy.idFromFilename("0123 _-@~a")); + assertEquals("big$money", idStrategy.idFromFilename("big$money")); } @Test diff --git a/test/src/test/java/hudson/model/UserTest.java b/test/src/test/java/hudson/model/UserTest.java index 0c3201b633..b81105fe55 100644 --- a/test/src/test/java/hudson/model/UserTest.java +++ b/test/src/test/java/hudson/model/UserTest.java @@ -813,6 +813,25 @@ public class UserTest { assertThat(empty.getFullName(), equalTo("Empty")); } + @Issue("JENKINS-47909") + @LocalData + @Test + public void shellyUsernameMigrated() { + File rootDir = new File(Jenkins.getInstance().getRootDir(), "users"); + User user = User.getById("bla$phem.us", false); + assertCorrectConfig(user, "users/bla$0024phem.us/config.xml"); + assertFalse(new File(rootDir, "bla$phem.us").exists()); + assertTrue(user.getConfigFile().getFile().exists()); + assertThat(user.getFullName(), equalTo("Weird Username")); + user = User.getById("make\u1000000", false); + assertNotNull("we do not prevent accesses to the phony name, alas", user); + user = User.getById("make$1000000", false); + assertCorrectConfig(user, "users/make$00241000000/config.xml"); + assertFalse(new File(rootDir, "make$1000000").exists()); + assertTrue("but asking for the real name triggers migration", user.getConfigFile().getFile().exists()); + assertThat(user.getFullName(), equalTo("Greedy Fella")); + } + private static void assertCorrectConfig(User user, String unixPath) { assertThat(user.getConfigFile().getFile().getPath(), endsWith(unixPath.replace('/', File.separatorChar))); } diff --git a/test/src/test/resources/hudson/model/UserTest/shellyUsernameMigrated.zip b/test/src/test/resources/hudson/model/UserTest/shellyUsernameMigrated.zip new file mode 100644 index 0000000000000000000000000000000000000000..5d57a6a6a2d7149f20f0f8cfc869ab1e73ce56d8 GIT binary patch literal 2881 zcmaKuc|26z8^Eu9&n`=t8Do2eVUS%>Xb_DdG`7aBL6$6~H>D|Ak};C47e8dF2w5hP z5ZTH)SxUyfyw)O;-^lxx4|?zY-19m2kMq6fJokK`=a?DM(DDEPzzC$exf@kal*hcK z27n%V0AL0V0T^#D7k5{=AnbVy6ca%6CXv==>j?;A0jOvvXaL|x6K&D&H4NhjojU^x zuQSI#GYw_TeaeHX9N=@amm#yhtS`DNU-CY0^Ti7ekhGx;n6q6CuI<+Z4Owk*$1vz} zwW-hc4`mMmxL|};k9+muwceH4o2!4=Orw1H?3D#iBcL`&L%fn?l%aSmM1%i=KF>i# z|CG?0Rk`x&JGF~WoY_ksBpt973${F#Z3(eUJi`l4DhS-^i32@(;w@}O92Rv5w_CO5 zk z8dVG#w`l{im>NK%c5UKa2$psaKeOD%pts-R`XmF>n4iL9mdx*5SNVunYfAjkAagv6 zfKmv7z&xii9@ToHxbpRL$}^@*X5o!s`)mBiW|O#UI?Aq9(^z7haM1iRvM>n;6Ci!* za0rR%3km1#K&8?pUwNZlPHL2`oi{u_uom)n^q=evOJzAt4_AamY6?FqTP^E*Z`}Lj zlfo~>EEA*Bf#gm?h&J4H#WxLKfUO_U;<{mg6GB2&Li)!c28ghS%59m)OwX0v@JEgQ zU0R)!yl4xa2Oo6%%F<0%g^~^qe>UvD*%;Zo=_kyqld3R^n|vVNM)a%^dfd$#FI9<# z^L|W}R%JPKe9kqmrB4~2-eorirdedSd}3M*cA%>*5SUQ94)WKLZ?1sF>qSW^H^0;- zjPa&tUw-XeBwAfrf!NGErbn1E5pQjonH!ihdGK1YkPh8HJ-3*JlD_h=gvDqecYU}asmIaDkvPS`+MBlVr~1m#p)&V3t}jK z#?t{5(!X=r$=YS?7Io{{Wh?<@?UxMWxYGW_0o}d|=5~r3%Dmph@w(00KEkicA7vHA z1uldd&z#!4bYo7;pn1OL%KB_oLBJv(5=)DYz17W(vpeioGtek@-b`kBE{TCq8&Tyd zPF-G=8N>7BE#@}os-dwXI+rYeOaeISH8bAqui;8 zkjxq%pC&FjktSBIId)XZO02j9)!ErRQn}^?diBM*m?}Wqa79f0VfS|{Hvyjn>9yCW zN1%n{b<42Dt5Z4+E40WW^HwRngB{QA>bbj=B4u>r6^D|Hia1IKT*1DA45HS!1=yc|5l1C2bA< zdIEgpc;n~ivdh-^!zDc(vQtDo()nNpcsapJf~Cjz1aD`wRDBV^UN!w4(|>}MDv=nL zw!u@@*`69|7D;E!aZ(?KA;n4N-6gKN(Ax*uiNij=kIWtTfPt7^i>rJJhWJWG?9k#*-0Q=6N(PC3z&7Xp;M0;f&wv>Z0x zO5GpR3r0;IDogPQP<1Pj(aZ5lXM50}_02nm*h{8o32sxs52;14Lg?}wXZ({5czQOq z!4;LBDwYy8&;uT57I5WtN&xkkE4JBKGyzH;~q7A9+fh8PP z(klLw>X!t&CF1_Maj}k`&QJx|kL#CF-}dg;Ulwe^YL0kI9HLn8j1~Zd_lND$U`y0} zumqH?U(!*YP>KcxaBVuJ?2|Kq0GDobwEjY-iM_f|Ij%bZOk^=s-@;5+{v6>#mvP_PMFlmuvDMDE*F;dd34K@F>aEM1)oP92il@w^*I2t}7XscfQ zK@bDEI@p?J$f@WR!}$qhJ8+#{FGQiP&`AC->{P>MCejx?N{D4>slPZ_`NaT9T;~;Qi{;Ncp|+Cs%|2T3y(KXKyCAm4W@bc1Ekg6VqY?j)feH0DuG7IL1RcSN&ULD6Fl$`yZ^mC1xiJ^^>);NLZjK+;8< literal 0 HcmV?d00001 -- GitLab From 7cbc8f55023e233470df5f8f1a86b3a4a44321b2 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 30 Nov 2017 23:00:20 -0500 Subject: [PATCH 0075/1380] Only inspect direct subdirectories for Surefire reports. --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 65dd3a66c7..92fa2cadea 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -58,7 +58,7 @@ for(i = 0; i < buildTypes.size(); i++) { archiveArtifacts artifacts: '**/target/*.jar, **/target/*.war, **/target/*.hpi', fingerprint: true if (runTests) { - junit healthScaleFactor: 20.0, testResults: '**/target/surefire-reports/*.xml' + junit healthScaleFactor: 20.0, testResults: '*/target/surefire-reports/TEST-*.xml' } } } -- GitLab From 8824857752f89ebaddf7a814c65b31b9c155ab2f Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 1 Dec 2017 10:00:49 +0300 Subject: [PATCH 0076/1380] Add default implementations of deprecated methods of BuilableItem and Item. (#3142) * Add default implementations to deprecated methods of BuilableItem and Item. Currently the interface requires the API user to implement already deprecated methods. It does not make much sense, and the API could be simplified. * Address comments from @jglick --- core/src/main/java/hudson/Functions.java | 31 +++++++++++----- .../main/java/hudson/model/AbstractItem.java | 21 +---------- .../main/java/hudson/model/BuildableItem.java | 10 ++++- core/src/main/java/hudson/model/Item.java | 37 ++++++++++++++++--- .../jenkins/model/ParameterizedJobMixIn.java | 18 --------- 5 files changed, 63 insertions(+), 54 deletions(-) diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 9203efd03c..c9c5e8545b 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -125,6 +125,7 @@ import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import java.util.regex.Pattern; +import javax.annotation.Nullable; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -1142,13 +1143,17 @@ public class Functions { * Gets the relative name or display name to the given item from the specified group. * * @since 1.515 - * @param p the Item we want the relative display name - * @param g the ItemGroup used as point of reference for the item + * @param p the Item we want the relative display name. + * If {@code null}, a {@code null} will be returned by the method + * @param g the ItemGroup used as point of reference for the item. + * If the group is not specified, item's path will be used. * @param useDisplayName if true, returns a display name, otherwise returns a name * @return - * String like "foo » bar" + * String like "foo » bar". + * {@code null} if item is null or if one of its parents is not an {@link Item}. */ - public static String getRelativeNameFrom(Item p, ItemGroup g, boolean useDisplayName) { + @Nullable + public static String getRelativeNameFrom(@CheckForNull Item p, @CheckForNull ItemGroup g, boolean useDisplayName) { if (p == null) return null; if (g == null) return useDisplayName ? p.getFullDisplayName() : p.getFullName(); String separationString = useDisplayName ? " » " : "/"; @@ -1182,7 +1187,7 @@ public class Functions { if (gr instanceof Item) i = (Item)gr; - else + else // Parent is a group, but not an item return null; } } @@ -1192,11 +1197,14 @@ public class Functions { * * @since 1.515 * @param p the Item we want the relative display name + * If {@code null}, the method will immediately return {@code null}. * @param g the ItemGroup used as point of reference for the item * @return - * String like "foo/bar" + * String like "foo/bar". + * {@code null} if the item is {@code null} or if one of its parents is not an {@link Item}. */ - public static String getRelativeNameFrom(Item p, ItemGroup g) { + @Nullable + public static String getRelativeNameFrom(@CheckForNull Item p, @CheckForNull ItemGroup g) { return getRelativeNameFrom(p, g, false); } @@ -1205,12 +1213,15 @@ public class Functions { * Gets the relative display name to the given item from the specified group. * * @since 1.512 - * @param p the Item we want the relative display name + * @param p the Item we want the relative display name. + * If {@code null}, the method will immediately return {@code null}. * @param g the ItemGroup used as point of reference for the item * @return - * String like "Foo » Bar" + * String like "Foo » Bar". + * {@code null} if the item is {@code null} or if one of its parents is not an {@link Item}. */ - public static String getRelativeDisplayNameFrom(Item p, ItemGroup g) { + @Nullable + public static String getRelativeDisplayNameFrom(@CheckForNull Item p, @CheckForNull ItemGroup g) { return getRelativeNameFrom(p, g, true); } diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index 01ef3a0bbf..4c026d5f31 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -381,21 +381,6 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet return getRelativeNameFrom(p); } - /** - * @param p - * The ItemGroup instance used as context to evaluate the relative name of this AbstractItem - * @return - * The name of the current item, relative to p. - * Nested ItemGroups are separated by / character. - */ - public String getRelativeNameFrom(ItemGroup p) { - return Functions.getRelativeNameFrom(this, p); - } - - public String getRelativeNameFrom(Item item) { - return getRelativeNameFrom(item.getParent()); - } - /** * Called right after when a {@link Item} is loaded from disk. * This is an opportunity to do a post load processing. @@ -470,12 +455,10 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet return getShortUrl(); } + @Override @Exported(visibility=999,name="url") public final String getAbsoluteUrl() { - String r = Jenkins.getInstance().getRootUrl(); - if(r==null) - throw new IllegalStateException("Root URL isn't configured yet. Cannot compute absolute URL."); - return Util.encode(r+getUrl()); + return Item.super.getAbsoluteUrl(); } /** diff --git a/core/src/main/java/hudson/model/BuildableItem.java b/core/src/main/java/hudson/model/BuildableItem.java index ef845e1175..0d65214e43 100644 --- a/core/src/main/java/hudson/model/BuildableItem.java +++ b/core/src/main/java/hudson/model/BuildableItem.java @@ -40,13 +40,19 @@ public interface BuildableItem extends Item, Task { * Use {@link #scheduleBuild(Cause)}. Since 1.283 */ @Deprecated - boolean scheduleBuild(); + default boolean scheduleBuild() { + return scheduleBuild(new Cause.LegacyCodeCause()); + } + boolean scheduleBuild(Cause c); /** * @deprecated * Use {@link #scheduleBuild(int, Cause)}. Since 1.283 */ @Deprecated - boolean scheduleBuild(int quietPeriod); + default boolean scheduleBuild(int quietPeriod) { + return scheduleBuild(quietPeriod, new Cause.LegacyCodeCause()); + } + boolean scheduleBuild(int quietPeriod, Cause c); } diff --git a/core/src/main/java/hudson/model/Item.java b/core/src/main/java/hudson/model/Item.java index d0c438744d..8187d608bd 100644 --- a/core/src/main/java/hudson/model/Item.java +++ b/core/src/main/java/hudson/model/Item.java @@ -25,9 +25,12 @@ package hudson.model; import hudson.Functions; +import hudson.Util; +import jenkins.model.Jenkins; import jenkins.util.SystemProperties; import hudson.security.PermissionScope; import jenkins.util.io.OnMaster; +import jline.internal.Nullable; import org.kohsuke.stapler.StaplerRequest; import java.io.IOException; @@ -39,6 +42,9 @@ import hudson.security.PermissionGroup; import hudson.security.AccessControlled; import hudson.util.Secret; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + /** * Basic configuration unit in Hudson. * @@ -131,18 +137,32 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont /** * Gets the relative name to this item from the specified group. * + * @param g + * The {@link ItemGroup} instance used as context to evaluate the relative name of this item + * @return + * The name of the current item, relative to p. Nested {@link ItemGroup}s are separated by {@code /} character. * @since 1.419 * @return - * String like "../foo/bar" + * String like "../foo/bar". + * {@code null} if one of item parents is not an {@link Item}. */ - String getRelativeNameFrom(ItemGroup g); + @Nullable + default String getRelativeNameFrom(@CheckForNull ItemGroup g) { + return Functions.getRelativeNameFrom(this, g); + } /** * Short for {@code getRelativeNameFrom(item.getParent())} * + * @return String like "../foo/bar". + * {@code null} if one of item parents is not an {@link Item}. * @since 1.419 */ - String getRelativeNameFrom(Item item); + @Nullable + default String getRelativeNameFrom(@Nonnull Item item) { + return getRelativeNameFrom(item.getParent()); + + } /** * Returns the URL of this item relative to the context root of the application. @@ -180,7 +200,12 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont * (even this won't work for the same reason, which should be fixed.) */ @Deprecated - String getAbsoluteUrl(); + default String getAbsoluteUrl() { + String r = Jenkins.getInstance().getRootUrl(); + if(r==null) + throw new IllegalStateException("Root URL isn't configured yet. Cannot compute absolute URL."); + return Util.encode(r+getUrl()); + } /** * Called right after when a {@link Item} is loaded from disk. @@ -207,7 +232,9 @@ public interface Item extends PersistenceRoot, SearchableModelObject, AccessCont * * @since 1.374 */ - default void onCreatedFromScratch() {} + default void onCreatedFromScratch() { + // do nothing by default + } /** * Save the settings to a file. diff --git a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java index 7cfbc23baf..55301e4f86 100644 --- a/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java +++ b/core/src/main/java/jenkins/model/ParameterizedJobMixIn.java @@ -377,29 +377,11 @@ public abstract class ParameterizedJobMixIn & Param */ Map> getTriggers(); - /** - * @deprecated use {@link #scheduleBuild(Cause)} - */ - @Deprecated - @Override - default boolean scheduleBuild() { - return getParameterizedJobMixIn().scheduleBuild(); - } - @Override default boolean scheduleBuild(Cause c) { return getParameterizedJobMixIn().scheduleBuild(c); } - /** - * @deprecated use {@link #scheduleBuild(int, Cause)} - */ - @Deprecated - @Override - default boolean scheduleBuild(int quietPeriod) { - return getParameterizedJobMixIn().scheduleBuild(quietPeriod); - } - @Override default boolean scheduleBuild(int quietPeriod, Cause c) { return getParameterizedJobMixIn().scheduleBuild(quietPeriod, c); -- GitLab From fdccc0e8384370684e25063e95f4a704773c53dd Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Fri, 1 Dec 2017 02:01:34 -0500 Subject: [PATCH 0077/1380] [JENKINS-36088] Use NIO implementations of chmod and mode by default (#3135) * Use NIO for FilePath#chmod and IOUtils#mode * Add tests for NIO mode and chmod implementations * Add test, remove new method, and update JavaDoc * Provide system property to use native implementations of chmod and mode * Revert unrelated whitespace modification * Don't remove exception from throws and put imports in original location * Fix broken JavaDoc links * Ignore file type bits (above 0o7777) in Util#modeToPermission * Use octal for constants and don't include file type bits * Revert unnecessary changes to TarArchiverTest * Add assertion that non-permission bits are ignored by chmod * Use NIO copy with StandardCopyOption.COPY_ATTRIBUTES in copyToWithPermissions where possible * Catch InvalidPathException and convert it to IOException * Create utility method for File#toPath and use File#createDirectories after review * Remove useless calls to toAbsolutePath and getAbsoluteFile * Fix typos and use octal for constant after review * Add test for behavior of copyToWithPermission with special bits --- core/src/main/java/hudson/FilePath.java | 30 +++++++++- core/src/main/java/hudson/Util.java | 57 +++++++++++++++++++ core/src/main/java/hudson/util/IOUtils.java | 19 ++++++- core/src/test/java/hudson/FilePathTest.java | 53 ++++++++++++++++- core/src/test/java/hudson/UtilTest.java | 39 +++++++++++++ .../java/hudson/util/io/TarArchiverTest.java | 8 +-- 6 files changed, 196 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index ad13edf5f3..197a27f60b 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -79,6 +79,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.InvalidPathException; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -1582,6 +1583,11 @@ public final class FilePath implements Serializable { *

* please note mask is expected to be an octal if you use chmod command line values, * so preceded by a '0' in java notation, ie chmod(0644) + *

+ * Only supports setting read, write, or execute permissions for the + * owner, group, or others, so the largest permissible value is 0777. + * Attempting to set larger values (i.e. the setgid, setuid, or sticky + * bits) will cause an IOException to be thrown. * * @since 1.303 * @see #mode() @@ -1591,7 +1597,6 @@ public final class FilePath implements Serializable { act(new SecureFileCallable() { private static final long serialVersionUID = 1L; public Void invoke(File f, VirtualChannel channel) throws IOException { - // TODO first check for Java 7+ and use PosixFileAttributeView _chmod(writing(f), mask); return null; @@ -1600,14 +1605,18 @@ public final class FilePath implements Serializable { } /** - * Run chmod via jnr-posix + * Change permissions via NIO. */ private static void _chmod(File f, int mask) throws IOException { // TODO WindowsPosix actually does something here (WindowsLibC._wchmod); should we let it? // Anyway the existing calls already skip this method if on Windows. if (File.pathSeparatorChar==';') return; // noop - PosixAPI.jnr().chmod(f.getAbsolutePath(),mask); + if (Util.NATIVE_CHMOD_MODE) { + PosixAPI.jnr().chmod(f.getAbsolutePath(), mask); + } else { + Files.setPosixFilePermissions(Util.fileToPath(f), Util.modeToPermissions(mask)); + } } private static boolean CHMOD_WARNED = false; @@ -2008,6 +2017,21 @@ public final class FilePath implements Serializable { * @since 1.311 */ public void copyToWithPermission(FilePath target) throws IOException, InterruptedException { + // Use NIO copy with StandardCopyOption.COPY_ATTRIBUTES when copying on the same machine. + if (this.channel == target.channel) { + act(new SecureFileCallable() { + public Void invoke(File f, VirtualChannel channel) throws IOException { + File targetFile = new File(target.remote); + File targetDir = targetFile.getParentFile(); + filterNonNull().mkdirs(targetDir); + Files.createDirectories(Util.fileToPath(targetDir)); + Files.copy(Util.fileToPath(reading(f)), Util.fileToPath(writing(targetFile)), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); + return null; + } + }); + return; + } + copyTo(target); // copy file permission target.chmod(mode()); diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index fc0afb1505..8ee5c7390a 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -68,6 +68,7 @@ import java.nio.file.LinkOption; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributes; import java.security.MessageDigest; @@ -1597,6 +1598,51 @@ public class Util { } } + @Restricted(NoExternalUse.class) + public static int permissionsToMode(Set permissions) { + PosixFilePermission[] allPermissions = PosixFilePermission.values(); + int result = 0; + for (int i = 0; i < allPermissions.length; i++) { + result <<= 1; + result |= permissions.contains(allPermissions[i]) ? 1 : 0; + } + return result; + } + + @Restricted(NoExternalUse.class) + public static Set modeToPermissions(int mode) throws IOException { + // Anything larger is a file type, not a permission. + int PERMISSIONS_MASK = 07777; + // setgid/setuid/sticky are not supported. + int MAX_SUPPORTED_MODE = 0777; + mode = mode & PERMISSIONS_MASK; + if ((mode & MAX_SUPPORTED_MODE) != mode) { + throw new IOException("Invalid mode: " + mode); + } + PosixFilePermission[] allPermissions = PosixFilePermission.values(); + Set result = EnumSet.noneOf(PosixFilePermission.class); + for (int i = 0; i < allPermissions.length; i++) { + if ((mode & 1) == 1) { + result.add(allPermissions[allPermissions.length - i - 1]); + } + mode >>= 1; + } + return result; + } + + /** + * Converts a {@link File} into a {@link Path} and checks runtime exceptions. + * @throws IOException if {@code f.toPath()} throws {@link InvalidPathException}. + */ + @Restricted(NoExternalUse.class) + public static @Nonnull Path fileToPath(@Nonnull File file) throws IOException { + try { + return file.toPath(); + } catch (InvalidPathException e) { + throw new IOException(e); + } + } + public static final FastDateFormat XS_DATETIME_FORMATTER = FastDateFormat.getInstance("yyyy-MM-dd'T'HH:mm:ss'Z'",new SimpleTimeZone(0,"GMT")); // Note: RFC822 dates must not be localized! @@ -1664,4 +1710,15 @@ public class Util { */ @Restricted(value = NoExternalUse.class) static boolean GC_AFTER_FAILED_DELETE = SystemProperties.getBoolean(Util.class.getName() + ".performGCOnFailedDelete"); + + /** + * If this flag is true, native implementations of {@link FilePath#chmod} + * and {@link hudson.util.IOUtils#mode} are used instead of NIO. + *

+ * This should only be enabled if the setgid/setuid/sticky bits are + * intentionally set on the Jenkins installation and they are being + * overwritten by Jenkins erroneously. + */ + @Restricted(value = NoExternalUse.class) + public static boolean NATIVE_CHMOD_MODE = SystemProperties.getBoolean(Util.class.getName() + ".useNativeChmodAndMode"); } diff --git a/core/src/main/java/hudson/util/IOUtils.java b/core/src/main/java/hudson/util/IOUtils.java index d726b43df5..4168379405 100644 --- a/core/src/main/java/hudson/util/IOUtils.java +++ b/core/src/main/java/hudson/util/IOUtils.java @@ -1,6 +1,7 @@ package hudson.util; import hudson.Functions; +import hudson.Util; import hudson.os.PosixAPI; import hudson.os.PosixException; import java.nio.file.Files; @@ -119,13 +120,27 @@ public class IOUtils { /** - * Gets the mode of a file/directory, if appropriate. + * Gets the mode of a file/directory, if appropriate. Only includes read, write, and + * execute permissions for the owner, group, and others, i.e. the max return value + * is 0777. Consider using {@link Files#getPosixFilePermissions} instead if you only + * care about access permissions. + * * @return a file mode, or -1 if not on Unix * @throws PosixException if the file could not be statted, e.g. broken symlink */ public static int mode(File f) throws PosixException { if(Functions.isWindows()) return -1; - return PosixAPI.jnr().stat(f.getPath()).mode(); + try { + if (Util.NATIVE_CHMOD_MODE) { + return PosixAPI.jnr().stat(f.getPath()).mode(); + } else { + return Util.permissionsToMode(Files.getPosixFilePermissions(Util.fileToPath(f))); + } + } catch (IOException cause) { + PosixException e = new PosixException("Unable to get file permissions", null); + e.initCause(cause); + throw e; + } } /** diff --git a/core/src/test/java/hudson/FilePathTest.java b/core/src/test/java/hudson/FilePathTest.java index 84ca31d889..846900424e 100644 --- a/core/src/test/java/hudson/FilePathTest.java +++ b/core/src/test/java/hudson/FilePathTest.java @@ -25,6 +25,7 @@ package hudson; import hudson.FilePath.TarCompression; import hudson.model.TaskListener; +import hudson.os.PosixAPI; import hudson.remoting.VirtualChannel; import hudson.util.NullStream; import hudson.util.StreamTaskListener; @@ -245,7 +246,7 @@ public class FilePathTest { throw x; } } finally { - toF.chmod(700); + toF.chmod(0700); } } @@ -471,6 +472,25 @@ public class FilePathTest { } } + @Test public void copyToWithPermissionSpecialPermissions() throws IOException, InterruptedException { + assumeFalse("Test uses POSIX-specific features", Functions.isWindows()); + File tmp = temp.getRoot(); + File original = new File(tmp,"original"); + FilePath originalP = new FilePath(channels.french, original.getPath()); + originalP.touch(0); + PosixAPI.jnr().chmod(original.getAbsolutePath(), 02777); // Read/write/execute for everyone and setuid. + + File sameChannelCopy = new File(tmp,"sameChannelCopy"); + FilePath sameChannelCopyP = new FilePath(channels.french, sameChannelCopy.getPath()); + originalP.copyToWithPermission(sameChannelCopyP); + assertEquals("Special permissions should be copied on the same machine", 02777, PosixAPI.jnr().stat(sameChannelCopy.getAbsolutePath()).mode() & 07777); + + File diffChannelCopy = new File(tmp,"diffChannelCopy"); + FilePath diffChannelCopyP = new FilePath(channels.british, diffChannelCopy.getPath()); + originalP.copyToWithPermission(diffChannelCopyP); + assertEquals("Special permissions should not be copied across machines", 00777, PosixAPI.jnr().stat(diffChannelCopy.getAbsolutePath()).mode() & 07777); + } + @Test public void symlinkInTar() throws Exception { assumeFalse("can't test on Windows", Functions.isWindows()); @@ -739,7 +759,38 @@ public class FilePathTest { // and now fail when flush is bad! tmpDirPath.child("../" + archive.getName()).untar(outDir, TarCompression.NONE); } + + @Test + public void chmod() throws Exception { + assumeFalse(Functions.isWindows()); + File f = temp.newFile("file"); + FilePath fp = new FilePath(f); + int prevMode = fp.mode(); + assertEquals(0400, chmodAndMode(fp, 0400)); + assertEquals(0412, chmodAndMode(fp, 0412)); + assertEquals(0777, chmodAndMode(fp, 0777)); + assertEquals(prevMode, chmodAndMode(fp, prevMode)); + } + @Test + public void chmodInvalidPermissions() throws Exception { + assumeFalse(Functions.isWindows()); + File f = temp.newFolder("folder"); + FilePath fp = new FilePath(f); + int invalidMode = 01770; // Full permissions for owner and group plus sticky bit. + try { + chmodAndMode(fp, invalidMode); + fail("Setting sticky bit should fail"); + } catch (IOException e) { + assertEquals("Invalid mode: " + invalidMode, e.getMessage()); + } + } + + private int chmodAndMode(FilePath path, int mode) throws Exception { + path.chmod(mode); + return path.mode(); + } + @Test public void deleteRecursiveOnUnix() throws Exception { assumeFalse("Uses Unix-specific features", Functions.isWindows()); Path targetDir = temp.newFolder("target").toPath(); diff --git a/core/src/test/java/hudson/UtilTest.java b/core/src/test/java/hudson/UtilTest.java index 3cef81aae9..21a8993de4 100644 --- a/core/src/test/java/hudson/UtilTest.java +++ b/core/src/test/java/hudson/UtilTest.java @@ -36,6 +36,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.attribute.PosixFilePermissions; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.containsString; @@ -43,6 +44,7 @@ import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.startsWith; import static org.junit.Assert.*; import org.apache.commons.io.FileUtils; @@ -724,4 +726,41 @@ public class UtilTest { assertTrue(Util.isDescendant(new File(root, "."), new File(new File(root, "child"), "."))); } + @Test + public void testModeToPermissions() throws Exception { + assertEquals(PosixFilePermissions.fromString("rwxrwxrwx"), Util.modeToPermissions(0777)); + assertEquals(PosixFilePermissions.fromString("rwxr-xrwx"), Util.modeToPermissions(0757)); + assertEquals(PosixFilePermissions.fromString("rwxr-x---"), Util.modeToPermissions(0750)); + assertEquals(PosixFilePermissions.fromString("r-xr-x---"), Util.modeToPermissions(0550)); + assertEquals(PosixFilePermissions.fromString("r-xr-----"), Util.modeToPermissions(0540)); + assertEquals(PosixFilePermissions.fromString("--xr-----"), Util.modeToPermissions(0140)); + assertEquals(PosixFilePermissions.fromString("--xr---w-"), Util.modeToPermissions(0142)); + assertEquals(PosixFilePermissions.fromString("--xr--rw-"), Util.modeToPermissions(0146)); + assertEquals(PosixFilePermissions.fromString("-wxr--rw-"), Util.modeToPermissions(0346)); + assertEquals(PosixFilePermissions.fromString("---------"), Util.modeToPermissions(0000)); + + assertEquals("Non-permission bits should be ignored", PosixFilePermissions.fromString("r-xr-----"), Util.modeToPermissions(0100540)); + + try { + Util.modeToPermissions(01777); + fail("Did not detect invalid mode"); + } catch (IOException e) { + assertThat(e.getMessage(), startsWith("Invalid mode")); + } + } + + @Test + public void testPermissionsToMode() throws Exception { + assertEquals(0777, Util.permissionsToMode(PosixFilePermissions.fromString("rwxrwxrwx"))); + assertEquals(0757, Util.permissionsToMode(PosixFilePermissions.fromString("rwxr-xrwx"))); + assertEquals(0750, Util.permissionsToMode(PosixFilePermissions.fromString("rwxr-x---"))); + assertEquals(0550, Util.permissionsToMode(PosixFilePermissions.fromString("r-xr-x---"))); + assertEquals(0540, Util.permissionsToMode(PosixFilePermissions.fromString("r-xr-----"))); + assertEquals(0140, Util.permissionsToMode(PosixFilePermissions.fromString("--xr-----"))); + assertEquals(0142, Util.permissionsToMode(PosixFilePermissions.fromString("--xr---w-"))); + assertEquals(0146, Util.permissionsToMode(PosixFilePermissions.fromString("--xr--rw-"))); + assertEquals(0346, Util.permissionsToMode(PosixFilePermissions.fromString("-wxr--rw-"))); + assertEquals(0000, Util.permissionsToMode(PosixFilePermissions.fromString("---------"))); + } + } diff --git a/core/src/test/java/hudson/util/io/TarArchiverTest.java b/core/src/test/java/hudson/util/io/TarArchiverTest.java index 144be06c55..7635ccc97b 100644 --- a/core/src/test/java/hudson/util/io/TarArchiverTest.java +++ b/core/src/test/java/hudson/util/io/TarArchiverTest.java @@ -83,9 +83,9 @@ public class TarArchiverTest { // extract via the tar command run(e, "tar", "xvpf", tar.getAbsolutePath()); - assertEquals(0100755,e.child("a.txt").mode()); + assertEquals(0755,e.child("a.txt").mode()); assertEquals(dirMode,e.child("subdir").mode()); - assertEquals(0100644,e.child("subdir/b.txt").mode()); + assertEquals(0644,e.child("subdir/b.txt").mode()); // extract via the zip command @@ -93,9 +93,9 @@ public class TarArchiverTest { run(e, "unzip", zip.getAbsolutePath()); e = e.listDirectories().get(0); - assertEquals(0100755, e.child("a.txt").mode()); + assertEquals(0755, e.child("a.txt").mode()); assertEquals(dirMode,e.child("subdir").mode()); - assertEquals(0100644,e.child("subdir/b.txt").mode()); + assertEquals(0644,e.child("subdir/b.txt").mode()); } finally { tar.delete(); zip.delete(); -- GitLab From 26e0ac512469c54099882a779223a9733f8ad83e Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 1 Dec 2017 12:04:16 -0500 Subject: [PATCH 0078/1380] war/src/test/js/ apparently produces war/target/surefire-reports/JasmineReport.xml without the TEST- prefix used by Surefire. --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 92fa2cadea..5d2a10435f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -58,7 +58,7 @@ for(i = 0; i < buildTypes.size(); i++) { archiveArtifacts artifacts: '**/target/*.jar, **/target/*.war, **/target/*.hpi', fingerprint: true if (runTests) { - junit healthScaleFactor: 20.0, testResults: '*/target/surefire-reports/TEST-*.xml' + junit healthScaleFactor: 20.0, testResults: '*/target/surefire-reports/*.xml' } } } -- GitLab From 67bc3869c9beed24f60619121794f3bd77b991b6 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 1 Dec 2017 16:20:03 -0500 Subject: [PATCH 0079/1380] Bump. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cbbdc1e0df..fcd8b606e5 100644 --- a/pom.xml +++ b/pom.xml @@ -170,7 +170,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.15-20171127.185115-3 + 3.15-20171201.211705-5 -- GitLab From 22efbad9450fc95748e0bc3bee04ec49702d7ed2 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Fri, 1 Dec 2017 23:17:18 +0100 Subject: [PATCH 0080/1380] [JENKINS-34855] Create a FileChannelWriter --- .../java/hudson/util/FileChannelWriter.java | 57 +++++++++++++++++ .../hudson/util/FileChannelWriterTest.java | 63 +++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 core/src/main/java/hudson/util/FileChannelWriter.java create mode 100644 core/src/test/java/hudson/util/FileChannelWriterTest.java diff --git a/core/src/main/java/hudson/util/FileChannelWriter.java b/core/src/main/java/hudson/util/FileChannelWriter.java new file mode 100644 index 0000000000..fd28d73427 --- /dev/null +++ b/core/src/main/java/hudson/util/FileChannelWriter.java @@ -0,0 +1,57 @@ +package hudson.util; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import java.io.IOException; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.nio.file.OpenOption; +import java.nio.file.Path; + +/** + * This class has been created to help make {@link AtomicFileWriter} hopefully more reliable in some corner cases. + * We created this wrapper to be able to access {@link FileChannel#force(boolean)} which seems to be one of the rare + * ways to actually have a guarantee that data be flushed to the physical device (only guaranteed for local, not for + * remote obviously though). + * + *

The goal using this is to reduce as much as we can the likeliness to see zero-length files be created in place + * of the original ones.

+ * + * @see JENKINS-34855 + * @see PR-2548 + */ +@Restricted(NoExternalUse.class) +public class FileChannelWriter extends Writer { + + private final Charset charset; + private final FileChannel channel; + + FileChannelWriter(Path filePath, Charset charset, OpenOption... options) throws IOException { + this.charset = charset; + channel = FileChannel.open(filePath, options); + } + + @Override + public void write(char cbuf[], int off, int len) throws IOException { + final CharBuffer charBuffer = CharBuffer.wrap(cbuf, off, len); + ByteBuffer byteBuffer = charset.encode(charBuffer); + channel.write(byteBuffer); + } + + @Override + public void flush() throws IOException { + channel.force(true); + } + + @Override + public void close() throws IOException { + if(channel.isOpen()) { + channel.force(true); + channel.close(); + } + } +} diff --git a/core/src/test/java/hudson/util/FileChannelWriterTest.java b/core/src/test/java/hudson/util/FileChannelWriterTest.java new file mode 100644 index 0000000000..c8aaf4c35d --- /dev/null +++ b/core/src/test/java/hudson/util/FileChannelWriterTest.java @@ -0,0 +1,63 @@ +package hudson.util; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.nio.charset.StandardCharsets; +import java.nio.file.StandardOpenOption; + +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class FileChannelWriterTest { + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + File file; + FileChannelWriter writer; + + @Before + public void setUp() throws Exception { + file = temporaryFolder.newFile(); + writer = new FileChannelWriter(file.toPath(), StandardCharsets.UTF_8, StandardOpenOption.WRITE); + } + + @Test + public void write() throws Exception { + writer.write("helloooo"); + writer.close(); + + assertContent("helloooo"); + } + + + @Test + public void flush() throws Exception { + writer.write("hello é è à".toCharArray()); + + writer.flush(); + assertContent("hello é è à"); + } + + @Test(expected = ClosedChannelException.class) + public void close() throws Exception { + writer.write("helloooo"); + writer.close(); + + writer.write("helloooo"); + fail("Should have failed the line above"); + } + + + private void assertContent(String string) throws IOException { + assertThat(FileUtils.readFileToString(file, StandardCharsets.UTF_8), equalTo(string)); + } +} -- GitLab From 9fb07a2c1a1b5c4bc628921b8a2023555ad58575 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Fri, 1 Dec 2017 23:24:07 +0100 Subject: [PATCH 0081/1380] Use FileChannelWriter behind AtomicFileWriter --- core/src/main/java/hudson/util/AtomicFileWriter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index d63be2648c..580a4c79dc 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -35,6 +35,7 @@ import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.util.logging.Level; import java.util.logging.Logger; @@ -116,7 +117,7 @@ public class AtomicFileWriter extends Writer { throw new IOException("Failed to create a temporary file in "+ dir,e); } - core = Files.newBufferedWriter(tmpPath, charset); + core = new FileChannelWriter(tmpPath, charset, StandardOpenOption.WRITE); } @Override -- GitLab From 7c9170a13ab7021ed5b9027a9a75a958b914bf77 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 3 Dec 2017 14:01:54 -0800 Subject: [PATCH 0082/1380] [maven-release-plugin] prepare release jenkins-2.93 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index be7db1f9a1..5826886099 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.93 cli diff --git a/core/pom.xml b/core/pom.xml index 9ce61b96c8..1ca00efed2 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.93 jenkins-core diff --git a/pom.xml b/pom.xml index 4a9ed89841..137e383dbe 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.93 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.93 diff --git a/test/pom.xml b/test/pom.xml index 282426e053..8064268fec 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.93 test diff --git a/war/pom.xml b/war/pom.xml index 94f80d2274..bbb93dd13c 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93-SNAPSHOT + 2.93 jenkins-war -- GitLab From a04b2dd63c1605d305c0d0ecbb9918649d6ad0d5 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 3 Dec 2017 14:01:54 -0800 Subject: [PATCH 0083/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 5826886099..b6771c7daf 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.93 + 2.94-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 1ca00efed2..a4b053eee5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93 + 2.94-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index 137e383dbe..c798586c2b 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93 + 2.94-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.93 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 8064268fec..2f87f51d5f 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93 + 2.94-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index bbb93dd13c..3d7ea1e54e 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.93 + 2.94-SNAPSHOT jenkins-war -- GitLab From 1191bb0ef08bd3e84fbc334799080551cdb0cb96 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Sun, 3 Dec 2017 23:21:30 +0100 Subject: [PATCH 0084/1380] New flag to FileChannel.force() on each Writer.flush() or not And disable FileChannel.force() in the case of AtomicFileWriter. See the committed javadoc for the full why. --- .../java/hudson/util/AtomicFileWriter.java | 2 +- .../java/hudson/util/FileChannelWriter.java | 33 +++++++++++++++++-- .../hudson/util/FileChannelWriterTest.java | 2 +- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 580a4c79dc..e67eafbb64 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -117,7 +117,7 @@ public class AtomicFileWriter extends Writer { throw new IOException("Failed to create a temporary file in "+ dir,e); } - core = new FileChannelWriter(tmpPath, charset, StandardOpenOption.WRITE); + core = new FileChannelWriter(tmpPath, charset, false, StandardOpenOption.WRITE); } @Override diff --git a/core/src/main/java/hudson/util/FileChannelWriter.java b/core/src/main/java/hudson/util/FileChannelWriter.java index fd28d73427..591009e8a5 100644 --- a/core/src/main/java/hudson/util/FileChannelWriter.java +++ b/core/src/main/java/hudson/util/FileChannelWriter.java @@ -11,6 +11,7 @@ import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.file.OpenOption; import java.nio.file.Path; +import java.util.logging.Logger; /** * This class has been created to help make {@link AtomicFileWriter} hopefully more reliable in some corner cases. @@ -27,11 +28,34 @@ import java.nio.file.Path; @Restricted(NoExternalUse.class) public class FileChannelWriter extends Writer { + private static final Logger LOGGER = Logger.getLogger(FileChannelWriter.class.getName()); + private final Charset charset; private final FileChannel channel; - FileChannelWriter(Path filePath, Charset charset, OpenOption... options) throws IOException { + /** + * {@link FileChannel#force(boolean)} is a very costly operation. This flag has been introduced mostly to + * accommodate Jenkins' previous behaviour, when using a simple {@link java.io.BufferedWriter}. + * + *

Basically, {@link BufferedWriter#flush()} does nothing, so when existing code was rewired to use + * {@link FileChannelWriter#flush()} behind {@link AtomicFileWriter} and that method actually ends up calling + * {@link FileChannel#force(boolean)}, many things started timing out. The main reason is probably because XStream's + * {@link com.thoughtworks.xstream.core.util.QuickWriter} uses flush() a lot. + * So we introduced this field to be able to still get a better integrity for the use case of {@link AtomicFileWriter}. + * Because from there, we make sure to call {@link #close()} from {@link AtomicFileWriter#commit()} anyway. + */ + private boolean forceOnFlush; + + /** + * @param filePath the path of the file to write to. + * @param charset the charset to use when writing characters. + * @param forceOnFlush set to true if you want {@link FileChannel#force(boolean)} to be called on {@link #flush()}. + * @param options the options for opening the file. + * @throws IOException if something went wrong. + */ + FileChannelWriter(Path filePath, Charset charset, boolean forceOnFlush, OpenOption... options) throws IOException { this.charset = charset; + this.forceOnFlush = forceOnFlush; channel = FileChannel.open(filePath, options); } @@ -44,7 +68,12 @@ public class FileChannelWriter extends Writer { @Override public void flush() throws IOException { - channel.force(true); + if (forceOnFlush) { + LOGGER.finest("Flush is forced"); + channel.force(true); + } else { + LOGGER.finest("Force disabled on flush(), no-op"); + } } @Override diff --git a/core/src/test/java/hudson/util/FileChannelWriterTest.java b/core/src/test/java/hudson/util/FileChannelWriterTest.java index c8aaf4c35d..59ac01ff96 100644 --- a/core/src/test/java/hudson/util/FileChannelWriterTest.java +++ b/core/src/test/java/hudson/util/FileChannelWriterTest.java @@ -27,7 +27,7 @@ public class FileChannelWriterTest { @Before public void setUp() throws Exception { file = temporaryFolder.newFile(); - writer = new FileChannelWriter(file.toPath(), StandardCharsets.UTF_8, StandardOpenOption.WRITE); + writer = new FileChannelWriter(file.toPath(), StandardCharsets.UTF_8, true, StandardOpenOption.WRITE); } @Test -- GitLab From f555cb6e7a0d4199a4417841e7b42e7f61786c22 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Sun, 3 Dec 2017 14:42:09 +0100 Subject: [PATCH 0085/1380] Bump to a bit longer (Note: mainly bumping for the CI to not fail since I was never able to see this test fail on my machine [takes ~17 with this change] anyway other than by running in Docker and aggressively reduce IOPS to 40 using Docker resource constraints.) Since we now *actually* sync to disk, this isn't really a regression IMO. From the very role of AtomicFileWriter, it looks more like a feature or an improvement than an issue. So I suspect that anyway 1) we won't see a lot of bad impact in the wild on most setups (as we generally already recommend SSDs and so on as per how Jenkins works) and 2) we win anyway in term of integrity by making sure (well, raising the chances, let's say) we do write to disk before renaming files. --- test/src/test/java/hudson/util/AtomicFileWriterPerfTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/test/java/hudson/util/AtomicFileWriterPerfTest.java b/test/src/test/java/hudson/util/AtomicFileWriterPerfTest.java index 94d5e6c687..31942ed318 100644 --- a/test/src/test/java/hudson/util/AtomicFileWriterPerfTest.java +++ b/test/src/test/java/hudson/util/AtomicFileWriterPerfTest.java @@ -24,7 +24,7 @@ public class AtomicFileWriterPerfTest { * really bad performance regressions. */ @Issue("JENKINS-34855") - @Test(timeout = 30 * 1000L) + @Test(timeout = 50 * 1000L) public void poorManPerformanceTestBed() throws Exception { int count = 1000; while (count-- > 0) { -- GitLab From 5fa7d0c7316db735b9676cd2e52e38107acf98aa Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 4 Dec 2017 12:45:16 -0500 Subject: [PATCH 0086/1380] Adjusting test after #3167, since now the rejection will become an IOException caught by Descriptor.save. --- .../java/jenkins/security/ClassFilterImplTest.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/src/test/java/jenkins/security/ClassFilterImplTest.java b/test/src/test/java/jenkins/security/ClassFilterImplTest.java index bed5648b6f..3e6ed480eb 100644 --- a/test/src/test/java/jenkins/security/ClassFilterImplTest.java +++ b/test/src/test/java/jenkins/security/ClassFilterImplTest.java @@ -146,18 +146,17 @@ public class ClassFilterImplTest { public void xstreamRequiresWhitelist() throws Exception { assumeThat(ClassFilterImpl.WHITELISTED_CLASSES, not(contains(LinkedListMultimap.class.getName()))); Config config = GlobalConfiguration.all().get(Config.class); + config.save(); config.obj = LinkedListMultimap.create(); - try { - config.save(); - fail("should not have been accepted"); - } catch (Exception x) { - x.printStackTrace(); - // OK - } + config.save(); + assertThat(config.xml(), not(containsString("LinkedListMultimap"))); } @TestExtension("xstreamRequiresWhitelist") public static class Config extends GlobalConfiguration { LinkedListMultimap obj; + String xml() throws IOException { + return getConfigFile().asString(); + } } } -- GitLab From 5c749cfac3c64693c4e71cbfc519340d421b1d7f Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 4 Dec 2017 23:11:31 -0500 Subject: [PATCH 0087/1380] Support dynamically-loaded plugins in CustomClassFilter.Contributed. --- core/src/main/java/hudson/PluginManager.java | 4 ++++ .../jenkins/security/CustomClassFilter.java | 3 ++- .../jenkins/security/CustomClassFilterTest.java | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index c816805ba6..ab203c20ec 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -150,6 +150,7 @@ import static java.util.logging.Level.FINE; import static java.util.logging.Level.INFO; import static java.util.logging.Level.SEVERE; import static java.util.logging.Level.WARNING; +import jenkins.security.CustomClassFilter; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -858,6 +859,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas ((UberClassLoader) uberClassLoader).loaded.clear(); } + // TODO antimodular; perhaps should have a PluginListener to complement ExtensionListListener? + CustomClassFilter.Contributed.load(); + try { p.resolvePluginDependencies(); strategy.load(p); diff --git a/core/src/main/java/jenkins/security/CustomClassFilter.java b/core/src/main/java/jenkins/security/CustomClassFilter.java index e0cd7186aa..a417b82017 100644 --- a/core/src/main/java/jenkins/security/CustomClassFilter.java +++ b/core/src/main/java/jenkins/security/CustomClassFilter.java @@ -131,7 +131,7 @@ public interface CustomClassFilter extends ExtensionPoint { * !com.acme.illadvised.YoloReflectionFactory$Handle * */ - @Restricted(DoNotUse.class) + @Restricted(NoExternalUse.class) @Extension public class Contributed implements CustomClassFilter { @@ -154,6 +154,7 @@ public interface CustomClassFilter extends ExtensionPoint { @Initializer(after = InitMilestone.PLUGINS_PREPARED, before = InitMilestone.PLUGINS_STARTED, fatal = false) public static void load() throws IOException { Map overrides = ExtensionList.lookup(CustomClassFilter.class).get(Contributed.class).overrides; + overrides.clear(); Enumeration resources = Jenkins.getInstance().getPluginManager().uberClassLoader.getResources("META-INF/hudson.remoting.ClassFilter"); while (resources.hasMoreElements()) { try (InputStream is = resources.nextElement().openStream()) { diff --git a/test/src/test/java/jenkins/security/CustomClassFilterTest.java b/test/src/test/java/jenkins/security/CustomClassFilterTest.java index a8797f4ebc..72efda1500 100644 --- a/test/src/test/java/jenkins/security/CustomClassFilterTest.java +++ b/test/src/test/java/jenkins/security/CustomClassFilterTest.java @@ -25,6 +25,7 @@ package jenkins.security; import hudson.remoting.ClassFilter; +import java.io.File; import java.util.logging.Level; import javax.script.ScriptEngineManager; import javax.script.ScriptException; @@ -32,10 +33,12 @@ import javax.script.SimpleBindings; import jenkins.util.BuildListenerAdapter; import jenkins.util.TreeString; import jenkins.util.TreeStringBuilder; +import org.apache.commons.io.FileUtils; import org.junit.Test; import static org.hamcrest.Matchers.*; import org.junit.Rule; import org.junit.rules.ErrorCollector; +import org.junit.rules.TemporaryFolder; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.LoggerRule; import org.jvnet.hudson.test.recipes.WithPlugin; @@ -55,6 +58,9 @@ public class CustomClassFilterTest { @Rule public LoggerRule logging = new LoggerRule().record("jenkins.security", Level.FINER); + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + @WithPlugin("custom-class-filter.jpi") @Test public void smokes() throws Exception { @@ -67,6 +73,17 @@ public class CustomClassFilterTest { assertBlacklisted("disabled via plugin", TreeStringBuilder.class, true); } + @Test + public void dynamicLoad() throws Exception { + assertBlacklisted("not yet enabled via plugin", ScriptException.class, true); + assertBlacklisted("not yet disabled via plugin", TreeStringBuilder.class, false); + File jpi = tmp.newFile("custom-class-filter.jpi"); + FileUtils.copyURLToFile(CustomClassFilterTest.class.getResource("/plugins/custom-class-filter.jpi"), jpi); + r.jenkins.pluginManager.dynamicLoad(jpi); + assertBlacklisted("enabled via plugin", ScriptException.class, false); + assertBlacklisted("disabled via plugin", TreeStringBuilder.class, true); + } + private void assertBlacklisted(String message, Class c, boolean blacklisted) { String name = c.getName(); errors.checkThat(name + ": " + message, ClassFilter.DEFAULT.isBlacklisted(c) || ClassFilter.DEFAULT.isBlacklisted(name), is(blacklisted)); -- GitLab From e8768491f62addc7fd1966db4b4e194ba186e82a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 5 Dec 2017 00:23:02 -0500 Subject: [PATCH 0088/1380] Adding some whitelist entries corresponding to keys. --- .../main/resources/jenkins/security/whitelisted-classes.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 238008d0e7..90f350d8ee 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -25,6 +25,7 @@ java.lang.StackTraceElement java.lang.String java.lang.reflect.Proxy java.net.URL +java.security.KeyRep java.util.ArrayDeque java.util.ArrayList java.util.Arrays$ArrayList @@ -81,3 +82,5 @@ org.jboss.marshalling.TraceInformation$ObjectInfo org.jvnet.hudson.MemoryUsage org.jvnet.localizer.Localizable org.jvnet.localizer.ResourceBundleHolder +sun.security.rsa.RSAPublicKeyImpl +sun.security.x509.X509Key -- GitLab From 6c04293d45567833d5c37316ec162d44a2249bf4 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Tue, 5 Dec 2017 13:05:45 +0100 Subject: [PATCH 0089/1380] [JENKINS-48383] Add loggedIn event on self-registration --- .../security/HudsonPrivateSecurityRealm.java | 3 + .../HudsonPrivateSecurityRealmTest.java | 126 ++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java index b82e8b70d0..95ff998a2a 100644 --- a/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java +++ b/core/src/main/java/hudson/security/HudsonPrivateSecurityRealm.java @@ -41,6 +41,7 @@ import hudson.util.PluginServletFilter; import hudson.util.Protector; import hudson.util.Scrambler; import hudson.util.XStream2; +import jenkins.security.SecurityListener; import net.sf.json.JSONObject; import org.acegisecurity.Authentication; import org.acegisecurity.AuthenticationException; @@ -258,6 +259,8 @@ public class HudsonPrivateSecurityRealm extends AbstractPasswordBasedSecurityRea a = this.getSecurityComponents().manager.authenticate(a); SecurityContextHolder.getContext().setAuthentication(a); + SecurityListener.fireLoggedIn(u.getId()); + // then back to top req.getView(this,"success.jelly").forward(req,rsp); } diff --git a/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java index 966b82c2ff..5e51c5ff04 100644 --- a/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java +++ b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java @@ -25,33 +25,57 @@ package hudson.security; import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.util.NameValuePair; import com.gargoylesoftware.htmlunit.xml.XmlPage; +import hudson.ExtensionList; import hudson.model.User; import hudson.remoting.Base64; import static hudson.security.HudsonPrivateSecurityRealm.CLASSIC; import static hudson.security.HudsonPrivateSecurityRealm.PASSWORD_ENCODER; import hudson.security.pages.SignupPage; import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.List; + import jenkins.security.ApiTokenProperty; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.xml.HasXPath.hasXPath; import static org.junit.Assert.*; + +import jenkins.security.SecurityListener; +import org.apache.commons.lang.StringUtils; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule.WebClient; +import org.jvnet.hudson.test.TestExtension; import org.jvnet.hudson.test.WithoutJenkins; import org.jvnet.hudson.test.recipes.LocalData; +import javax.annotation.Nonnull; + public class HudsonPrivateSecurityRealmTest { @Rule public JenkinsRule j = new JenkinsRule(); + private SpySecurityListenerImpl spySecurityListener; + + @Before + public void linkExtension() throws Exception { + spySecurityListener = ExtensionList.lookup(SecurityListener.class).get(SpySecurityListenerImpl.class); + } + /** * Tests the data compatibility with Hudson before 1.283. * Starting 1.283, passwords are now stored hashed. @@ -259,4 +283,106 @@ public class HudsonPrivateSecurityRealmTest { assertNull(User.get("unknown2",false, Collections.emptyMap())); } + @Issue("JENKINS-48383") + @Test + public void selfRegistrationTriggerLoggedIn() throws Exception { + HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(true, false, null); + j.jenkins.setSecurityRealm(securityRealm); + j.jenkins.setCrumbIssuer(null); + + assertTrue(spySecurityListener.loggedInUsernames.isEmpty()); + + createFirstAccount("admin"); + assertTrue(spySecurityListener.loggedInUsernames.get(0).equals("admin")); + + createAccountByAdmin("alice"); + // no new event in such case + assertTrue(spySecurityListener.loggedInUsernames.isEmpty()); + + selfRegistration("bob"); + assertTrue(spySecurityListener.loggedInUsernames.get(0).equals("bob")); + } + + private void createFirstAccount(String login) throws Exception { + assertNull(User.getById(login, false)); + + JenkinsRule.WebClient wc = j.createWebClient(); + + HudsonPrivateSecurityRealm.SignupInfo info = new HudsonPrivateSecurityRealm.SignupInfo(); + info.username = login; + info.password1 = login; + info.password2 = login; + info.fullname = StringUtils.capitalize(login); + + WebRequest request = new WebRequest(new URL(wc.getContextPath() + "securityRealm/createFirstAccount"), HttpMethod.POST); + request.setRequestParameters(Arrays.asList( + new NameValuePair("username", login), + new NameValuePair("password1", login), + new NameValuePair("password2", login), + new NameValuePair("fullname", StringUtils.capitalize(login)), + new NameValuePair("email", login + "@" + login + ".com") + )); + + HtmlPage p = wc.getPage(request); + assertEquals(200, p.getWebResponse().getStatusCode()); + assertTrue(p.getDocumentElement().getElementsByAttribute("div", "class", "error").isEmpty()); + + assertNotNull(User.getById(login, false)); + } + + private void createAccountByAdmin(String login) throws Exception { + // user should not exist before + assertNull(User.getById(login, false)); + + JenkinsRule.WebClient wc = j.createWebClient(); + wc.login("admin"); + + spySecurityListener.loggedInUsernames.clear(); + + HtmlPage page = wc.goTo("securityRealm/addUser"); + HtmlForm form = page.getForms().stream() + .filter(htmlForm -> htmlForm.getActionAttribute().endsWith("/securityRealm/createAccountByAdmin")) + .findFirst() + .orElseThrow(() -> new AssertionError("Form must be present")); + + form.getInputByName("username").setValueAttribute(login); + form.getInputByName("password1").setValueAttribute(login); + form.getInputByName("password2").setValueAttribute(login); + form.getInputByName("fullname").setValueAttribute(StringUtils.capitalize(login)); + form.getInputByName("email").setValueAttribute(login + "@" + login + ".com"); + + HtmlPage p = j.submit(form); + assertEquals(200, p.getWebResponse().getStatusCode()); + assertTrue(p.getDocumentElement().getElementsByAttribute("div", "class", "error").isEmpty()); + + assertNotNull(User.getById(login, false)); + } + + private void selfRegistration(String login) throws Exception { + // user should not exist before + assertNull(User.getById(login, false)); + + JenkinsRule.WebClient wc = j.createWebClient(); + SignupPage signup = new SignupPage(wc.goTo("signup")); + signup.enterUsername(login); + signup.enterPassword(login); + signup.enterFullName(StringUtils.capitalize(login)); + signup.enterEmail(login + "@" + login + ".com"); + + HtmlPage p = signup.submit(j); + assertEquals(200, p.getWebResponse().getStatusCode()); + assertTrue(p.getDocumentElement().getElementsByAttribute("div", "class", "error").isEmpty()); + + assertNotNull(User.getById(login, false)); + } + + @TestExtension + public static class SpySecurityListenerImpl extends SecurityListener { + private List loggedInUsernames = new ArrayList<>(); + + @Override + protected void loggedIn(@Nonnull String username) { + loggedInUsernames.add(username); + } + } } -- GitLab From f3623d7b78f73a44a7d47726e44f5957e6bca58f Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 5 Dec 2017 10:04:41 -0500 Subject: [PATCH 0090/1380] Forgotten charset. --- test/src/test/java/jenkins/security/ClassFilterImplTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/test/java/jenkins/security/ClassFilterImplTest.java b/test/src/test/java/jenkins/security/ClassFilterImplTest.java index 3e6ed480eb..752c3634ed 100644 --- a/test/src/test/java/jenkins/security/ClassFilterImplTest.java +++ b/test/src/test/java/jenkins/security/ClassFilterImplTest.java @@ -35,6 +35,7 @@ import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.TreeSet; import java.util.logging.Level; @@ -67,7 +68,7 @@ public class ClassFilterImplTest { @Test public void whitelistSanity() throws Exception { try (InputStream is = ClassFilterImpl.class.getResourceAsStream("whitelisted-classes.txt")) { - List lines = IOUtils.readLines(is); + List lines = IOUtils.readLines(is, StandardCharsets.UTF_8); assertThat("whitelist is ordered", new TreeSet<>(lines), contains(lines.toArray(new String[0]))); for (String line : lines) { try { -- GitLab From e65601b40746d5da21cff1e5d5cb92b02586e784 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 5 Dec 2017 22:19:20 -0500 Subject: [PATCH 0091/1380] org.jenkinsci.plugins.gitclient.RemotingTest.testRemotability --- core/src/main/resources/jenkins/security/whitelisted-classes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 90f350d8ee..fd2f6443ec 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -73,6 +73,7 @@ org.apache.commons.fileupload.disk.DiskFileItem org.apache.commons.fileupload.util.FileItemHeadersImpl org.eclipse.jgit.lib.ObjectId org.eclipse.jgit.lib.ObjectIdOwnerMap$Entry +org.eclipse.jgit.lib.PersonIdent org.eclipse.jgit.revwalk.RevCommit org.eclipse.jgit.revwalk.RevObject org.eclipse.jgit.revwalk.RevTree -- GitLab From ed802569169a26812eecb70e7a36eb810651f9fa Mon Sep 17 00:00:00 2001 From: surenpi Date: Wed, 6 Dec 2017 11:58:49 +0800 Subject: [PATCH 0092/1380] Add Chinese translation --- .../hudson/markup/Messages_zh_CN.properties | 23 +++++++++++++ .../config_zh_CN.properties | 2 +- .../config_zh_CN.properties | 25 ++++++++++++++ .../config_zh_CN.properties | 24 +++++++++++++ .../model/Job/permalinks_zh_CN.properties | 2 ++ .../hudson/model/Messages_zh_CN.properties | 34 +++++++++++++++++++ .../config_zh_CN.properties | 25 ++++++++++++++ .../config_zh_CN.properties | 31 +++++++++++++++++ .../config_zh_CN.properties | 3 +- .../config_zh_CN.properties | 25 ++++++++++++++ .../config-assignedLabel_zh_CN.properties | 24 +++++++++++++ .../webapp/help/parameter/trim_zh_CN.html | 3 ++ 12 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 core/src/main/resources/hudson/markup/Messages_zh_CN.properties create mode 100644 core/src/main/resources/hudson/model/ChoiceParameterDefinition/config_zh_CN.properties create mode 100644 core/src/main/resources/hudson/model/FileParameterDefinition/config_zh_CN.properties create mode 100644 core/src/main/resources/hudson/model/PasswordParameterDefinition/config_zh_CN.properties create mode 100644 core/src/main/resources/hudson/model/RunParameterDefinition/config_zh_CN.properties create mode 100644 core/src/main/resources/hudson/model/TextParameterDefinition/config_zh_CN.properties create mode 100644 core/src/main/resources/lib/hudson/project/config-assignedLabel_zh_CN.properties create mode 100644 war/src/main/webapp/help/parameter/trim_zh_CN.html diff --git a/core/src/main/resources/hudson/markup/Messages_zh_CN.properties b/core/src/main/resources/hudson/markup/Messages_zh_CN.properties new file mode 100644 index 0000000000..9f9f5caf2e --- /dev/null +++ b/core/src/main/resources/hudson/markup/Messages_zh_CN.properties @@ -0,0 +1,23 @@ +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. + +EscapedMarkupFormatter.DisplayName=\u7EAF\u6587\u672C diff --git a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_CN.properties index 0703474e44..ce517528d3 100644 --- a/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_CN.properties +++ b/core/src/main/resources/hudson/model/BooleanParameterDefinition/config_zh_CN.properties @@ -21,4 +21,4 @@ # THE SOFTWARE. Name=\u540D\u79F0 Default\ Value=\u9ED8\u8BA4\u503C -Description=\u8BF4\u660E +Description=\u63CF\u8FF0 diff --git a/core/src/main/resources/hudson/model/ChoiceParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/ChoiceParameterDefinition/config_zh_CN.properties new file mode 100644 index 0000000000..628e80119f --- /dev/null +++ b/core/src/main/resources/hudson/model/ChoiceParameterDefinition/config_zh_CN.properties @@ -0,0 +1,25 @@ +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. + +Name=\u540D\u79F0 +Choices=\u9009\u9879 +Description=\u63CF\u8FF0 diff --git a/core/src/main/resources/hudson/model/FileParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/FileParameterDefinition/config_zh_CN.properties new file mode 100644 index 0000000000..15c668fdb4 --- /dev/null +++ b/core/src/main/resources/hudson/model/FileParameterDefinition/config_zh_CN.properties @@ -0,0 +1,24 @@ +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. + +File\ location=\u6587\u4EF6\u8DEF\u5F84 +Description=\u63CF\u8FF0 diff --git a/core/src/main/resources/hudson/model/Job/permalinks_zh_CN.properties b/core/src/main/resources/hudson/model/Job/permalinks_zh_CN.properties index a743d1c687..f7ce595cdf 100644 --- a/core/src/main/resources/hudson/model/Job/permalinks_zh_CN.properties +++ b/core/src/main/resources/hudson/model/Job/permalinks_zh_CN.properties @@ -21,3 +21,5 @@ # THE SOFTWARE. Permalinks=\u76F8\u5173\u8FDE\u63A5 +Restrict\ where\ this\ project\ can\ be\ run=\u9650\u5236\u9879\u76EE\u7684\u8FD0\u884C\u8282\u70B9 +Label\ Expression=\u6807\u7B7E\u8868\u8FBE\u5F0F diff --git a/core/src/main/resources/hudson/model/Messages_zh_CN.properties b/core/src/main/resources/hudson/model/Messages_zh_CN.properties index 379e26ac1d..7c3ccde407 100644 --- a/core/src/main/resources/hudson/model/Messages_zh_CN.properties +++ b/core/src/main/resources/hudson/model/Messages_zh_CN.properties @@ -26,6 +26,30 @@ FreeStyleProject.Description=\u8FD9\u662FJenkins\u7684\u4E3B\u8981\u529F\u80FD.J HealthReport.EmptyString= +BallColor.Aborted=\u5DF2\u7EC8\u6B62 +BallColor.Disabled=\u7981\u7528 +BallColor.Failed=\u5931\u8D25 +BallColor.InProgress=\u6267\u884C\u4E2D +BallColor.NotBuilt=\u672A\u6784\u5EFA +BallColor.Pending=\u7B49\u5F85 +BallColor.Success=\u6210\u529F +BallColor.Unstable=\u4E0D\u7A33\u5B9A + +Item.Permissions.Title=\u4EFB\u52A1 +Item.CREATE.description=\u521B\u5EFA\u65B0\u7684\u4EFB\u52A1\u3002 +Item.DELETE.description=\u5220\u9664\u4EFB\u52A1\u3002 +Item.CONFIGURE.description=\u4FEE\u6539\u4EFB\u52A1\u7684\u914D\u7F6E\u3002 + +ResultTrend.Aborted=\u5DF2\u7EC8\u6B62 +ResultTrend.Failure=\u5931\u8D25 +ResultTrend.Fixed=\u56FA\u5B9A +ResultTrend.NotBuilt=\u672A\u6784\u5EFA +ResultTrend.NowUnstable=\u4E0D\u7A33\u5B9A +ResultTrend.StillFailing=\u4ECD\u7136\u5931\u8D25 +ResultTrend.StillUnstable=\u4ECD\u7136\u4E0D\u7A33\u5B9A +ResultTrend.Success=\u6210\u529F +ResultTrend.Unstable=\u4E0D\u7A33\u5B9A + Node.Mode.NORMAL=\u5C3D\u53EF\u80FD\u7684\u4F7F\u7528\u8FD9\u4E2A\u8282\u70B9 Node.Mode.EXCLUSIVE=\u53EA\u5141\u8BB8\u8FD0\u884C\u7ED1\u5B9A\u5230\u8FD9\u53F0\u673A\u5668\u7684Job @@ -34,3 +58,13 @@ MyView.DisplayName=\u6211\u7684\u89C6\u56FE ManageJenkinsAction.DisplayName=\u7CFB\u7EDF\u7BA1\u7406 ParametersDefinitionProperty.DisplayName=\u53C2\u6570\u5316\u6784\u5EFA\u8FC7\u7A0B ListView.DisplayName=\u7B80\u5355\u89C6\u56FE + +ParameterAction.DisplayName=\u53C2\u6570 +StringParameterDefinition.DisplayName=\u5B57\u7B26\u53C2\u6570 +TextParameterDefinition.DisplayName=\u6587\u672C\u53C2\u6570 +FileParameterDefinition.DisplayName=\u6587\u4EF6\u53C2\u6570 +BooleanParameterDefinition.DisplayName=\u5E03\u5C14\u503C\u53C2\u6570 +ChoiceParameterDefinition.DisplayName=\u9009\u9879\u53C2\u6570 +ChoiceParameterDefinition.MissingChoices=\u9700\u8981\u9009\u9879\u3002 +RunParameterDefinition.DisplayName=\u8FD0\u884C\u65F6\u53C2\u6570 +PasswordParameterDefinition.DisplayName=\u5BC6\u7801\u53C2\u6570 diff --git a/core/src/main/resources/hudson/model/PasswordParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/PasswordParameterDefinition/config_zh_CN.properties new file mode 100644 index 0000000000..67cd3a0398 --- /dev/null +++ b/core/src/main/resources/hudson/model/PasswordParameterDefinition/config_zh_CN.properties @@ -0,0 +1,25 @@ +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. + +Name=\u540D\u79F0 +Default\ Value=\u9ED8\u8BA4\u503C +Description=\u63CF\u8FF0 diff --git a/core/src/main/resources/hudson/model/RunParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/RunParameterDefinition/config_zh_CN.properties new file mode 100644 index 0000000000..22a181a57a --- /dev/null +++ b/core/src/main/resources/hudson/model/RunParameterDefinition/config_zh_CN.properties @@ -0,0 +1,31 @@ +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. + +Name=\u540D\u79F0 +Project=\u9879\u76EE +Description=\u63CF\u8FF0 + +All\ Builds=\u6240\u6709\u6784\u5EFA +Successful\ Builds\ Only=\u6210\u529F\u7684\u6784\u5EFA +Completed\ Builds\ Only=\u6267\u884C\u5B8C\u6210\u7684\u6784\u5EFA +Stable\ Builds\ Only=\u7A33\u5B9A\u7684\u6784\u5EFA + diff --git a/core/src/main/resources/hudson/model/StringParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/StringParameterDefinition/config_zh_CN.properties index 43c49864a8..f051916103 100644 --- a/core/src/main/resources/hudson/model/StringParameterDefinition/config_zh_CN.properties +++ b/core/src/main/resources/hudson/model/StringParameterDefinition/config_zh_CN.properties @@ -2,4 +2,5 @@ Default\ Value=\u9ED8\u8BA4\u503C Description=\u63CF\u8FF0 -Name=\u540D\u5B57 +Name=\u540D\u79F0 +Trim\ the\ string=\u6E05\u9664\u7A7A\u767D\u5B57\u7B26 diff --git a/core/src/main/resources/hudson/model/TextParameterDefinition/config_zh_CN.properties b/core/src/main/resources/hudson/model/TextParameterDefinition/config_zh_CN.properties new file mode 100644 index 0000000000..67cd3a0398 --- /dev/null +++ b/core/src/main/resources/hudson/model/TextParameterDefinition/config_zh_CN.properties @@ -0,0 +1,25 @@ +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. + +Name=\u540D\u79F0 +Default\ Value=\u9ED8\u8BA4\u503C +Description=\u63CF\u8FF0 diff --git a/core/src/main/resources/lib/hudson/project/config-assignedLabel_zh_CN.properties b/core/src/main/resources/lib/hudson/project/config-assignedLabel_zh_CN.properties new file mode 100644 index 0000000000..b9384115a4 --- /dev/null +++ b/core/src/main/resources/lib/hudson/project/config-assignedLabel_zh_CN.properties @@ -0,0 +1,24 @@ +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. + +Restrict\ where\ this\ project\ can\ be\ run=\u9650\u5236\u9879\u76EE\u7684\u8FD0\u884C\u8282\u70B9 +Label\ Expression=\u6807\u7B7E\u8868\u8FBE\u5F0F diff --git a/war/src/main/webapp/help/parameter/trim_zh_CN.html b/war/src/main/webapp/help/parameter/trim_zh_CN.html new file mode 100644 index 0000000000..a3c56e13b7 --- /dev/null +++ b/war/src/main/webapp/help/parameter/trim_zh_CN.html @@ -0,0 +1,3 @@ +

+ 清除字符串前后的清白字符。 +
-- GitLab From 2fa723b1ee4ba09d087877a7efc89245c6ca4c65 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 6 Dec 2017 11:10:45 -0800 Subject: [PATCH 0093/1380] [maven-release-plugin] prepare release jenkins-2.89.1 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index c37054f397..149bd085fb 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.89.1-SNAPSHOT + 2.89.1 cli diff --git a/core/pom.xml b/core/pom.xml index 05c8d0244b..7810dd6d28 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.1-SNAPSHOT + 2.89.1 jenkins-core diff --git a/pom.xml b/pom.xml index 4beccc327a..da396fbf7b 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.1-SNAPSHOT + 2.89.1 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.89.1 diff --git a/test/pom.xml b/test/pom.xml index da7682aef1..7f1e787369 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.1-SNAPSHOT + 2.89.1 test diff --git a/war/pom.xml b/war/pom.xml index 092fe2503a..ce2df93097 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.1-SNAPSHOT + 2.89.1 jenkins-war -- GitLab From 2a1406e4bf07f2fccdd59780c4ffaa1e161b629e Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 6 Dec 2017 11:10:45 -0800 Subject: [PATCH 0094/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 149bd085fb..1fc896f74f 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.89.1 + 2.89.2-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 7810dd6d28..6278a3fead 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.1 + 2.89.2-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index da396fbf7b..4e57abc3bf 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.1 + 2.89.2-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.89.1 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 7f1e787369..839aed6dbd 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.1 + 2.89.2-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index ce2df93097..a7f48bb18a 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.1 + 2.89.2-SNAPSHOT jenkins-war -- GitLab From 938c12ad56f3142695b76165a7cbde5f53ce1a16 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 6 Dec 2017 16:00:41 -0500 Subject: [PATCH 0095/1380] Easier way to make an entire JAR component be treated like plugin code for purposes of whitelisting. org.apache.maven.plugins maven-jar-plugin true --- core/src/main/java/jenkins/security/ClassFilterImpl.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/security/ClassFilterImpl.java b/core/src/main/java/jenkins/security/ClassFilterImpl.java index 639b99d5f6..ec7737383a 100644 --- a/core/src/main/java/jenkins/security/ClassFilterImpl.java +++ b/core/src/main/java/jenkins/security/ClassFilterImpl.java @@ -229,7 +229,8 @@ public class ClassFilterImpl extends ClassFilter { private static boolean isPluginManifest(Manifest mf) { Attributes attr = mf.getMainAttributes(); - return attr.getValue("Short-Name") != null && (attr.getValue("Plugin-Version") != null || attr.getValue("Jenkins-Version") != null); + return attr.getValue("Short-Name") != null && (attr.getValue("Plugin-Version") != null || attr.getValue("Jenkins-Version") != null) || + "true".equals(attr.getValue("Jenkins-ClassFilter-Whitelisted")); } @Override -- GitLab From 79481041974776746f71c4e5e68304c4cb91c71e Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 7 Dec 2017 10:08:39 -0500 Subject: [PATCH 0096/1380] [JENKINS-20474] Optimize away ACL construction and AuthorizationStrategy calls when checking permissions for SYSTEM. --- core/src/main/java/hudson/security/ACL.java | 15 +++- .../hudson/security/AccessControlled.java | 3 + .../test/java/hudson/security/ACLTest.java | 75 +++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/hudson/security/ACLTest.java diff --git a/core/src/main/java/hudson/security/ACL.java b/core/src/main/java/hudson/security/ACL.java index a2c463d97c..0efe770050 100644 --- a/core/src/main/java/hudson/security/ACL.java +++ b/core/src/main/java/hudson/security/ACL.java @@ -64,6 +64,9 @@ public abstract class ACL { */ public final void checkPermission(@Nonnull Permission p) { Authentication a = Jenkins.getAuthentication(); + if (a == SYSTEM) { + return; + } if(!hasPermission(a,p)) throw new AccessDeniedException2(a,p); } @@ -75,7 +78,11 @@ public abstract class ACL { * if the user doesn't have the permission. */ public final boolean hasPermission(@Nonnull Permission p) { - return hasPermission(Jenkins.getAuthentication(),p); + Authentication a = Jenkins.getAuthentication(); + if (a == SYSTEM) { + return true; + } + return hasPermission(a, p); } /** @@ -101,6 +108,9 @@ public abstract class ACL { public final void checkCreatePermission(@Nonnull ItemGroup c, @Nonnull TopLevelItemDescriptor d) { Authentication a = Jenkins.getAuthentication(); + if (a == SYSTEM) { + return; + } if (!hasCreatePermission(a, c, d)) { throw new AccessDeniedException(Messages.AccessDeniedException2_MissingPermission(a.getName(), Item.CREATE.group.title+"/"+Item.CREATE.name + Item.CREATE + "/" + d.getDisplayName())); @@ -136,6 +146,9 @@ public abstract class ACL { public final void checkCreatePermission(@Nonnull ViewGroup c, @Nonnull ViewDescriptor d) { Authentication a = Jenkins.getAuthentication(); + if (a == SYSTEM) { + return; + } if (!hasCreatePermission(a, c, d)) { throw new AccessDeniedException(Messages.AccessDeniedException2_MissingPermission(a.getName(), View.CREATE.group.title + "/" + View.CREATE.name + View.CREATE + "/" + d.getDisplayName())); diff --git a/core/src/main/java/hudson/security/AccessControlled.java b/core/src/main/java/hudson/security/AccessControlled.java index 50f977c349..5a2b246a2b 100644 --- a/core/src/main/java/hudson/security/AccessControlled.java +++ b/core/src/main/java/hudson/security/AccessControlled.java @@ -59,6 +59,9 @@ public interface AccessControlled { * @since FIXME */ default boolean hasPermission(@Nonnull Authentication a, @Nonnull Permission permission) { + if (a == ACL.SYSTEM) { + return true; + } return getACL().hasPermission(a, permission); } diff --git a/test/src/test/java/hudson/security/ACLTest.java b/test/src/test/java/hudson/security/ACLTest.java new file mode 100644 index 0000000000..c97f01198b --- /dev/null +++ b/test/src/test/java/hudson/security/ACLTest.java @@ -0,0 +1,75 @@ +/* + * The MIT License + * + * Copyright 2017 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 hudson.security; + +import hudson.model.FreeStyleProject; +import hudson.model.Item; +import java.util.Collection; +import java.util.Collections; +import org.acegisecurity.Authentication; +import org.junit.Test; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +public class ACLTest { + + @Rule + public JenkinsRule r = new JenkinsRule(); + + @Issue("JENKINS-20474") + @Test + public void bypassStrategyOnSystem() throws Exception { + FreeStyleProject p = r.createFreeStyleProject(); + r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); + r.jenkins.setAuthorizationStrategy(new DoNotBotherMe()); + assertTrue(p.hasPermission(Item.CONFIGURE)); + assertTrue(p.hasPermission(ACL.SYSTEM, Item.CONFIGURE)); + p.checkPermission(Item.CONFIGURE); + p.checkAbortPermission(); + assertEquals(Collections.singletonList(p), r.jenkins.getAllItems()); + } + + private static class DoNotBotherMe extends AuthorizationStrategy { + + @Override + public ACL getRootACL() { + return new ACL() { + @Override + public boolean hasPermission(Authentication a, Permission permission) { + throw new AssertionError("should not have needed to check " + permission + " for " + a); + } + }; + } + + @Override + public Collection getGroups() { + return Collections.emptySet(); + } + + } + +} -- GitLab From 0463f0aa82ae8ac1f5e38c229d07b4dbc3155536 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Fri, 8 Dec 2017 10:13:36 +0100 Subject: [PATCH 0097/1380] Correction of quote for French + space before colon --- core/src/main/resources/hudson/model/View/newJob.jelly | 2 +- core/src/main/resources/hudson/model/View/newJob.properties | 2 +- .../src/main/resources/hudson/model/View/newJob_bg.properties | 4 ++-- .../src/main/resources/hudson/model/View/newJob_fr.properties | 4 ++-- .../src/main/resources/hudson/model/View/newJob_lt.properties | 2 +- .../src/main/resources/hudson/model/View/newJob_pl.properties | 2 +- .../src/main/resources/hudson/model/View/newJob_sr.properties | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/main/resources/hudson/model/View/newJob.jelly b/core/src/main/resources/hudson/model/View/newJob.jelly index ffd1f4e44e..e987f69eef 100644 --- a/core/src/main/resources/hudson/model/View/newJob.jelly +++ b/core/src/main/resources/hudson/model/View/newJob.jelly @@ -51,7 +51,7 @@ THE SOFTWARE.
-

${%CopyOption.description}:

+

${%CopyOption.description}

diff --git a/core/src/main/resources/hudson/model/View/newJob.properties b/core/src/main/resources/hudson/model/View/newJob.properties index 83629e60f9..4aa1ef73d6 100644 --- a/core/src/main/resources/hudson/model/View/newJob.properties +++ b/core/src/main/resources/hudson/model/View/newJob.properties @@ -5,7 +5,7 @@ ItemName.label=Enter an item name ItemName.validation.required=This field cannot be empty, please enter a valid name ItemType.validation.required=Please select an item type CopyOption.placeholder=Type to autocomplete -CopyOption.description=if you want to create a new item from other existing, you can use this option +CopyOption.description=If you want to create a new item from other existing, you can use this option: CopyOption.label=Copy from CopyExisting=Copy existing {0} diff --git a/core/src/main/resources/hudson/model/View/newJob_bg.properties b/core/src/main/resources/hudson/model/View/newJob_bg.properties index 34d0346a53..81b97d885b 100644 --- a/core/src/main/resources/hudson/model/View/newJob_bg.properties +++ b/core/src/main/resources/hudson/model/View/newJob_bg.properties @@ -30,9 +30,9 @@ NewJob=\ # This field cannot be empty, please enter a valid name ItemName.validation.required=\ \u041f\u043e\u043b\u0435\u0442\u043e \u043d\u0435 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0435 \u043f\u0440\u0430\u0437\u043d\u043e. \u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043a\u043e\u0440\u0435\u043a\u0442\u043d\u043e \u0438\u043c\u0435 -# if you want to create a new item from other existing, you can use this option +# If you want to create a new item from other existing, you can use this option: CopyOption.description=\ - \u0422\u043e\u0432\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0432\u0430 \u0434\u0430 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u0442\u0435 \u043d\u043e\u0432 \u043e\u0431\u0435\u043a\u0442 \u043d\u0430 \u0431\u0430\u0437\u0430\u0442\u0430 \u043d\u0430 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430\u0449. + \u0422\u043e\u0432\u0430 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0432\u0430 \u0434\u0430 \u0441\u044a\u0437\u0434\u0430\u0434\u0435\u0442\u0435 \u043d\u043e\u0432 \u043e\u0431\u0435\u043a\u0442 \u043d\u0430 \u0431\u0430\u0437\u0430\u0442\u0430 \u043d\u0430 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430\u0449: # Please select an item type ItemType.validation.required=\ \u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0438\u0434 \u043d\u0430 \u043e\u0431\u0435\u043a\u0442\u0430 diff --git a/core/src/main/resources/hudson/model/View/newJob_fr.properties b/core/src/main/resources/hudson/model/View/newJob_fr.properties index 27b0eb583b..e7d3a7f85f 100644 --- a/core/src/main/resources/hudson/model/View/newJob_fr.properties +++ b/core/src/main/resources/hudson/model/View/newJob_fr.properties @@ -26,7 +26,7 @@ CopyExisting=Copier un {0} existant ItemName.help=Champ obligatoire ItemName.label=Saisissez un nom ItemName.validation.required=Ce champ ne peut pas \u00eatre vide. Veuillez saisir un nom valide et appuyer sur OK. -ItemType.validation.required=Veuillez choisir un type d\'Item +ItemType.validation.required=Veuillez choisir un type d\u2019Item CopyOption.placeholder=Taper pour autocompl\u00e9tion -CopyOption.description=Si vous voulez cr\u00e9er un nouvel item \u00e0 partir d\'un autre, vous pouvez utiliser cette option +CopyOption.description=Si vous voulez cr\u00e9er un nouvel item \u00e0 partir d\u2019un autre, vous pouvez utiliser cette option : CopyOption.label=Copier depuis diff --git a/core/src/main/resources/hudson/model/View/newJob_lt.properties b/core/src/main/resources/hudson/model/View/newJob_lt.properties index 53443de2c2..d3704ddd47 100644 --- a/core/src/main/resources/hudson/model/View/newJob_lt.properties +++ b/core/src/main/resources/hudson/model/View/newJob_lt.properties @@ -5,7 +5,7 @@ ItemName.label=\u012eveskite elemento pavadinim\u0105 ItemName.validation.required=\u0160is laukas negali b\u016bti tu\u0161\u010dias. Pra\u0161ome \u012fvesti tinkam\u0105 pavadinim\u0105 ir spauskite mygtuk\u0105 \u201eGerai\u201c. ItemType.validation.required=Pra\u0161ome parinkti elemento tip\u0105 CopyOption.placeholder=Ra\u0161ykite automatiniam u\u017ebaigimui -CopyOption.description=galite naudoti \u0161i\u0105 parinkt\u012f, jei norite sukurti nauj\u0105 element\u0105 pagal kit\u0105, jau egzistuojant\u012f +CopyOption.description=galite naudoti \u0161i\u0105 parinkt\u012f, jei norite sukurti nauj\u0105 element\u0105 pagal kit\u0105, jau egzistuojant\u012f: CopyOption.label=Kopijuoti i\u0161 CopyExisting=Kopijuoti esam\u0105 {0} diff --git a/core/src/main/resources/hudson/model/View/newJob_pl.properties b/core/src/main/resources/hudson/model/View/newJob_pl.properties index fe4e021424..d6e9d810b6 100644 --- a/core/src/main/resources/hudson/model/View/newJob_pl.properties +++ b/core/src/main/resources/hudson/model/View/newJob_pl.properties @@ -26,5 +26,5 @@ ItemName.label=Podaj nazw\u0119 projektu ItemName.validation.required=To pole nie mo\u017Ce by\u0107 puste, podaj nazw\u0119 projektu ItemType.validation.required=Wybierz rodzaj projektu CopyOption.placeholder=Podaj nazw\u0119 -CopyOption.description=Je\u015Bli chcesz stworzy\u0107 nowy projekt na podstawie istniej\u0105cego, mo\u017Cesz u\u017Cy\u0107 tej opcji +CopyOption.description=Je\u015Bli chcesz stworzy\u0107 nowy projekt na podstawie istniej\u0105cego, mo\u017Cesz u\u017Cy\u0107 tej opcji: CopyOption.label=Kopiuj z diff --git a/core/src/main/resources/hudson/model/View/newJob_sr.properties b/core/src/main/resources/hudson/model/View/newJob_sr.properties index 8431e7c839..74c6c5afc8 100644 --- a/core/src/main/resources/hudson/model/View/newJob_sr.properties +++ b/core/src/main/resources/hudson/model/View/newJob_sr.properties @@ -5,7 +5,7 @@ ItemName.label=\u0423\u043D\u0435\u0441\u0438\u0442\u0435 \u0438\u043C\u0435 \u0 ItemName.help=\u041E\u0431\u0430\u0432\u0435\u0437\u043D\u043E \u043F\u043E\u0459\u0435 ItemName.validation.required=\u041F\u043E\u0459\u0435 \u043D\u0435 \u043C\u043E\u0436\u0435 \u0431\u0438\u0442\u0438 \u043F\u0440\u0430\u0437\u043D\u043E. \u0423\u043D\u0435\u0441\u0438\u0442\u0435 \u0432\u0430\u0436\u0435\u045B\u0435 \u0438\u043C\u0435. ItemType.validation.required=\u0423\u043D\u0435\u0441\u0438\u0442\u0435 \u0442\u0438\u043F \u043E\u0431\u0458\u0435\u043A\u0442\u0430 -CopyOption.description=\u043E\u0432\u043E \u0432\u0430\u043C \u043E\u043C\u043E\u0433\u0443\u045B\u0430\u0432\u0430 \u0434\u0430 \u043A\u0440\u0435\u0438\u0440\u0430\u0442\u0435 \u043D\u043E\u0432\u0438 \u043E\u0431\u0458\u0435\u043A\u0430\u0442 \u043D\u0430 \u043E\u0441\u043D\u043E\u0432\u0443 \u043F\u043E\u0441\u0442\u043E\u0458\u0435\u045B\u0435\u0433. +CopyOption.description=\u043E\u0432\u043E \u0432\u0430\u043C \u043E\u043C\u043E\u0433\u0443\u045B\u0430\u0432\u0430 \u0434\u0430 \u043A\u0440\u0435\u0438\u0440\u0430\u0442\u0435 \u043D\u043E\u0432\u0438 \u043E\u0431\u0458\u0435\u043A\u0430\u0442 \u043D\u0430 \u043E\u0441\u043D\u043E\u0432\u0443 \u043F\u043E\u0441\u0442\u043E\u0458\u0435\u045B\u0435\u0433.: CopyOption.label=\u041A\u043E\u043F\u0438\u0440\u0430\u0458 \u043E\u0434 CopyOption.placeholder=\u0423\u043D\u0435\u0448\u0435\u043D\u043E \u0438\u043C\u0435 \u045B\u0435 \u0441\u0435 \u0430\u0443\u0442\u043E\u043C\u0430\u0442\u0441\u043A\u0438 \u0434\u043E\u043F\u0438\u0441\u0430\u043D\u043E JobName=\u041D\u0430\u0437\u0438\u0432 {0} -- GitLab From bbec44313a378db102e1bcda163b9d9e002bd353 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 09:24:39 -0500 Subject: [PATCH 0098/1380] surefire.skipAfterFailureCount=100 --- test/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/test/pom.xml b/test/pom.xml index 2f87f51d5f..49ba42663a 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -320,6 +320,7 @@ THE SOFTWARE. true 4 + 100 -- GitLab From d3645f7dfe5acbef8c2a1e921e69b49f49f24581 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 10:44:58 -0500 Subject: [PATCH 0099/1380] Updating Powermock and Objenesis to avoid Mockito/Hamcrest library mismatches. --- cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java | 2 +- pom.xml | 9 +++++++-- test/pom.xml | 1 - 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java index 8697026508..d5636f3bd7 100644 --- a/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java +++ b/cli/src/test/java/hudson/cli/PrivateKeyProviderTest.java @@ -47,7 +47,7 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; @RunWith(PowerMockRunner.class) -@PrepareForTest(CLI.class) // When mocking new operator caller has to be @PreparedForTest, not class itself +@PrepareForTest({CLI.class, CLIConnectionFactory.class}) // When mocking new operator caller has to be @PreparedForTest, not class itself public class PrivateKeyProviderTest { @Test diff --git a/pom.xml b/pom.xml index c798586c2b..e5cf8ea367 100644 --- a/pom.xml +++ b/pom.xml @@ -153,12 +153,17 @@ THE SOFTWARE. org.powermock powermock-module-junit4 - 1.6.2 + 1.6.6 org.powermock powermock-api-mockito - 1.6.2 + 1.6.6 + + + org.objenesis + objenesis + 2.6 diff --git a/test/pom.xml b/test/pom.xml index 2f87f51d5f..e6a94a49c8 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -207,7 +207,6 @@ THE SOFTWARE. org.objenesis objenesis - 2.5.1 test -- GitLab From af95fef755b23c07f14e54eeb0ae86c91bb94299 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 12:22:23 -0500 Subject: [PATCH 0100/1380] Removing core/src/main/groovy/. --- core/pom.xml | 2 +- .../groovy/hudson/util/LoadMonitor.groovy | 116 ------------------ .../groovy/AbstractGroovyViewModule.groovy | 47 ------- .../util/groovy/AbstractGroovyViewModule.java | 48 ++++++++ pom.xml | 28 ----- test/pom.xml | 1 - 6 files changed, 49 insertions(+), 193 deletions(-) delete mode 100644 core/src/main/groovy/hudson/util/LoadMonitor.groovy delete mode 100644 core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy create mode 100644 core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java diff --git a/core/pom.xml b/core/pom.xml index a4b053eee5..5b8e70a1ee 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -799,10 +799,10 @@ THE SOFTWARE. org.codehaus.gmaven gmaven-plugin - + generateTestStubs testCompile diff --git a/core/src/main/groovy/hudson/util/LoadMonitor.groovy b/core/src/main/groovy/hudson/util/LoadMonitor.groovy deleted file mode 100644 index 97d7018161..0000000000 --- a/core/src/main/groovy/hudson/util/LoadMonitor.groovy +++ /dev/null @@ -1,116 +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 - -import hudson.model.Computer -import jenkins.util.Timer -import jenkins.model.Jenkins -import hudson.model.Label -import hudson.model.Queue.BlockedItem -import hudson.model.Queue.BuildableItem -import hudson.model.Queue.WaitingItem -import hudson.triggers.SafeTimerTask -import java.text.DateFormat -import java.util.concurrent.TimeUnit - -/** - * Spits out the load information. - * - *

- * I'm using this code to design the auto scaling feature. - * In future this might be useful data to expose to the UI. - * - * @author Kohsuke Kawaguchi - */ -public class LoadMonitorImpl extends SafeTimerTask { - - private final File dataFile; - private List labels; - - public LoadMonitorImpl(File dataFile) { - this.dataFile = dataFile; - labels = Jenkins.getInstance().labels*.name; - printHeaders(); - Timer.get().scheduleAtFixedRate(this,0,10*1000, TimeUnit.MILLISECONDS); - } - - private String quote(Object s) { "\"${s}\""; } - - protected void printHeaders() { - def headers = ["# of executors","# of busy executors","BuildableItems in Q","BuildableItem avg wait time"]; - def data = ["timestamp"]; - data += headers; - data += ["WaitingItems in Q","BlockedItems in Q"]; - - for( String label : labels) - data += headers.collect { "${it} (${label}}" } - - dataFile.append(data.collect({ quote(it) }).join(",")+"\n"); - } - - @Override - protected void doRun() { - def now = new Date(); - def data = []; - data.add(quote(FORMATTER.format(now))); - - def h = Jenkins.getInstance(); - - def items = h.queue.items; - def filterByType = {Class type -> items.findAll { type.isInstance(it) } } - - def builder = {List cs, Closure itemFilter -> - // number of total executor, number of busy executor - data.add(cs.sum { it.isOffline() ? 0 : it.numExecutors }); - data.add(cs.sum {Computer c -> - c.executors.findAll { !it.isIdle() }.size() - }); - - // queue statistics - def is = filterByType(BuildableItem).findAll(itemFilter); - data.add(is.size()); - data.add(is.sum {BuildableItem bi -> now.time - bi.buildableStartMilliseconds }?:0 / Math.max(1,is.size()) ); - }; - - - // for the whole thing - builder(Arrays.asList(h.computers),{ it->true }); - - data.add(filterByType(WaitingItem).size()); - data.add(filterByType(BlockedItem).size()); - - // per label stats - for (String label : labels) { - Label l = h.getLabel(label) - builder(l.nodes.collect { it.toComputer() }) { BuildableItem bi -> bi.task.assignedLabel==l }; - } - - dataFile.append(data.join(",")+"\n"); - } - - private static final DateFormat FORMATTER = DateFormat.getDateTimeInstance(); -} - -new LoadMonitorImpl(new File("/files/hudson/load.txt")); - diff --git a/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy b/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy deleted file mode 100644 index 08cb1c23a3..0000000000 --- a/core/src/main/groovy/jenkins/util/groovy/AbstractGroovyViewModule.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package jenkins.util.groovy - -import lib.FormTagLib -import lib.LayoutTagLib -import org.kohsuke.stapler.jelly.groovy.JellyBuilder -import org.kohsuke.stapler.jelly.groovy.Namespace -import lib.JenkinsTagLib - -/** - * Base class for utility classes for Groovy view scripts - *

- * Usage from script of a subclass, say ViewHelper: - *

- * new ViewHelper(delegate).method(); - *

- * see ModularizeViewScript in ui-samples for an example how to use this class. - * - * @author Stefan Wolf (wolfs) - */ -abstract class AbstractGroovyViewModule { - JellyBuilder builder - FormTagLib f - LayoutTagLib l - JenkinsTagLib t - Namespace st - - public AbstractGroovyViewModule(JellyBuilder b) { - builder = b - f= builder.namespace(FormTagLib) - l=builder.namespace(LayoutTagLib) - t=builder.namespace(JenkinsTagLib) - st=builder.namespace("jelly:stapler") - - } - - def methodMissing(String name, args) { - builder.invokeMethod(name,args) - } - - def propertyMissing(String name) { - builder.getProperty(name) - } - - def propertyMissing(String name, value) { - builder.setProperty(name, value) - } -} diff --git a/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java b/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java new file mode 100644 index 0000000000..9a18a67128 --- /dev/null +++ b/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java @@ -0,0 +1,48 @@ +package jenkins.util.groovy; + +import groovy.lang.GroovyObjectSupport; +import lib.FormTagLib; +import lib.LayoutTagLib; +import org.kohsuke.stapler.jelly.groovy.JellyBuilder; +import org.kohsuke.stapler.jelly.groovy.Namespace; +import lib.JenkinsTagLib; + +/** + * Base class for utility classes for Groovy view scripts + *

+ * Usage from script of a subclass, say ViewHelper: + *

+ * new ViewHelper(delegate).method(); + *

+ * see ModularizeViewScript in ui-samples for an example how to use + * this class. + */ +public abstract class AbstractGroovyViewModule extends GroovyObjectSupport { + + public JellyBuilder builder; + public FormTagLib f; + public LayoutTagLib l; + public JenkinsTagLib t; + public Namespace st; + + public AbstractGroovyViewModule(JellyBuilder b) { + builder = b; + f = builder.namespace(FormTagLib.class); + l = builder.namespace(LayoutTagLib.class); + t = builder.namespace(JenkinsTagLib.class); + st = builder.namespace("jelly:stapler"); + } + + public Object methodMissing(String name, Object args) { + return builder.invokeMethod(name, args); + } + + public Object propertyMissing(String name) { + return builder.getProperty(name); + } + + public void propertyMissing(String name, Object value) { + builder.setProperty(name, value); + } + +} diff --git a/pom.xml b/pom.xml index c798586c2b..9e95d80f7a 100644 --- a/pom.xml +++ b/pom.xml @@ -722,34 +722,6 @@ THE SOFTWARE. org.jenkins-ci.tools maven-jenkins-dev-plugin - - - - org.codehaus.gmaven - gmaven-plugin - - - - generateStubs - compile - generateTestStubs - testCompile - - - - - - diff --git a/test/pom.xml b/test/pom.xml index 2f87f51d5f..afa1599871 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -244,7 +244,6 @@ THE SOFTWARE. org.codehaus.gmaven gmaven-plugin - default -- GitLab From 4af739f2f29dae01d32ebe59f45c0252576a18e6 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 12:35:40 -0500 Subject: [PATCH 0101/1380] Javadoc error. --- .../java/jenkins/util/groovy/AbstractGroovyViewModule.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java b/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java index 9a18a67128..ccf4720293 100644 --- a/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java +++ b/core/src/main/java/jenkins/util/groovy/AbstractGroovyViewModule.java @@ -9,11 +9,11 @@ import lib.JenkinsTagLib; /** * Base class for utility classes for Groovy view scripts - *

+ *

* Usage from script of a subclass, say ViewHelper: - *

+ *

* new ViewHelper(delegate).method(); - *

+ *

* see ModularizeViewScript in ui-samples for an example how to use * this class. */ -- GitLab From dbdcaaf36be97fc77aabbc53a309edace51e9936 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 13:01:47 -0500 Subject: [PATCH 0102/1380] SecretRewriterTest --- .../java/hudson/util/HistoricalSecrets.java | 2 +- .../hudson/util/SecretRewriterTest.groovy | 120 ------------------ .../java/hudson/util/SecretRewriterTest.java | 116 +++++++++++++++++ 3 files changed, 117 insertions(+), 121 deletions(-) delete mode 100644 core/src/test/groovy/hudson/util/SecretRewriterTest.groovy create mode 100644 core/src/test/java/hudson/util/SecretRewriterTest.java diff --git a/core/src/main/java/hudson/util/HistoricalSecrets.java b/core/src/main/java/hudson/util/HistoricalSecrets.java index 37a6fa39e1..962ab26089 100644 --- a/core/src/main/java/hudson/util/HistoricalSecrets.java +++ b/core/src/main/java/hudson/util/HistoricalSecrets.java @@ -80,5 +80,5 @@ public class HistoricalSecrets { return Util.toAes128Key(secret); } - private static final String MAGIC = "::::MAGIC::::"; + static final String MAGIC = "::::MAGIC::::"; } diff --git a/core/src/test/groovy/hudson/util/SecretRewriterTest.groovy b/core/src/test/groovy/hudson/util/SecretRewriterTest.groovy deleted file mode 100644 index a429bef54e..0000000000 --- a/core/src/test/groovy/hudson/util/SecretRewriterTest.groovy +++ /dev/null @@ -1,120 +0,0 @@ -package hudson.util - -import com.trilead.ssh2.crypto.Base64 -import hudson.FilePath -import hudson.Functions -import jenkins.security.ConfidentialStoreRule -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder - -import javax.crypto.Cipher - -import static org.junit.Assume.assumeFalse - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class SecretRewriterTest { - @Rule - public MockSecretRule mockSecretRule = new MockSecretRule() - - @Rule - public ConfidentialStoreRule confidentialStoreRule = new ConfidentialStoreRule(); - - @Rule public TemporaryFolder tmp = new TemporaryFolder() - - def FOO_PATTERN = /\{[A-Za-z0-9+\/]+={0,2}}<\/foo>/ - def MSG_PATTERN = /\{[A-Za-z0-9+\/]+={0,2}}<\/msg>/ - def FOO_PATTERN2 = /(\{[A-Za-z0-9+\/]+={0,2}}<\/foo>){2}/ - def ABC_FOO_PATTERN = /\s\{[A-Za-z0-9+\/]+={0,2}}<\/foo>\s<\/abc>/ - - @Test - void singleFileRewrite() { - def o = encryptOld('foobar') // old - def n = encryptNew('foobar') // new - roundtrip "${o}", - {assert it ==~ FOO_PATTERN} - - - roundtrip "${o}${o}", - {assert it ==~ FOO_PATTERN2} - - roundtrip "${n}", - {assert it == "${n}"} - - roundtrip " thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret ", - {assert it == "thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret"} - - // to be rewritten, it needs to be between a tag - roundtrip "$o", {assert it == "$o"} - roundtrip "$o", {assert it == "$o"} - - // - roundtrip "\n$o\n", {assert it ==~ ABC_FOO_PATTERN} - } - - void roundtrip(String before, Closure check) { - def sr = new SecretRewriter(null); - def f = File.createTempFile("test", "xml", tmp.root) - f.text = before - sr.rewrite(f,null) - check(f.text.replaceAll(System.getProperty("line.separator"), "\n").trim()) - //assert after.replaceAll(System.getProperty("line.separator"), "\n").trim()==f.text.replaceAll(System.getProperty("line.separator"), "\n").trim() - } - - String encryptOld(str) { - def cipher = Secret.getCipher("AES"); - cipher.init(Cipher.ENCRYPT_MODE, HistoricalSecrets.legacyKey); - return new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8")))) - } - - String encryptNew(str) { - return Secret.fromString(str).encryptedValue - } - - /** - * Directory rewrite and recursion detection - */ - @Test - void recursionDetection() { - assumeFalse("Symlinks don't work on Windows very well", Functions.isWindows()) - def sw = new SecretRewriter(); - def st = StreamTaskListener.fromStdout() - - def o = encryptOld("Hello world") - def n = encryptNew("Hello world") - def payload = "$o" - def answer = "$n" - - // set up some directories with stuff - def t = tmp.newFolder("t") - def dirs = ["a", "b", "c", "c/d", "c/d/e"] - dirs.each { p -> - def d = new File(t, p) - d.mkdir() - new File(d,"foo.xml").text = payload - } - - // stuff outside - def t2 = tmp.newFolder("t2") - new File(t2,"foo.xml").text = payload - - // some recursions as well as valid symlinks - new FilePath(t).child("c/symlink").symlinkTo("..",st) - new FilePath(t).child("b/symlink").symlinkTo(".",st) - new FilePath(t).child("a/symlink").symlinkTo(t2.absolutePath,st) - - assert 6==sw.rewriteRecursive(t, st) - - dirs.each { p-> - assert new File(t,"$p/foo.xml").text.trim() ==~ MSG_PATTERN - } - - // t2 is only reachable by following a symlink. this should be covered, too - assert new File(t2,"foo.xml").text.trim() ==~ MSG_PATTERN - } - -} diff --git a/core/src/test/java/hudson/util/SecretRewriterTest.java b/core/src/test/java/hudson/util/SecretRewriterTest.java new file mode 100644 index 0000000000..5ca97ff060 --- /dev/null +++ b/core/src/test/java/hudson/util/SecretRewriterTest.java @@ -0,0 +1,116 @@ +package hudson.util; + +import com.trilead.ssh2.crypto.Base64; +import hudson.FilePath; +import hudson.Functions; +import hudson.model.TaskListener; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import javax.crypto.Cipher; +import jenkins.security.ConfidentialStoreRule; +import org.apache.commons.io.FileUtils; +import static org.junit.Assert.*; +import static org.junit.Assume.*; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class SecretRewriterTest { + + @Rule + public MockSecretRule mockSecretRule = new MockSecretRule(); + + @Rule + public ConfidentialStoreRule confidentialStoreRule = new ConfidentialStoreRule(); + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private static final Pattern FOO_PATTERN = Pattern.compile("[{][A-Za-z0-9+/]+={0,2}[}]"); + private static final Pattern MSG_PATTERN = Pattern.compile("[{][A-Za-z0-9+/]+={0,2}[}]"); + private static final Pattern FOO_PATTERN2 = Pattern.compile("([{][A-Za-z0-9+/]+={0,2}[}]){2}"); + private static final Pattern ABC_FOO_PATTERN = Pattern.compile("\\s[{][A-Za-z0-9+/]+={0,2}[}]\\s"); + + @Test + public void singleFileRewrite() throws Exception { + String o = encryptOld("foobar"); // old + String n = encryptNew("foobar"); // new + assertTrue(FOO_PATTERN.matcher(roundtrip("" + o + "")).matches()); + assertTrue(FOO_PATTERN2.matcher(roundtrip("" + o + "" + o + "")).matches()); + assertEquals("" + n + "", roundtrip("" + n + "")); + assertEquals("thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret", roundtrip(" thisIsLegalBase64AndLongEnoughThatItCouldLookLikeSecret ")); + // to be rewritten, it needs to be between a tag + assertEquals("" + o, roundtrip("" + o)); + assertEquals(o + "", roundtrip(o + "")); + assertTrue(ABC_FOO_PATTERN.matcher(roundtrip("\n" + o + "\n")).matches()); + } + + private String roundtrip(String before) throws Exception { + SecretRewriter sr = new SecretRewriter(null); + File f = File.createTempFile("test", "xml", tmp.getRoot()); + FileUtils.write(f, before); + sr.rewrite(f, null); + //assert after.replaceAll(System.getProperty("line.separator"), "\n").trim()==f.text.replaceAll(System.getProperty("line.separator"), "\n").trim() + return FileUtils.readFileToString(f).replaceAll(System.getProperty("line.separator"), "\n").trim(); + } + + private String encryptOld(String str) throws Exception { + Cipher cipher = Secret.getCipher("AES"); + cipher.init(Cipher.ENCRYPT_MODE, HistoricalSecrets.getLegacyKey()); + return new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8")))); + } + + private String encryptNew(String str) { + return Secret.fromString(str).getEncryptedValue(); + } + + /** + * Directory rewrite and recursion detection + */ + @Test + public void recursionDetection() throws Exception { + assumeFalse("Symlinks don't work on Windows very well", Functions.isWindows()); + SecretRewriter sw = new SecretRewriter(); + TaskListener st = StreamTaskListener.fromStdout(); + + String o = encryptOld("Hello world"); + String n = encryptNew("Hello world"); + String payload = "" + o + ""; + String answer = "" + n + ""; + + // set up some directories with stuff + File t = tmp.newFolder("t"); + List dirs = Arrays.asList("a", "b", "c", "c/d", "c/d/e"); + for (String p : dirs) { + File d = new File(t, p); + d.mkdir(); + try { + FileUtils.write(new File(d, "foo.xml"), payload); + } catch (IOException x) { + assert false : x; + } + } + + // stuff outside + File t2 = tmp.newFolder("t2"); + FileUtils.write(new File(t2, "foo.xml"), payload); + + // some recursions as well as valid symlinks + new FilePath(t).child("c/symlink").symlinkTo("..", st); + new FilePath(t).child("b/symlink").symlinkTo(".", st); + new FilePath(t).child("a/symlink").symlinkTo(t2.getAbsolutePath(), st); + + assertEquals(6, sw.rewriteRecursive(t, st)); + + for (String p : dirs) { + assertTrue(MSG_PATTERN.matcher(FileUtils.readFileToString(new File(t, p + "/foo.xml")).trim()).matches()); + } + + // t2 is only reachable by following a symlink. this should be covered, too + assertTrue(MSG_PATTERN.matcher(FileUtils.readFileToString(new File(t2, "foo.xml")).trim()).matches()); + } + +} -- GitLab From 5b0cca77050d91dbe8d117aeffe8426e3bef858a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 13:19:33 -0500 Subject: [PATCH 0103/1380] SecretTest --- .../hudson/util/SecretTest.java} | 94 ++++++++++--------- 1 file changed, 48 insertions(+), 46 deletions(-) rename core/src/test/{groovy/hudson/util/SecretTest.groovy => java/hudson/util/SecretTest.java} (55%) diff --git a/core/src/test/groovy/hudson/util/SecretTest.groovy b/core/src/test/java/hudson/util/SecretTest.java similarity index 55% rename from core/src/test/groovy/hudson/util/SecretTest.groovy rename to core/src/test/java/hudson/util/SecretTest.java index 8f39ae7ec0..0b9c13b9b6 100644 --- a/core/src/test/groovy/hudson/util/SecretTest.groovy +++ b/core/src/test/java/hudson/util/SecretTest.java @@ -21,51 +21,51 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package hudson.util -import com.trilead.ssh2.crypto.Base64; -import jenkins.model.Jenkins -import jenkins.security.ConfidentialStoreRule; -import org.apache.commons.lang.RandomStringUtils; -import org.junit.Rule -import org.junit.Test +package hudson.util; +import com.trilead.ssh2.crypto.Base64; import java.util.Random; -import javax.crypto.Cipher import java.util.regex.Pattern; +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import jenkins.model.Jenkins; +import jenkins.security.ConfidentialStoreRule; +import org.apache.commons.lang.RandomStringUtils; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; -/** - * @author Kohsuke Kawaguchi - */ public class SecretTest { + @Rule - public ConfidentialStoreRule confidentialStore = new ConfidentialStoreRule() + public ConfidentialStoreRule confidentialStore = new ConfidentialStoreRule(); @Rule - public MockSecretRule mockSecretRule = new MockSecretRule() + public MockSecretRule mockSecretRule = new MockSecretRule(); - static final Pattern ENCRYPTED_VALUE_PATTERN = Pattern.compile("\\{?[A-Za-z0-9+/]+={0,2}}?"); + private static final Pattern ENCRYPTED_VALUE_PATTERN = Pattern.compile("\\{?[A-Za-z0-9+/]+={0,2}}?"); @Test - void testEncrypt() { - def secret = Secret.fromString("abc"); - assert "abc"==secret.plainText; + public void encrypt() { + Secret secret = Secret.fromString("abc"); + assertEquals("abc", secret.getPlainText()); // make sure we got some encryption going - println secret.encryptedValue; - assert !"abc".equals(secret.encryptedValue); + assertNotEquals("abc", secret.getEncryptedValue()); // can we round trip? - assert secret==Secret.fromString(secret.encryptedValue); + assertEquals(secret, Secret.fromString(secret.getEncryptedValue())); //Two consecutive encryption requests of the same object should result in the same encrypted value - SECURITY-304 - assert secret.encryptedValue == secret.encryptedValue + assertEquals(secret.getEncryptedValue(), secret.getEncryptedValue()); //Two consecutive encryption requests of different objects with the same value should not result in the same encrypted value - SECURITY-304 - assert secret.encryptedValue != Secret.fromString(secret.plainText).encryptedValue + assertNotEquals(secret.getEncryptedValue(), Secret.fromString(secret.getPlainText()).getEncryptedValue()); } @Test - void testEncryptedValuePattern() { + public void encryptedValuePattern() { for (int i = 1; i < 100; i++) { String plaintext = RandomStringUtils.random(new Random().nextInt(i)); String ciphertext = Secret.fromString(plaintext).getEncryptedValue(); @@ -83,19 +83,20 @@ public class SecretTest { } @Test - void testDecrypt() { - assert "abc"==Secret.toString(Secret.fromString("abc")) + public void decrypt() { + assertEquals("abc", Secret.toString(Secret.fromString("abc"))); } @Test - void testSerialization() { - def s = Secret.fromString("Mr.Jenkins"); - def xml = Jenkins.XSTREAM.toXML(s); - assert !xml.contains(s.plainText) - assert xml ==~ /\{[A-Za-z0-9+\/]+={0,2}}<\/hudson\.util\.Secret>/ - - def o = Jenkins.XSTREAM.fromXML(xml); - assert o==s : xml; + public void serialization() { + Secret s = Secret.fromString("Mr.Jenkins"); + String xml = Jenkins.XSTREAM.toXML(s); + assertThat(xml, not(containsString(s.getPlainText()))); + // TODO MatchesPattern not available until Hamcrest 2.0 + assertTrue(xml, xml.matches("[{][A-Za-z0-9+/]+={0,2}[}]")); + + Object o = Jenkins.XSTREAM.fromXML(xml); + assertEquals(xml, s, o); } public static class Foo { @@ -106,27 +107,28 @@ public class SecretTest { * Makes sure the serialization form is backward compatible with String. */ @Test - void testCompatibilityFromString() { - def tagName = Foo.class.name.replace("\$","_-"); - def xml = "<$tagName>secret"; - def foo = new Foo(); + public void testCompatibilityFromString() { + String tagName = Foo.class.getName().replace("$", "_-"); + String xml = "<" + tagName + ">secret"; + Foo foo = new Foo(); Jenkins.XSTREAM.fromXML(xml, foo); - assert "secret"==Secret.toString(foo.password) + assertEquals("secret", Secret.toString(foo.password)); } /** * Secret persisted with Jenkins.getSecretKey() should still decrypt OK. */ @Test - void migrationFromLegacyKeyToConfidentialStore() { - def legacy = HistoricalSecrets.legacyKey - ["Hello world","","\u0000unprintable"].each { str -> - def cipher = Secret.getCipher("AES"); + public void migrationFromLegacyKeyToConfidentialStore() throws Exception { + SecretKey legacy = HistoricalSecrets.getLegacyKey(); + for (String str : new String[] {"Hello world", "", "\u0000unprintable"}) { + Cipher cipher = Secret.getCipher("AES"); cipher.init(Cipher.ENCRYPT_MODE, legacy); - def old = new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8")))) - def s = Secret.fromString(old) - assert s.plainText==str : "secret by the old key should decrypt" - assert s.encryptedValue!=old : "but when encrypting, ConfidentialKey should be in use" + String old = new String(Base64.encode(cipher.doFinal((str + HistoricalSecrets.MAGIC).getBytes("UTF-8")))); + Secret s = Secret.fromString(old); + assertEquals("secret by the old key should decrypt", str, s.getPlainText()); + assertNotEquals("but when encrypting, ConfidentialKey should be in use", old, s.getEncryptedValue()); } } + } -- GitLab From 27c83f9c4f827faa8bdc29b20e755c6ff63ad51a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 13:25:49 -0500 Subject: [PATCH 0104/1380] CryptoConfidentialKeyTest --- .../security/CryptoConfidentialKeyTest.groovy | 37 ------------------- .../security/CryptoConfidentialKeyTest.java | 35 ++++++++++++++++++ 2 files changed, 35 insertions(+), 37 deletions(-) delete mode 100644 core/src/test/groovy/jenkins/security/CryptoConfidentialKeyTest.groovy create mode 100644 core/src/test/java/jenkins/security/CryptoConfidentialKeyTest.java diff --git a/core/src/test/groovy/jenkins/security/CryptoConfidentialKeyTest.groovy b/core/src/test/groovy/jenkins/security/CryptoConfidentialKeyTest.groovy deleted file mode 100644 index e3816a342c..0000000000 --- a/core/src/test/groovy/jenkins/security/CryptoConfidentialKeyTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package jenkins.security - -import org.junit.Rule -import org.junit.Test - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class CryptoConfidentialKeyTest { - @Rule - public ConfidentialStoreRule store = new ConfidentialStoreRule() - - def key = new CryptoConfidentialKey("test") - - @Test - void decryptGetsPlainTextBack() { - ["Hello world","","\u0000"].each { str -> - assert key.decrypt().doFinal(key.encrypt().doFinal(str.bytes))==str.bytes - } - } - - @Test - void multipleEncryptsAreIdempotent() { - def str = "Hello world".bytes - assert key.encrypt().doFinal(str)==key.encrypt().doFinal(str) - } - - @Test - void loadingExistingKey() { - def key2 = new CryptoConfidentialKey("test") // this will cause the key to be loaded from the disk - ["Hello world","","\u0000"].each { str -> - assert key2.decrypt().doFinal(key.encrypt().doFinal(str.bytes))==str.bytes - } - } -} diff --git a/core/src/test/java/jenkins/security/CryptoConfidentialKeyTest.java b/core/src/test/java/jenkins/security/CryptoConfidentialKeyTest.java new file mode 100644 index 0000000000..1b302a358b --- /dev/null +++ b/core/src/test/java/jenkins/security/CryptoConfidentialKeyTest.java @@ -0,0 +1,35 @@ +package jenkins.security; + +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; + +public class CryptoConfidentialKeyTest { + + @Rule + public ConfidentialStoreRule store = new ConfidentialStoreRule(); + + private CryptoConfidentialKey key = new CryptoConfidentialKey("test"); + + @Test + public void decryptGetsPlainTextBack() throws Exception { + for (String str : new String[] {"Hello world", "", "\u0000"}) { + assertArrayEquals(str.getBytes(), key.decrypt().doFinal(key.encrypt().doFinal(str.getBytes()))); + } + } + + @Test + public void multipleEncryptsAreIdempotent() throws Exception { + byte[] str = "Hello world".getBytes(); + assertArrayEquals(key.encrypt().doFinal(str), key.encrypt().doFinal(str)); + } + + @Test + public void loadingExistingKey() throws Exception { + CryptoConfidentialKey key2 = new CryptoConfidentialKey("test"); // this will cause the key to be loaded from the disk + for (String str : new String[] {"Hello world", "", "\u0000"}) { + assertArrayEquals(str.getBytes(), key2.decrypt().doFinal(key.encrypt().doFinal(str.getBytes()))); + } + } + +} -- GitLab From 5d7ea02e572c6883fb75fe7c03295309756dde44 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 13:31:54 -0500 Subject: [PATCH 0105/1380] DefaultConfidentialStoreTest --- .../DefaultConfidentialStoreTest.groovy | 54 ------------------- .../DefaultConfidentialStoreTest.java | 47 ++++++++++++++++ 2 files changed, 47 insertions(+), 54 deletions(-) delete mode 100644 core/src/test/groovy/jenkins/security/DefaultConfidentialStoreTest.groovy create mode 100644 core/src/test/java/jenkins/security/DefaultConfidentialStoreTest.java diff --git a/core/src/test/groovy/jenkins/security/DefaultConfidentialStoreTest.groovy b/core/src/test/groovy/jenkins/security/DefaultConfidentialStoreTest.groovy deleted file mode 100644 index 83cdaf218c..0000000000 --- a/core/src/test/groovy/jenkins/security/DefaultConfidentialStoreTest.groovy +++ /dev/null @@ -1,54 +0,0 @@ -package jenkins.security - -import hudson.FilePath -import hudson.Functions -import hudson.Util -import org.junit.After -import org.junit.Before -import org.junit.Test - -/** - * @author Kohsuke Kawaguchi - */ -public class DefaultConfidentialStoreTest { - - def tmp; - - @Before - void setup() { - tmp = Util.createTempDir() - } - - @After - void tearDown() { - Util.deleteRecursive(tmp) - } - - @Test - void roundtrip() { - tmp.deleteDir() // let ConfidentialStore create a directory - - def store = new DefaultConfidentialStore(tmp); - def key = new ConfidentialKey("test") {}; - - // basic roundtrip - def str = "Hello world!" - store.store(key, str.bytes) - assert new String(store.load(key))==str - - // data storage should have some stuff - assert new File(tmp,"test").exists() - assert new File(tmp,"master.key").exists() - - assert !new File(tmp,"test").text.contains("Hello") // the data shouldn't be a plain text, obviously - - if (!Functions.isWindows()) - assert (new FilePath(tmp).mode()&0777) == 0700 // should be read only - - // if the master key changes, we should gracefully fail to load the store - new File(tmp,"master.key").delete() - def store2 = new DefaultConfidentialStore(tmp) - assert new File(tmp,"master.key").exists() // we should have a new key now - assert store2.load(key)==null; - } -} diff --git a/core/src/test/java/jenkins/security/DefaultConfidentialStoreTest.java b/core/src/test/java/jenkins/security/DefaultConfidentialStoreTest.java new file mode 100644 index 0000000000..539b72ddc4 --- /dev/null +++ b/core/src/test/java/jenkins/security/DefaultConfidentialStoreTest.java @@ -0,0 +1,47 @@ +package jenkins.security; + +import hudson.FilePath; +import hudson.Functions; +import java.io.File; +import org.apache.commons.io.FileUtils; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class DefaultConfidentialStoreTest { + + @Rule + public TemporaryFolder tmpRule = new TemporaryFolder(); + + @Test + public void roundtrip() throws Exception { + File tmp = new File(tmpRule.getRoot(), "tmp"); // let ConfidentialStore create a directory + + DefaultConfidentialStore store = new DefaultConfidentialStore(tmp); + ConfidentialKey key = new ConfidentialKey("test") {}; + + // basic roundtrip + String str = "Hello world!"; + store.store(key, str.getBytes()); + assertEquals(str, new String(store.load(key))); + + // data storage should have some stuff + assertTrue(new File(tmp, "test").exists()); + assertTrue(new File(tmp, "master.key").exists()); + + assertThat(FileUtils.readFileToString(new File(tmp, "test")), not(containsString("Hello"))); // the data shouldn't be a plain text, obviously + + if (!Functions.isWindows()) { + assertEquals(0700, new FilePath(tmp).mode() & 0777); // should be read only + } + + // if the master key changes, we should gracefully fail to load the store + new File(tmp, "master.key").delete(); + DefaultConfidentialStore store2 = new DefaultConfidentialStore(tmp); + assertTrue(new File(tmp, "master.key").exists()); // we should have a new key now + assertNull(store2.load(key)); + } + +} -- GitLab From e094f01edaeaf878be61e4775ea6ea20aea8f234 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 13:37:22 -0500 Subject: [PATCH 0106/1380] Removing SleepBuilderTest: https://github.com/jenkinsci/jenkins-test-harness/pull/90 --- .../jvnet/hudson/test/SleepBuilderTest.groovy | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 test/src/test/groovy/org/jvnet/hudson/test/SleepBuilderTest.groovy diff --git a/test/src/test/groovy/org/jvnet/hudson/test/SleepBuilderTest.groovy b/test/src/test/groovy/org/jvnet/hudson/test/SleepBuilderTest.groovy deleted file mode 100644 index a50e3e8fea..0000000000 --- a/test/src/test/groovy/org/jvnet/hudson/test/SleepBuilderTest.groovy +++ /dev/null @@ -1,22 +0,0 @@ -package org.jvnet.hudson.test - -import org.junit.Rule -import org.junit.Test - -/** - * @author schristou@cloudbees.com - * Date: 9/17/13 - * Time: 11:43 AM - * To change this template use File | Settings | File Templates. - */ -class SleepBuilderTest { - @Rule public JenkinsRule j = new JenkinsRule(); - @Test - void testPerform() { - def project = j.createFreeStyleProject(); - def builder = new SleepBuilder(30) - project.buildersList.add(builder); - j.configRoundtrip(project); - j.assertEqualDataBoundBeans(project.buildersList.get(SleepBuilder.class), builder); - } -} -- GitLab From 1cad7b5ff2b03ef859e08614f8d294f466bcf44b Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 14:00:05 -0500 Subject: [PATCH 0107/1380] HMACConfidentialKeyTest --- .../security/HMACConfidentialKeyTest.groovy | 39 ------------------- .../security/HMACConfidentialKeyTest.java | 38 ++++++++++++++++++ 2 files changed, 38 insertions(+), 39 deletions(-) delete mode 100644 core/src/test/groovy/jenkins/security/HMACConfidentialKeyTest.groovy create mode 100644 core/src/test/java/jenkins/security/HMACConfidentialKeyTest.java diff --git a/core/src/test/groovy/jenkins/security/HMACConfidentialKeyTest.groovy b/core/src/test/groovy/jenkins/security/HMACConfidentialKeyTest.groovy deleted file mode 100644 index 118ff5b904..0000000000 --- a/core/src/test/groovy/jenkins/security/HMACConfidentialKeyTest.groovy +++ /dev/null @@ -1,39 +0,0 @@ -package jenkins.security - -import org.junit.Rule -import org.junit.Test - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class HMACConfidentialKeyTest { - @Rule - public ConfidentialStoreRule store = new ConfidentialStoreRule() - - def key = new HMACConfidentialKey("test",16) - - @Test - void basics() { - def unique = [] as TreeSet - ["Hello world","","\u0000"].each { str -> - def mac = key.mac(str) - unique.add(mac) - assert mac =~ /[0-9A-Fa-f]{32}/ - assert key.checkMac(str,mac) - assert !key.checkMac("garbage",mac) - } - - assert unique.size()==3 // make sure all 3 MAC are different - } - - @Test - void loadingExistingKey() { - // this second key of the same ID will cause it to load the key from the disk - def key2 = new HMACConfidentialKey("test",16) - ["Hello world","","\u0000"].each { str -> - assert key.mac(str)==key2.mac(str) - } - } -} diff --git a/core/src/test/java/jenkins/security/HMACConfidentialKeyTest.java b/core/src/test/java/jenkins/security/HMACConfidentialKeyTest.java new file mode 100644 index 0000000000..93cb94cfdb --- /dev/null +++ b/core/src/test/java/jenkins/security/HMACConfidentialKeyTest.java @@ -0,0 +1,38 @@ +package jenkins.security; + +import java.util.Set; +import java.util.TreeSet; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; + +public class HMACConfidentialKeyTest { + + @Rule + public ConfidentialStoreRule store = new ConfidentialStoreRule(); + + private HMACConfidentialKey key = new HMACConfidentialKey("test", 16); + + @Test + public void basics() { + Set unique = new TreeSet<>(); + for (String str : new String[] {"Hello world", "", "\u0000"}) { + String mac = key.mac(str); + unique.add(mac); + assertTrue(mac, mac.matches("[0-9A-Fa-f]{32}")); + assertTrue(key.checkMac(str, mac)); + assertFalse(key.checkMac("garbage", mac)); + } + assertEquals("all 3 MAC are different", 3, unique.size()); + } + + @Test + public void loadingExistingKey() { + // this second key of the same ID will cause it to load the key from the disk + HMACConfidentialKey key2 = new HMACConfidentialKey("test", 16); + for (String str : new String[] {"Hello world", "", "\u0000"}) { + assertEquals(key.mac(str), key2.mac(str)); + } + } + +} -- GitLab From 9585f94cfc13cbc0289d6d7f4bfc9baf60f1333f Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Fri, 8 Dec 2017 14:33:12 -0500 Subject: [PATCH 0108/1380] [oss-2590] updated dependencies to support move to xml 1.1 --- core/pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index a4b053eee5..cdd4e104ff 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -240,6 +240,11 @@ THE SOFTWARE. + + net.sf.kxml + kxml2 + 2.3.0 + jfree jfreechart @@ -446,11 +451,6 @@ THE SOFTWARE. spring-aop ${spring.version} - - xpp3 - xpp3 - 1.1.4c - junit junit -- GitLab From 39826800222af9e5753015c60ce3a9ac294144ef Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 15:46:17 -0500 Subject: [PATCH 0109/1380] HexStringConfidentialKeyTest --- .../HexStringConfidentialKeyTest.groovy | 43 ------------------- .../HexStringConfidentialKeyTest.java | 41 ++++++++++++++++++ 2 files changed, 41 insertions(+), 43 deletions(-) delete mode 100644 core/src/test/groovy/jenkins/security/HexStringConfidentialKeyTest.groovy create mode 100644 core/src/test/java/jenkins/security/HexStringConfidentialKeyTest.java diff --git a/core/src/test/groovy/jenkins/security/HexStringConfidentialKeyTest.groovy b/core/src/test/groovy/jenkins/security/HexStringConfidentialKeyTest.groovy deleted file mode 100644 index 7062d2326f..0000000000 --- a/core/src/test/groovy/jenkins/security/HexStringConfidentialKeyTest.groovy +++ /dev/null @@ -1,43 +0,0 @@ -package jenkins.security - -import org.junit.Rule -import org.junit.Test - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class HexStringConfidentialKeyTest { - @Rule - public ConfidentialStoreRule store = new ConfidentialStoreRule() - - @Test - void hexStringShouldProduceHexString() { - def key = new HexStringConfidentialKey("test",8) - assert key.get() =~ /[A-Fa-f0-9]{8}/ - } - - @Test - void multipleGetsAreIdempotent() { - def key = new HexStringConfidentialKey("test",8) - assert key.get()==key.get() - } - - @Test - void specifyLengthAndMakeSureItTakesEffect() { - [8,16,32,256].each { n -> - new HexStringConfidentialKey("test"+n,n).get().length()==n - } - } - - @Test - void loadingExistingKey() { - def key1 = new HexStringConfidentialKey("test",8) - key1.get() // this causes the ke to be generated - - // this second key of the same ID will cause it to load the key from the disk - def key2 = new HexStringConfidentialKey("test",8) - assert key1.get()==key2.get() - } -} diff --git a/core/src/test/java/jenkins/security/HexStringConfidentialKeyTest.java b/core/src/test/java/jenkins/security/HexStringConfidentialKeyTest.java new file mode 100644 index 0000000000..e76ad914f9 --- /dev/null +++ b/core/src/test/java/jenkins/security/HexStringConfidentialKeyTest.java @@ -0,0 +1,41 @@ +package jenkins.security; + +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; + +public class HexStringConfidentialKeyTest { + + @Rule + public ConfidentialStoreRule store = new ConfidentialStoreRule(); + + @Test + public void hexStringShouldProduceHexString() { + HexStringConfidentialKey key = new HexStringConfidentialKey("test", 8); + assertTrue(key.get().matches("[A-Fa-f0-9]{8}")); + } + + @Test + public void multipleGetsAreIdempotent() { + HexStringConfidentialKey key = new HexStringConfidentialKey("test", 8); + assertEquals(key.get(), key.get()); + } + + @Test + public void specifyLengthAndMakeSureItTakesEffect() { + for (int n : new int[] {8, 16, 32, 256}) { + assertEquals(n, new HexStringConfidentialKey("test" + n, n).get().length()); + } + } + + @Test + public void loadingExistingKey() { + HexStringConfidentialKey key1 = new HexStringConfidentialKey("test", 8); + key1.get(); // this causes the ke to be generated + + // this second key of the same ID will cause it to load the key from the disk + HexStringConfidentialKey key2 = new HexStringConfidentialKey("test", 8); + assertEquals(key1.get(), key2.get()); + } + +} -- GitLab From 046fca7e2f0845e0922277cb49a73e3dd0592dd8 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 15:49:41 -0500 Subject: [PATCH 0110/1380] RSAConfidentialKeyTest --- .../security/RSAConfidentialKeyTest.java} | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) rename core/src/test/{groovy/jenkins/security/RSAConfidentialKeyTest.groovy => java/jenkins/security/RSAConfidentialKeyTest.java} (74%) diff --git a/core/src/test/groovy/jenkins/security/RSAConfidentialKeyTest.groovy b/core/src/test/java/jenkins/security/RSAConfidentialKeyTest.java similarity index 74% rename from core/src/test/groovy/jenkins/security/RSAConfidentialKeyTest.groovy rename to core/src/test/java/jenkins/security/RSAConfidentialKeyTest.java index ed9ef183fe..7b4513940c 100644 --- a/core/src/test/groovy/jenkins/security/RSAConfidentialKeyTest.groovy +++ b/core/src/test/java/jenkins/security/RSAConfidentialKeyTest.java @@ -21,28 +21,26 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package jenkins.security +package jenkins.security; -import org.junit.Rule -import org.junit.Test +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; + +public class RSAConfidentialKeyTest { -/** - * - * - * @author Kohsuke Kawaguchi - */ -class RSAConfidentialKeyTest { @Rule - public ConfidentialStoreRule store = new ConfidentialStoreRule() + public ConfidentialStoreRule store = new ConfidentialStoreRule(); - def key = new RSAConfidentialKey("test") {} + private RSAConfidentialKey key = new RSAConfidentialKey("test") {}; @Test - void loadingExistingKey() { + public void loadingExistingKey() { // this second key of the same ID will cause it to load the key from the disk - def key2 = new RSAConfidentialKey("test") {} + RSAConfidentialKey key2 = new RSAConfidentialKey("test") {}; - assert key.privateKey==key2.privateKey; - assert key.publicKey ==key2.publicKey; + assertEquals(key.getPrivateKey(), key2.getPrivateKey()); + assertEquals(key.getPublicKey(), key2.getPublicKey()); } + } -- GitLab From a8251f66d50ae535d32d8b09da32a08b9baa96c3 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 15:51:44 -0500 Subject: [PATCH 0111/1380] RSADigitalSignatureConfidentialKeyTest --- ...ADigitalSignatureConfidentialKeyTest.java} | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) rename core/src/test/{groovy/jenkins/security/RSADigitalSignatureConfidentialKeyTest.groovy => java/jenkins/security/RSADigitalSignatureConfidentialKeyTest.java} (65%) diff --git a/core/src/test/groovy/jenkins/security/RSADigitalSignatureConfidentialKeyTest.groovy b/core/src/test/java/jenkins/security/RSADigitalSignatureConfidentialKeyTest.java similarity index 65% rename from core/src/test/groovy/jenkins/security/RSADigitalSignatureConfidentialKeyTest.groovy rename to core/src/test/java/jenkins/security/RSADigitalSignatureConfidentialKeyTest.java index 7c31c13365..16e57e117f 100644 --- a/core/src/test/groovy/jenkins/security/RSADigitalSignatureConfidentialKeyTest.groovy +++ b/core/src/test/java/jenkins/security/RSADigitalSignatureConfidentialKeyTest.java @@ -21,35 +21,32 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package jenkins.security +package jenkins.security; -import hudson.remoting.Base64 -import org.junit.Rule -import org.junit.Test +import hudson.remoting.Base64; +import java.security.Signature; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; -import java.security.Signature +public class RSADigitalSignatureConfidentialKeyTest { -/** - * - * - * @author Kohsuke Kawaguchi - */ -class RSADigitalSignatureConfidentialKeyTest { @Rule - public ConfidentialStoreRule store = new ConfidentialStoreRule() + public ConfidentialStoreRule store = new ConfidentialStoreRule(); - def key = new RSADigitalSignatureConfidentialKey("test"); + private final RSADigitalSignatureConfidentialKey key = new RSADigitalSignatureConfidentialKey("test"); @Test - void dsigSignAndVerify() { - def plainText = "Hello world" - def msg = key.sign(plainText); - println msg; + public void dsigSignAndVerify() throws Exception { + String plainText = "Hello world"; + String msg = key.sign(plainText); + System.out.println(msg); - def sig = Signature.getInstance("SHA256withRSA"); - sig.initVerify(key.publicKey); + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(key.getPublicKey()); sig.update(plainText.getBytes("UTF-8")); - assert sig.verify(Base64.decode(msg)) + assertTrue(sig.verify(Base64.decode(msg))); } + } -- GitLab From 9b419100afab9eae182de01935ea8c18482adba2 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 15:59:31 -0500 Subject: [PATCH 0112/1380] AtmostOneTaskExecutorTest --- core/pom.xml | 19 ------- .../util/AtmostOneTaskExecutorTest.groovy | 47 ------------------ .../util/AtmostOneTaskExecutorTest.java | 49 +++++++++++++++++++ 3 files changed, 49 insertions(+), 66 deletions(-) delete mode 100644 core/src/test/groovy/jenkins/util/AtmostOneTaskExecutorTest.groovy create mode 100644 core/src/test/java/jenkins/util/AtmostOneTaskExecutorTest.java diff --git a/core/pom.xml b/core/pom.xml index 5b8e70a1ee..46a63c7c81 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -796,25 +796,6 @@ THE SOFTWARE. - - org.codehaus.gmaven - gmaven-plugin - - - - generateTestStubs - testCompile - - - - - - org.codehaus.groovy - groovy-all - ${groovy.version} - - - org.codehaus.mojo findbugs-maven-plugin diff --git a/core/src/test/groovy/jenkins/util/AtmostOneTaskExecutorTest.groovy b/core/src/test/groovy/jenkins/util/AtmostOneTaskExecutorTest.groovy deleted file mode 100644 index e39addae77..0000000000 --- a/core/src/test/groovy/jenkins/util/AtmostOneTaskExecutorTest.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package jenkins.util - -import hudson.util.OneShotEvent -import org.junit.Test - -import java.util.concurrent.Callable -import java.util.concurrent.Executors -import java.util.concurrent.atomic.AtomicInteger - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class AtmostOneTaskExecutorTest { - def counter = new AtomicInteger() - - def lock = new OneShotEvent() - - @Test - public void doubleBooking() { - def f1,f2; - - def base = Executors.newCachedThreadPool() - def es = new AtmostOneTaskExecutor(base, - { -> - counter.incrementAndGet() - lock.block() - } as Callable); - f1 = es.submit() - while (counter.get() == 0) - ; // spin lock until executor gets to the choking point - - f2 = es.submit() // this should hang - Thread.sleep(500) // make sure the 2nd task is hanging - assert counter.get() == 1 - assert !f2.isDone() - - lock.signal() // let the first one go - - f1.get(); // first one should complete - - // now 2nd one gets going and hits the choke point - f2.get() - assert counter.get()==2 - } -} diff --git a/core/src/test/java/jenkins/util/AtmostOneTaskExecutorTest.java b/core/src/test/java/jenkins/util/AtmostOneTaskExecutorTest.java new file mode 100644 index 0000000000..c3e8b8461a --- /dev/null +++ b/core/src/test/java/jenkins/util/AtmostOneTaskExecutorTest.java @@ -0,0 +1,49 @@ +package jenkins.util; + +import hudson.util.OneShotEvent; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; +import static org.junit.Assert.*; +import org.junit.Test; + +public class AtmostOneTaskExecutorTest { + + @SuppressWarnings("empty-statement") + @Test + public void doubleBooking() throws Exception { + AtomicInteger counter = new AtomicInteger(); + OneShotEvent lock = new OneShotEvent(); + Future f1, f2; + + ExecutorService base = Executors.newCachedThreadPool(); + AtmostOneTaskExecutor es = new AtmostOneTaskExecutor(base, () -> { + counter.incrementAndGet(); + try { + lock.block(); + } catch (InterruptedException x) { + assert false : x; + } + return null; + }); + f1 = es.submit(); + while (counter.get() == 0) { + // spin lock until executor gets to the choking point + } + + f2 = es.submit(); // this should hang + Thread.sleep(500); // make sure the 2nd task is hanging + assertEquals(1, counter.get()); + assertFalse(f2.isDone()); + + lock.signal(); // let the first one go + + f1.get(); // first one should complete + + // now 2nd one gets going and hits the choke point + f2.get(); + assertEquals(2, counter.get()); + } + +} -- GitLab From 46c8f144716bfbcc5cd31a8eff64c14c5190708b Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 16:04:43 -0500 Subject: [PATCH 0113/1380] Deleting ancient cobertura profile, which was broken at least by Java 8 if not earlier. If we want code coverage we can use JaCoCo as plugin-pom does. --- core/pom.xml | 36 ----------- core/src/build-script/Cobertura.groovy | 82 -------------------------- core/src/build-script/readme.txt | 4 -- core/src/build-script/unitTest.groovy | 11 ---- 4 files changed, 133 deletions(-) delete mode 100644 core/src/build-script/Cobertura.groovy delete mode 100644 core/src/build-script/readme.txt delete mode 100644 core/src/build-script/unitTest.groovy diff --git a/core/pom.xml b/core/pom.xml index 46a63c7c81..fbc89c67de 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -870,41 +870,5 @@ THE SOFTWARE. true - - - cobertura - - - - org.codehaus.gmaven - gmaven-plugin - - - - - test - - execute - - - - ${project.basedir}/src/build-script - - ${project.basedir}/src/build-script/unitTest.groovy - - - - - - - maven-surefire-plugin - - - true - - - - - diff --git a/core/src/build-script/Cobertura.groovy b/core/src/build-script/Cobertura.groovy deleted file mode 100644 index 49b773b49b..0000000000 --- a/core/src/build-script/Cobertura.groovy +++ /dev/null @@ -1,82 +0,0 @@ -import org.apache.maven.project.MavenProject; - -/** - * Cobertura invoker. - */ -public class Cobertura { - private final MavenProject project; - // maven helper - private def maven; - // ant builder - private def ant; - /** - * Cobertura data file. - */ - private final File ser; - - def Cobertura(project, maven, ant, ser) { - this.project = maven.project; - this.maven = maven; - this.ant = ant; - this.ser =ser; - - // define cobertura tasks - ant.taskdef(resource:"tasks.properties") - } - - // function that ensures that the given directory exists - private String dir(String dir) { - new File(project.basedir,dir).mkdirs(); - return dir; - } - - /** - * Instruments the given class dirs/jars by cobertura - * - * @param files - * List of jar files and class dirs to instrument. - */ - def instrument(files) { - ant."cobertura-instrument"(todir:dir("target/cobertura-classes"),datafile:ser) { - fileset(dir:"target/classes"); - files.each{ fileset(file:it) } - } - } - - def runTests() { - ant.junit(fork:true, forkMode:"once", failureproperty:"failed", printsummary:true) { - classpath { - junitClasspath() - } - batchtest(todir:dir("target/surefire-reports")) { - fileset(dir:"target/test-classes") { - include(name:"**/*Test.class") - } - formatter(type:"xml") - } - sysproperty(key:"net.sourceforge.cobertura.datafile",value:ser) - sysproperty(key:"hudson.ClassicPluginStrategy.useAntClassLoader",value:"true") - } - } - - def junitClasspath() { - ant.pathelement(path: "target/cobertura-classes") // put the instrumented classes first - ant.fileset(dir:"target/cobertura-classes",includes:"*.jar") // instrumented jar files - ant.pathelement(path: maven.resolveArtifact("net.sourceforge.cobertura:cobertura:1.9")) // cobertura runtime - project.getTestClasspathElements().each { ant.pathelement(path: it) } // the rest of the dependencies - } - - def report(dirs) { - maven.attachArtifact(ser,"ser","cobertura") - ["html","xml"].each { format -> - ant."cobertura-report"(format:format,datafile:ser,destdir:dir("target/cobertura-reports"),srcdir:"src/main/java") { - dirs.each{ fileset(dir:it) } - } - } - } - - def makeBuildFailIfTestFail() { - if(ant.project.getProperty("failed")!=null && !Boolean.getBoolean("testFailureIgnore")) - throw new Exception("Some unit tests failed"); - } -} diff --git a/core/src/build-script/readme.txt b/core/src/build-script/readme.txt deleted file mode 100644 index 6c53a01026..0000000000 --- a/core/src/build-script/readme.txt +++ /dev/null @@ -1,4 +0,0 @@ -Parts of the build scripts that are written in Groovy. - -IntelliJ needs Groovy files to be in its source directory to provide completion and such, -so we need this to be in its own directory. \ No newline at end of file diff --git a/core/src/build-script/unitTest.groovy b/core/src/build-script/unitTest.groovy deleted file mode 100644 index 653391aad7..0000000000 --- a/core/src/build-script/unitTest.groovy +++ /dev/null @@ -1,11 +0,0 @@ -// run unit tests - -ant.project.setBaseDir(project.basedir) -ser=new File(project.basedir,"target/cobertura.ser"); // store cobertura data file in a module-specific location - -cob = new Cobertura(project,maven,ant,ser); - -cob.instrument([]) -cob.runTests() -cob.report([]) -cob.makeBuildFailIfTestFail(); -- GitLab From 9d43c3c9bce891854890046987f64770d38cda9a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 16:07:34 -0500 Subject: [PATCH 0114/1380] Obsolete m2e exclusion. --- pom.xml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/pom.xml b/pom.xml index 9e95d80f7a..3f0adde0c5 100644 --- a/pom.xml +++ b/pom.xml @@ -584,20 +584,6 @@ THE SOFTWARE. - - - org.codehaus.gmaven - gmaven-plugin - 1.5-jenkins-1 - - execute - testCompile - - - - - - org.apache.maven.plugins -- GitLab From e39007250a90291b0b83afaa32f05ddb902bb1dc Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 16:09:35 -0500 Subject: [PATCH 0115/1380] Cleaning up some more unusable Cobertura executions. --- pom.xml | 9 --------- test/pom.xml | 29 --------------------------- test/src/build-script/unitTest.groovy | 16 --------------- 3 files changed, 54 deletions(-) delete mode 100644 test/src/build-script/unitTest.groovy diff --git a/pom.xml b/pom.xml index 3f0adde0c5..52e85c8a2c 100644 --- a/pom.xml +++ b/pom.xml @@ -349,11 +349,6 @@ THE SOFTWARE. ant-junit 1.7.0 - - net.sourceforge.cobertura - cobertura - 1.9 - javax.servlet @@ -516,10 +511,6 @@ THE SOFTWARE. antlr-maven-plugin 2.1 - - org.codehaus.mojo - cobertura-maven-plugin - org.codehaus.mojo findbugs-maven-plugin diff --git a/test/pom.xml b/test/pom.xml index afa1599871..f0c8d40325 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -321,35 +321,6 @@ THE SOFTWARE. 4 - - - cobertura - - - - org.codehaus.gmaven - gmaven-plugin - - - - - test - - execute - - - - - ${project.basedir}/../core/src/build-script - - ${project.basedir}/src/build-script/unitTest.groovy - - - - - - - jacoco diff --git a/test/src/build-script/unitTest.groovy b/test/src/build-script/unitTest.groovy deleted file mode 100644 index 73e5a56465..0000000000 --- a/test/src/build-script/unitTest.groovy +++ /dev/null @@ -1,16 +0,0 @@ -// run unit tests -import org.apache.commons.io.FileUtils - -ant.project.setBaseDir(project.basedir) - -// start from where the core left off, and build from there -ser=new File(project.basedir,"target/cobertura.ser"); -FileUtils.copyFile(maven.resolveArtifact("${project.groupId}:hudson-core:${project.version}:cobertura:ser"),ser) - -cob = new Cobertura(project,maven,ant,ser); - -// instrumenting remote.jar causes all native m2 job to fail, because forked Maven doesn't have cobertura runtime -cob.instrument(["hudson-core"].collect{ m -> maven.resolveArtifact("${project.groupId}:${m}:${project.version}") }) -cob.runTests() -cob.report(["../core/src/main/java"]) -cob.makeBuildFailIfTestFail(); -- GitLab From aa5e776d9a61a290a11ba055d9ac114738098bbc Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 16:15:47 -0500 Subject: [PATCH 0116/1380] All gmaven usage is now contained in the test module. --- pom.xml | 28 ---------------------------- test/pom.xml | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/pom.xml b/pom.xml index 52e85c8a2c..469439f8a8 100644 --- a/pom.xml +++ b/pom.xml @@ -339,34 +339,6 @@ THE SOFTWARE. - - org.codehaus.gmaven - gmaven-plugin - 1.5-jenkins-3 - - - org.apache.ant - ant-junit - 1.7.0 - - - - javax.servlet - javax.servlet-api - 3.1.0 - - - org.codehaus.gmaven.runtime - gmaven-runtime-2.0 - 1.5-jenkins-3 - - - - - 2.0 - - org.apache.maven.plugins maven-deploy-plugin diff --git a/test/pom.xml b/test/pom.xml index f0c8d40325..6d70dd8805 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -244,6 +244,7 @@ THE SOFTWARE. org.codehaus.gmaven gmaven-plugin + 1.5-jenkins-3 default @@ -266,7 +267,28 @@ THE SOFTWARE. ant-launcher 1.8.0 + + org.apache.ant + ant-junit + 1.7.0 + + + + javax.servlet + javax.servlet-api + 3.1.0 + + + org.codehaus.gmaven.runtime + gmaven-runtime-2.0 + 1.5-jenkins-3 + + + + 2.0 + maven-deploy-plugin -- GitLab From 21ea7956f928c8567b1f113ea9b855a5f3ab04a9 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 16:19:22 -0500 Subject: [PATCH 0117/1380] Deleting GroovyTest, which only tested the ability to write Jenkins tests in Groovy, not Jenkins itself. --- test/src/test/groovy/hudson/GroovyTest.groovy | 45 ------------------- 1 file changed, 45 deletions(-) delete mode 100644 test/src/test/groovy/hudson/GroovyTest.groovy diff --git a/test/src/test/groovy/hudson/GroovyTest.groovy b/test/src/test/groovy/hudson/GroovyTest.groovy deleted file mode 100644 index e512cf56ef..0000000000 --- a/test/src/test/groovy/hudson/GroovyTest.groovy +++ /dev/null @@ -1,45 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, 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 hudson - -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.JenkinsRule - -/** - * First groovy test! - * - * @author Kohsuke Kawaguchi - */ -public class GroovyTest { - - @Rule - public JenkinsRule j = new JenkinsRule() - - @Test - void test() { - def wc = j.createWebClient(); - wc.goTo(""); - } -} -- GitLab From c3c01c0335e2f1f8d87fb4fb57bcdfd3c8124049 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 16:27:19 -0500 Subject: [PATCH 0118/1380] RelativePathTest --- .../groovy/hudson/RelativePathTest.groovy | 55 ------------------ .../test/java/hudson/RelativePathTest.java | 58 +++++++++++++++++++ 2 files changed, 58 insertions(+), 55 deletions(-) delete mode 100644 test/src/test/groovy/hudson/RelativePathTest.groovy create mode 100644 test/src/test/java/hudson/RelativePathTest.java diff --git a/test/src/test/groovy/hudson/RelativePathTest.groovy b/test/src/test/groovy/hudson/RelativePathTest.groovy deleted file mode 100644 index 25902f811c..0000000000 --- a/test/src/test/groovy/hudson/RelativePathTest.groovy +++ /dev/null @@ -1,55 +0,0 @@ -package hudson - -import hudson.model.AbstractDescribableImpl -import hudson.model.Describable -import hudson.model.Descriptor -import hudson.util.ListBoxModel -import org.jvnet.hudson.test.Issue -import org.jvnet.hudson.test.HudsonTestCase -import org.jvnet.hudson.test.TestExtension -import org.kohsuke.stapler.QueryParameter - -/** - * Regression test for JENKINS-18776 - * - * @author Kohsuke Kawaguchi - */ -class RelativePathTest extends HudsonTestCase implements Describable { - @Issue("JENKINS-18776") - void testRelativePath() { - // I was having trouble causing annotation processing on test stubs - jenkins.getDescriptorOrDie(RelativePathTest.class) - jenkins.getDescriptorOrDie(Model.class) - - createWebClient().goTo("/self/"); - assert jenkins.getDescriptorOrDie(Model.class).touched - } - - String getName() { - return "Alice"; - } - - Model getModel() { - return new Model(); - } - - DescriptorImpl getDescriptor() { - return jenkins.getDescriptorOrDie(getClass()); - } - - @TestExtension - static class DescriptorImpl extends Descriptor {} - - static class Model extends AbstractDescribableImpl { - @TestExtension - static class DescriptorImpl extends Descriptor { - boolean touched; - - ListBoxModel doFillAbcItems(@RelativePath("..") @QueryParameter String name) { - assert name=="Alice"; - touched = true; - return new ListBoxModel().add("foo").add("bar") - } - } - } -} diff --git a/test/src/test/java/hudson/RelativePathTest.java b/test/src/test/java/hudson/RelativePathTest.java new file mode 100644 index 0000000000..494a37f9e9 --- /dev/null +++ b/test/src/test/java/hudson/RelativePathTest.java @@ -0,0 +1,58 @@ +package hudson; + +import hudson.model.AbstractDescribableImpl; +import hudson.model.Describable; +import hudson.model.Descriptor; +import hudson.util.ListBoxModel; +import org.jvnet.hudson.test.HudsonTestCase; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.QueryParameter; + +public class RelativePathTest extends HudsonTestCase implements Describable { + + @Issue("JENKINS-18776") + public void testRelativePath() throws Exception { + // I was having trouble causing annotation processing on test stubs +// jenkins.getDescriptorOrDie(RelativePathTest.class); +// jenkins.getDescriptorOrDie(Model.class); + + createWebClient().goTo("/self/"); + assertTrue(((Model.DescriptorImpl) jenkins.getDescriptorOrDie(Model.class)).touched); + } + + @Override // TODO this is horrible, should change the property here and in @QueryParameter and in RelativePathTest/index.groovy to, say, personName + public String getName() { + return "Alice"; + } + + public Model getModel() { + return new Model(); + } + + @Override // TODO would suffice to extend AbstractDescribableImpl + public DescriptorImpl getDescriptor() { + return (DescriptorImpl) jenkins.getDescriptorOrDie(getClass()); + } + + @TestExtension + public static class DescriptorImpl extends Descriptor {} + + public static class Model extends AbstractDescribableImpl { + + @TestExtension + public static class DescriptorImpl extends Descriptor { + + boolean touched; + + public ListBoxModel doFillAbcItems(@RelativePath("..") @QueryParameter String name) { + assertEquals("Alice", name); + touched = true; + return new ListBoxModel().add("foo").add("bar"); + } + + } + + } + +} -- GitLab From 5ed56faaf3689a1392a02e4c4325fcd343efea61 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 16:46:30 -0500 Subject: [PATCH 0119/1380] BuildCommandTest --- .../groovy/hudson/cli/BuildCommandTest.groovy | 296 ----------------- .../java/hudson/cli/BuildCommand2Test.java | 74 ----- .../java/hudson/cli/BuildCommandTest.java | 313 ++++++++++++++++++ 3 files changed, 313 insertions(+), 370 deletions(-) delete mode 100644 test/src/test/groovy/hudson/cli/BuildCommandTest.groovy delete mode 100644 test/src/test/java/hudson/cli/BuildCommand2Test.java create mode 100644 test/src/test/java/hudson/cli/BuildCommandTest.java diff --git a/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy b/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy deleted file mode 100644 index 86ea1ecc83..0000000000 --- a/test/src/test/groovy/hudson/cli/BuildCommandTest.groovy +++ /dev/null @@ -1,296 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, 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 hudson.cli - -import org.apache.commons.io.output.TeeOutputStream - -import static org.hamcrest.Matchers.*; -import static hudson.cli.CLICommandInvoker.Matcher.*; -import static org.junit.Assert.* -import hudson.Extension - -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.Issue -import org.jvnet.hudson.test.CaptureEnvironmentBuilder -import org.jvnet.hudson.test.JenkinsRule -import org.jvnet.hudson.test.TestBuilder -import org.jvnet.hudson.test.TestExtension -import org.kohsuke.stapler.StaplerRequest - -import hudson.Launcher -import hudson.cli.CLICommandInvoker; -import hudson.model.AbstractBuild -import hudson.model.Action -import hudson.model.BuildListener -import hudson.model.Executor -import hudson.model.FreeStyleProject; -import hudson.model.ParameterDefinition.ParameterDescriptor -import hudson.model.ParameterValue -import hudson.model.ParametersAction -import hudson.model.ParametersDefinitionProperty -import hudson.model.Queue.QueueDecisionHandler -import hudson.model.Queue.Task; -import hudson.model.SimpleParameterDefinition -import hudson.model.StringParameterDefinition -import hudson.model.StringParameterValue -import hudson.model.labels.LabelAtom -import hudson.tasks.Shell -import hudson.util.OneShotEvent - -import java.util.concurrent.Executor - -import net.sf.json.JSONObject - -/** - * {@link BuildCommand} test. - * - * @author Kohsuke Kawaguchi - */ -public class BuildCommandTest { - - @Rule public JenkinsRule j = new JenkinsRule(); - - /** - * Just schedules a build and return. - */ - @Test void async() { - def p = j.createFreeStyleProject(); - def started = new OneShotEvent(); - def completed = new OneShotEvent(); - p.buildersList.add([perform: {AbstractBuild build, Launcher launcher, BuildListener listener -> - started.signal(); - completed.block(); - return true; - }] as TestBuilder); - - // this should be asynchronous - def cli = new CLI(j.URL) - try { - assertEquals(0,cli.execute(["build", p.name])) - started.block() - assertTrue(p.getBuildByNumber(1).isBuilding()) - completed.signal() - } finally { - cli.close(); - } - } - - /** - * Tests synchronous execution. - */ - @Test void sync() { - def p = j.createFreeStyleProject(); - p.buildersList.add(new Shell("sleep 3")); - - def cli = new CLI(j.URL) - try { - cli.execute(["build","-s",p.name]) - assertFalse(p.getBuildByNumber(1).isBuilding()) - } finally { - cli.close(); - } - } - - /** - * Tests synchronous execution with retried verbose output - */ - @Test void syncWOutputStreaming() { - def p = j.createFreeStyleProject(); - p.buildersList.add(new Shell("sleep 3")); - - def cli =new CLI(j.URL) - try { - cli.execute(["build","-s","-v","-r","5",p.name]) - assertFalse(p.getBuildByNumber(1).isBuilding()) - } finally { - cli.close(); - } - } - - @Test void parameters() { - def p = j.createFreeStyleProject(); - p.addProperty(new ParametersDefinitionProperty([new StringParameterDefinition("key",null)])); - - def cli = new CLI(j.URL) - try { - cli.execute(["build","-s","-p","key=foobar",p.name]) - def b = j.assertBuildStatusSuccess(p.getBuildByNumber(1)) - assertEquals("foobar",b.getAction(ParametersAction.class).getParameter("key").value) - } finally { - cli.close(); - } - } - - @Test void defaultParameters() { - def p = j.createFreeStyleProject(); - p.addProperty(new ParametersDefinitionProperty([new StringParameterDefinition("key","default"), new StringParameterDefinition("key2","default2") ])); - - def cli = new CLI(j.URL) - try { - cli.execute(["build","-s","-p","key=foobar",p.name]) - def b = j.assertBuildStatusSuccess(p.getBuildByNumber(1)) - assertEquals("foobar",b.getAction(ParametersAction.class).getParameter("key").value) - assertEquals("default2",b.getAction(ParametersAction.class).getParameter("key2").value) - } finally { - cli.close(); - } - } - - // TODO randomly fails: Started test0 #1 - @Test void consoleOutput() { - def p = j.createFreeStyleProject() - def cli = new CLI(j.URL) - try { - def o = new ByteArrayOutputStream() - cli.execute(["build","-s","-v",p.name],System.in,new TeeOutputStream(System.out,o),System.err) - j.assertBuildStatusSuccess(p.getBuildByNumber(1)) - assertTrue(o.toString(), o.toString().contains("Started from command line by anonymous")) - assertTrue(o.toString().contains("Finished: SUCCESS")) - } finally { - cli.close() - } - } - - // TODO randomly fails: Started test0 #1 - @Test void consoleOutputWhenBuildSchedulingRefused() { - def p = j.createFreeStyleProject() - def cli = new CLI(j.URL) - try { - def o = new ByteArrayOutputStream() - cli.execute(["build","-s","-v",p.name],System.in,System.out,new TeeOutputStream(System.err,o)) - assertTrue(o.toString(), o.toString().contains(BuildCommand.BUILD_SCHEDULING_REFUSED)) - } finally { - cli.close() - } - } - // <=> - @TestExtension("consoleOutputWhenBuildSchedulingRefused") - static class UnschedulingVetoer extends QueueDecisionHandler { - public boolean shouldSchedule(Task task, List actions) { - return false; - } - } - - @Test void refuseToBuildDisabledProject() { - - def project = j.createFreeStyleProject("the-project"); - project.disable(); - def invoker = new CLICommandInvoker(j, new BuildCommand()); - def result = invoker.invokeWithArgs("the-project"); - - assertThat(result, failedWith(4)); - assertThat(result.stderr(), containsString("ERROR: Cannot build the-project because it is disabled.")); - assertNull("Project should not be built", project.getBuildByNumber(1)); - } - - @Test void refuseToBuildNewlyCopiedProject() { - - def original = j.createFreeStyleProject("original"); - def newOne = (FreeStyleProject) j.jenkins.copy(original, "new-one"); - def invoker = new CLICommandInvoker(j, new BuildCommand()); - def result = invoker.invokeWithArgs("new-one"); - - assertThat(result, failedWith(4)); - assertThat(result.stderr(), containsString("ERROR: Cannot build new-one because its configuration has not been saved.")); - assertNull("Project should not be built", newOne.getBuildByNumber(1)); - } - - @Test void correctlyParseMapValuesContainingEqualsSign() { - - def project = j.createFreeStyleProject("the-project"); - project.addProperty(new ParametersDefinitionProperty([ - new StringParameterDefinition("expr", null) - ])); - - def invoker = new CLICommandInvoker(j, new BuildCommand()); - def result = invoker.invokeWithArgs("the-project", "-p", "expr=a=b", "-s"); - - assertThat(result, succeeded()); - assertEquals("a=b", project.getBuildByNumber(1).getBuildVariables().get("expr")); - } - - @Issue("JENKINS-15094") - @Test public void executorsAliveOnParameterWithNullDefaultValue() throws Exception { - def slave = j.createSlave(); - FreeStyleProject project = j.createFreeStyleProject("foo"); - project.setAssignedNode(slave); - - // Create test parameter with Null default value - def nullDefaultDefinition = new NullDefaultValueParameterDefinition(); - ParametersDefinitionProperty pdp = new ParametersDefinitionProperty( - new StringParameterDefinition("string", "defaultValue", "description"), - nullDefaultDefinition); - project.addProperty(pdp); - CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder(); - project.getBuildersList().add(builder); - - // Warmup - j.buildAndAssertSuccess(project); - - for (def exec : slave.toComputer().getExecutors()) { - assertTrue("Executor has died before the test start: "+exec, exec.isActive()); - } - - // Create CLI & run command - def invoker = new CLICommandInvoker(j, new BuildCommand()); - def result = invoker.invokeWithArgs("foo","-p","string=value"); - assertThat(result, failedWith(2)); - assertThat(result.stderr(), containsString("ERROR: No default value for the parameter \'FOO\'.")); - - Thread.sleep(5000); // Give the job 5 seconds to be submitted - assertNull("Build should not be scheduled", j.jenkins.getQueue().getItem(project)); - assertNull("Build should not be scheduled", project.getBuildByNumber(2)); - - // Check executors health after a timeout - for (def exec : slave.toComputer().getExecutors()) { - assertTrue("Executor is dead: "+exec, exec.isActive()); - } - } - - public static final class NullDefaultValueParameterDefinition extends SimpleParameterDefinition { - - /*package*/ NullDefaultValueParameterDefinition() { - super("FOO", "Always null default value"); - } - - @Override - public ParameterValue createValue(String value) { - return new StringParameterValue("FOO", "BAR"); - } - - @Override - public ParameterValue createValue(StaplerRequest req, JSONObject jo) { - return createValue("BAR"); - } - - @Override - public ParameterValue getDefaultParameterValue() { - return null; // Equals to super.getDefaultParameterValue(); - } - - @Extension - public static class DescriptorImpl extends ParameterDescriptor {} - } -} diff --git a/test/src/test/java/hudson/cli/BuildCommand2Test.java b/test/src/test/java/hudson/cli/BuildCommand2Test.java deleted file mode 100644 index 1f5366e1bb..0000000000 --- a/test/src/test/java/hudson/cli/BuildCommand2Test.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * The MIT License - * - * Copyright 2017 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 hudson.cli; - -import hudson.Launcher; -import hudson.model.AbstractBuild; -import hudson.model.BuildListener; -import hudson.model.FileParameterDefinition; -import hudson.model.FreeStyleBuild; -import hudson.model.FreeStyleProject; -import hudson.model.ParametersDefinitionProperty; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import org.junit.ClassRule; -import org.junit.Test; -import static org.junit.Assert.*; -import org.junit.Rule; -import org.jvnet.hudson.test.BuildWatcher; -import org.jvnet.hudson.test.Issue; -import org.jvnet.hudson.test.JenkinsRule; -import org.jvnet.hudson.test.TestBuilder; - -public class BuildCommand2Test { - - @ClassRule - public static BuildWatcher buildWatcher = new BuildWatcher(); - - @Rule - public JenkinsRule r = new JenkinsRule(); - - @Issue("JENKINS-41745") - @Test - public void fileParameter() throws Exception { - FreeStyleProject p = r.createFreeStyleProject("myjob"); - p.addProperty(new ParametersDefinitionProperty(new FileParameterDefinition("file", null))); - p.getBuildersList().add(new TestBuilder() { - @Override - public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { - listener.getLogger().println("Found in my workspace: " + build.getWorkspace().child("file").readToString()); - return true; - } - }); - assertThat(new CLICommandInvoker(r, "build"). - withStdin(new ByteArrayInputStream("uploaded content here".getBytes())). - invokeWithArgs("-f", "-p", "file=", "myjob"), - CLICommandInvoker.Matcher.succeeded()); - FreeStyleBuild b = p.getBuildByNumber(1); - assertNotNull(b); - r.assertLogContains("uploaded content here", b); - } - -} diff --git a/test/src/test/java/hudson/cli/BuildCommandTest.java b/test/src/test/java/hudson/cli/BuildCommandTest.java new file mode 100644 index 0000000000..a55d7c1fe9 --- /dev/null +++ b/test/src/test/java/hudson/cli/BuildCommandTest.java @@ -0,0 +1,313 @@ +/* + * The MIT License + * + * Copyright (c) 2004-2009, Sun Microsystems, 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 hudson.cli; + +import hudson.Extension; +import hudson.Launcher; +import static hudson.cli.CLICommandInvoker.Matcher.*; +import hudson.model.AbstractBuild; +import hudson.model.Action; +import hudson.model.BuildListener; +import hudson.model.Executor; +import hudson.model.FileParameterDefinition; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.ParameterDefinition.ParameterDescriptor; +import hudson.model.ParameterValue; +import hudson.model.ParametersAction; +import hudson.model.ParametersDefinitionProperty; +import hudson.model.Queue.QueueDecisionHandler; +import hudson.model.Queue.Task; +import hudson.model.SimpleParameterDefinition; +import hudson.model.StringParameterDefinition; +import hudson.model.StringParameterValue; +import hudson.model.TopLevelItem; +import hudson.slaves.DumbSlave; +import hudson.tasks.Shell; +import hudson.util.OneShotEvent; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import net.sf.json.JSONObject; +import org.apache.commons.io.output.TeeOutputStream; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.BuildWatcher; +import org.jvnet.hudson.test.CaptureEnvironmentBuilder; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestBuilder; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.StaplerRequest; + +/** + * {@link BuildCommand} test. + */ +public class BuildCommandTest { + + @ClassRule + public static BuildWatcher buildWatcher = new BuildWatcher(); + + @Rule + public JenkinsRule j = new JenkinsRule(); + + /** + * Just schedules a build and return. + */ + @Test + public void async() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + OneShotEvent started = new OneShotEvent(); + OneShotEvent completed = new OneShotEvent(); + p.getBuildersList().add(new TestBuilder() { + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + started.signal(); + completed.block(); + return true; + } + }); + + // this should be asynchronous + try (CLI cli = new CLI(j.getURL())) { + assertEquals(0, cli.execute("build", p.getName())); + started.block(); + assertTrue(p.getBuildByNumber(1).isBuilding()); + completed.signal(); + } + } + + /** + * Tests synchronous execution. + */ + @Test + public void sync() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + p.getBuildersList().add(new Shell("sleep 3")); + + try (CLI cli = new CLI(j.getURL())) { + cli.execute("build", "-s", p.getName()); + assertFalse(p.getBuildByNumber(1).isBuilding()); + } + } + + /** + * Tests synchronous execution with retried verbose output + */ + @Test + public void syncWOutputStreaming() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + p.getBuildersList().add(new Shell("sleep 3")); + + try (CLI cli = new CLI(j.getURL())) { + cli.execute("build", "-s", "-v", "-r", "5", p.getName()); + assertFalse(p.getBuildByNumber(1).isBuilding()); + } + } + + @Test + public void parameters() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + p.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("key", null))); + + try (CLI cli = new CLI(j.getURL())) { + cli.execute("build", "-s", "-p", "key=foobar", p.getName()); + FreeStyleBuild b = j.assertBuildStatusSuccess(p.getBuildByNumber(1)); + assertEquals("foobar", b.getAction(ParametersAction.class).getParameter("key").getValue()); + } + } + + @Test + public void defaultParameters() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + p.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("key", "default"), new StringParameterDefinition("key2", "default2"))); + + try (CLI cli = new CLI(j.getURL())) { + cli.execute("build", "-s", "-p", "key=foobar", p.getName()); + FreeStyleBuild b = j.assertBuildStatusSuccess(p.getBuildByNumber(1)); + assertEquals("foobar", b.getAction(ParametersAction.class).getParameter("key").getValue()); + assertEquals("default2", b.getAction(ParametersAction.class).getParameter("key2").getValue()); + } + } + + // TODO randomly fails: Started test0 #1 + @Test + public void consoleOutput() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + try (CLI cli = new CLI(j.getURL())) { + ByteArrayOutputStream o = new ByteArrayOutputStream(); + cli.execute(Arrays.asList("build", "-s", "-v", p.getName()), System.in, new TeeOutputStream(System.out, o), System.err); + j.assertBuildStatusSuccess(p.getBuildByNumber(1)); + assertThat(o.toString(), allOf(containsString("Started from command line by anonymous"), containsString("Finished: SUCCESS"))); + } + } + + // TODO randomly fails: Started test0 #1 + @Test + public void consoleOutputWhenBuildSchedulingRefused() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + try (CLI cli = new CLI(j.getURL())) { + ByteArrayOutputStream o = new ByteArrayOutputStream(); + cli.execute(Arrays.asList("build", "-s", "-v", p.getName()), System.in, System.out, new TeeOutputStream(System.err, o)); + assertThat(o.toString(), containsString(BuildCommand.BUILD_SCHEDULING_REFUSED)); + } + } + // <=> + @TestExtension("consoleOutputWhenBuildSchedulingRefused") + public static class UnschedulingVetoer extends QueueDecisionHandler { + @Override + public boolean shouldSchedule(Task task, List actions) { + return false; + } + } + + @Test + public void refuseToBuildDisabledProject() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("the-project"); + project.disable(); + CLICommandInvoker invoker = new CLICommandInvoker(j, new BuildCommand()); + CLICommandInvoker.Result result = invoker.invokeWithArgs("the-project"); + + assertThat(result, failedWith(4)); + assertThat(result.stderr(), containsString("ERROR: Cannot build the-project because it is disabled.")); + assertNull("Project should not be built", project.getBuildByNumber(1)); + } + + @Test + public void refuseToBuildNewlyCopiedProject() throws Exception { + FreeStyleProject original = j.createFreeStyleProject("original"); + FreeStyleProject newOne = (FreeStyleProject) j.jenkins.copy(original, "new-one"); + CLICommandInvoker invoker = new CLICommandInvoker(j, new BuildCommand()); + CLICommandInvoker.Result result = invoker.invokeWithArgs("new-one"); + + assertThat(result, failedWith(4)); + assertThat(result.stderr(), containsString("ERROR: Cannot build new-one because its configuration has not been saved.")); + assertNull("Project should not be built", newOne.getBuildByNumber(1)); + } + + @Test + public void correctlyParseMapValuesContainingEqualsSign() throws Exception { + FreeStyleProject project = j.createFreeStyleProject("the-project"); + project.addProperty(new ParametersDefinitionProperty(new StringParameterDefinition("expr", null))); + + CLICommandInvoker invoker = new CLICommandInvoker(j, new BuildCommand()); + CLICommandInvoker.Result result = invoker.invokeWithArgs("the-project", "-p", "expr=a=b", "-s"); + + assertThat(result, succeeded()); + assertEquals("a=b", project.getBuildByNumber(1).getBuildVariables().get("expr")); + } + + @Issue("JENKINS-15094") + @Test + public void executorsAliveOnParameterWithNullDefaultValue() throws Exception { + DumbSlave slave = j.createSlave(); + FreeStyleProject project = j.createFreeStyleProject("foo"); + project.setAssignedNode(slave); + + // Create test parameter with Null default value + NullDefaultValueParameterDefinition nullDefaultDefinition = new NullDefaultValueParameterDefinition(); + ParametersDefinitionProperty pdp = new ParametersDefinitionProperty( + new StringParameterDefinition("string", "defaultValue", "description"), + nullDefaultDefinition); + project.addProperty(pdp); + CaptureEnvironmentBuilder builder = new CaptureEnvironmentBuilder(); + project.getBuildersList().add(builder); + + // Warmup + j.buildAndAssertSuccess(project); + + for (Executor exec : slave.toComputer().getExecutors()) { + assertTrue("Executor has died before the test start: " + exec, exec.isActive()); + } + + // Create CLI & run command + CLICommandInvoker invoker = new CLICommandInvoker(j, new BuildCommand()); + CLICommandInvoker.Result result = invoker.invokeWithArgs("foo", "-p", "string=value"); + assertThat(result, failedWith(2)); + assertThat(result.stderr(), containsString("ERROR: No default value for the parameter \'FOO\'.")); + + Thread.sleep(5000); // Give the job 5 seconds to be submitted + assertNull("Build should not be scheduled", j.jenkins.getQueue().getItem(project)); + assertNull("Build should not be scheduled", project.getBuildByNumber(2)); + + // Check executors health after a timeout + for (Executor exec : slave.toComputer().getExecutors()) { + assertTrue("Executor is dead: " + exec, exec.isActive()); + } + } + + public static final class NullDefaultValueParameterDefinition extends SimpleParameterDefinition { + + /*package*/ NullDefaultValueParameterDefinition() { + super("FOO", "Always null default value"); + } + + @Override + public ParameterValue createValue(String value) { + return new StringParameterValue("FOO", "BAR"); + } + + @Override + public ParameterValue createValue(StaplerRequest req, JSONObject jo) { + return createValue("BAR"); + } + + @Override + public ParameterValue getDefaultParameterValue() { + return null; // Equals to super.getDefaultParameterValue(); + } + + @Extension + public static class DescriptorImpl extends ParameterDescriptor {} + + } + + @Issue("JENKINS-41745") + @Test + public void fileParameter() throws Exception { + FreeStyleProject p = j.createFreeStyleProject("myjob"); + p.addProperty(new ParametersDefinitionProperty(new FileParameterDefinition("file", null))); + p.getBuildersList().add(new TestBuilder() { + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + listener.getLogger().println("Found in my workspace: " + build.getWorkspace().child("file").readToString()); + return true; + } + }); + assertThat(new CLICommandInvoker(j, "build"). + withStdin(new ByteArrayInputStream("uploaded content here".getBytes())). + invokeWithArgs("-f", "-p", "file=", "myjob"), + CLICommandInvoker.Matcher.succeeded()); + FreeStyleBuild b = p.getBuildByNumber(1); + assertNotNull(b); + j.assertLogContains("uploaded content here", b); + } + +} -- GitLab From 26c4ab77db3802ac256ab5f0aa5a9cf38aaecc03 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 16:54:17 -0500 Subject: [PATCH 0120/1380] SetBuildParameterCommandTest --- .../cli/SetBuildParameterCommandTest.groovy | 82 ----------------- .../cli/SetBuildParameterCommandTest.java | 88 +++++++++++++++++++ 2 files changed, 88 insertions(+), 82 deletions(-) delete mode 100644 test/src/test/groovy/hudson/cli/SetBuildParameterCommandTest.groovy create mode 100644 test/src/test/java/hudson/cli/SetBuildParameterCommandTest.java diff --git a/test/src/test/groovy/hudson/cli/SetBuildParameterCommandTest.groovy b/test/src/test/groovy/hudson/cli/SetBuildParameterCommandTest.groovy deleted file mode 100644 index dcfba02ed8..0000000000 --- a/test/src/test/groovy/hudson/cli/SetBuildParameterCommandTest.groovy +++ /dev/null @@ -1,82 +0,0 @@ -package hudson.cli - -import hudson.Functions -import hudson.Launcher -import hudson.model.AbstractBuild -import hudson.model.BuildListener -import hudson.model.ParametersAction -import hudson.model.ParametersDefinitionProperty -import hudson.model.ParameterDefinition -import hudson.model.Result -import hudson.model.StringParameterDefinition -import hudson.tasks.BatchFile -import hudson.tasks.Builder -import hudson.tasks.Shell -import jenkins.model.JenkinsLocationConfiguration -import org.junit.Assert -import org.junit.ClassRule -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.BuildWatcher -import org.jvnet.hudson.test.JenkinsRule -import org.jvnet.hudson.test.TestBuilder - -/** - * @author Kohsuke Kawaguchi - */ -public class SetBuildParameterCommandTest { - @Rule - public JenkinsRule j = new JenkinsRule(); - - @ClassRule - public static BuildWatcher buildWatcher = new BuildWatcher(); - - @Test - public void cli() { - JenkinsLocationConfiguration.get().url = j.URL; - - def p = j.createFreeStyleProject(); - p.buildersList.add(new TestBuilder() { - @Override - boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { - def jar = j.jenkins.servletContext.getResource("/WEB-INF/jenkins-cli.jar") - build.workspace.child("cli.jar").copyFrom(jar); - - return true; - } - }); - List pd = [new StringParameterDefinition("a", ""), new StringParameterDefinition("b", "")]; - p.addProperty(new ParametersDefinitionProperty(pd)) - p.buildersList.add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b")) - p.buildersList.add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a x")) - p.buildersList.add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter b y")) - - def r = [:]; - - def b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)) - b.getAction(ParametersAction.class).parameters.each { v -> r[v.name]=v.value } - - assert r.equals(["a":"x", "b":"y"]); - - if (Functions.isWindows()) { - p.buildersList.add(new BatchFile("set BUILD_NUMBER=1\r\njava -jar cli.jar -remoting -noKeyAuth set-build-parameter a b")) - } else { - p.buildersList.add(new Shell("BUILD_NUMBER=1 java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b")) - } - def b2 = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); - j.assertLogContains("#1 is not currently being built", b2) - r = [:]; - b.getAction(ParametersAction.class).parameters.each { v -> r[v.name]=v.value } - assert r.equals(["a":"x", "b":"y"]); - } - - //TODO: determine if this should be pulled out into JenkinsRule or something - /** - * Create a script based builder (either Shell or BatchFile) depending on platform - * @param script the contents of the script to run - * @return A Builder instance of either Shell or BatchFile - */ - private Builder createScriptBuilder(String script) { - return Functions.isWindows() ? new BatchFile(script) : new Shell(script); - } -} diff --git a/test/src/test/java/hudson/cli/SetBuildParameterCommandTest.java b/test/src/test/java/hudson/cli/SetBuildParameterCommandTest.java new file mode 100644 index 0000000000..937abe4628 --- /dev/null +++ b/test/src/test/java/hudson/cli/SetBuildParameterCommandTest.java @@ -0,0 +1,88 @@ +package hudson.cli; + +import com.google.common.collect.ImmutableMap; +import hudson.Functions; +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.BuildListener; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.ParametersAction; +import hudson.model.ParametersDefinitionProperty; +import hudson.model.ParameterDefinition; +import hudson.model.Result; +import hudson.model.StringParameterDefinition; +import hudson.tasks.BatchFile; +import hudson.tasks.Builder; +import hudson.tasks.Shell; +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import static org.junit.Assert.*; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.BuildWatcher; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestBuilder; + +public class SetBuildParameterCommandTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @ClassRule + public static BuildWatcher buildWatcher = new BuildWatcher(); + + @Test + public void cli() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + p.getBuildersList().add(new TestBuilder() { + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + URL jar = j.jenkins.servletContext.getResource("/WEB-INF/jenkins-cli.jar"); + build.getWorkspace().child("cli.jar").copyFrom(jar); + + return true; + } + }); + List pd = Arrays.asList(new StringParameterDefinition("a", ""), new StringParameterDefinition("b", "")); + p.addProperty(new ParametersDefinitionProperty(pd)); + p.getBuildersList().add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b")); + p.getBuildersList().add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter a x")); + p.getBuildersList().add(createScriptBuilder("java -jar cli.jar -remoting -noKeyAuth set-build-parameter b y")); + + Map r = new TreeMap<>(); + + FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + b.getAction(ParametersAction.class).getParameters().forEach(v -> r.put(v.getName(), v.getValue())); + + assertEquals(ImmutableMap.of("a", "x", "b", "y"), r); + + if (Functions.isWindows()) { + p.getBuildersList().add(new BatchFile("set BUILD_NUMBER=1\r\njava -jar cli.jar -remoting -noKeyAuth set-build-parameter a b")); + } else { + p.getBuildersList().add(new Shell("BUILD_NUMBER=1 java -jar cli.jar -remoting -noKeyAuth set-build-parameter a b")); + } + FreeStyleBuild b2 = j.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + j.assertLogContains("#1 is not currently being built", b2); + r.clear(); + b.getAction(ParametersAction.class).getParameters().forEach(v -> r.put(v.getName(), v.getValue())); + assertEquals(ImmutableMap.of("a", "x", "b", "y"), r); + } + + //TODO: determine if this should be pulled out into JenkinsRule or something + /** + * Create a script based builder (either Shell or BatchFile) depending on + * platform + * @param script the contents of the script to run + * @return A Builder instance of either Shell or BatchFile + */ + private Builder createScriptBuilder(String script) { + return Functions.isWindows() ? new BatchFile(script) : new Shell(script); + } + +} -- GitLab From ba44ed593def941cc1184c740826ad8504e4d81f Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 17:00:05 -0500 Subject: [PATCH 0121/1380] AbstractItemTest --- .../hudson/model/AbstractItemTest.groovy | 42 ------------------ .../java/hudson/model/AbstractItemTest.java | 43 +++++++++++++++++++ 2 files changed, 43 insertions(+), 42 deletions(-) delete mode 100644 test/src/test/groovy/hudson/model/AbstractItemTest.groovy create mode 100644 test/src/test/java/hudson/model/AbstractItemTest.java diff --git a/test/src/test/groovy/hudson/model/AbstractItemTest.groovy b/test/src/test/groovy/hudson/model/AbstractItemTest.groovy deleted file mode 100644 index 8c7cbe8ef4..0000000000 --- a/test/src/test/groovy/hudson/model/AbstractItemTest.groovy +++ /dev/null @@ -1,42 +0,0 @@ -package hudson.model - -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.JenkinsRule - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class AbstractItemTest { - @Rule - public JenkinsRule j = new JenkinsRule() - - /** - * Tests the reload functionality - */ - @Test - void reload() { - def jenkins = j.jenkins; - def p = jenkins.createProject(FreeStyleProject.class,"foo"); - p.description = "Hello World"; - - def b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)) - b.description = "This is my build" - - // update on disk representation - def f = p.configFile.file - f.text = f.text.replaceAll("Hello World","Good Evening"); - - // reload away - p.doReload() - - assert p.description == "Good Evening"; - - def b2 = p.getBuildByNumber(1) - - assert !b.is(b2); // should be different object - assert b.description == b2.description // but should have the same properties - } -} diff --git a/test/src/test/java/hudson/model/AbstractItemTest.java b/test/src/test/java/hudson/model/AbstractItemTest.java new file mode 100644 index 0000000000..267f8f3524 --- /dev/null +++ b/test/src/test/java/hudson/model/AbstractItemTest.java @@ -0,0 +1,43 @@ +package hudson.model; + +import java.io.File; +import jenkins.model.Jenkins; +import org.apache.commons.io.FileUtils; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; + +public class AbstractItemTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + /** + * Tests the reload functionality + */ + @Test + public void reload() throws Exception { + Jenkins jenkins = j.jenkins; + FreeStyleProject p = jenkins.createProject(FreeStyleProject.class, "foo"); + p.setDescription("Hello World"); + + FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + b.setDescription("This is my build"); + + // update on disk representation + File f = p.getConfigFile().getFile(); + FileUtils.writeStringToFile(f, FileUtils.readFileToString(f).replaceAll("Hello World", "Good Evening")); + + // reload away + p.doReload(); + + assertEquals("Good Evening", p.getDescription()); + + FreeStyleBuild b2 = p.getBuildByNumber(1); + + assertNotEquals(b, b2); // should be different object + assertEquals(b.getDescription(), b2.getDescription()); // but should have the same properties + } + +} -- GitLab From ea2e4f5788449535767809bcdbc7c32f3706129a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 17:37:37 -0500 Subject: [PATCH 0122/1380] AbstractProjectTest --- .../hudson/model/AbstractProjectTest.groovy | 596 ---------------- .../hudson/model/AbstractProject2Test.java | 73 -- .../hudson/model/AbstractProjectTest.java | 646 ++++++++++++++++++ 3 files changed, 646 insertions(+), 669 deletions(-) delete mode 100644 test/src/test/groovy/hudson/model/AbstractProjectTest.groovy delete mode 100644 test/src/test/java/hudson/model/AbstractProject2Test.java create mode 100644 test/src/test/java/hudson/model/AbstractProjectTest.java diff --git a/test/src/test/groovy/hudson/model/AbstractProjectTest.groovy b/test/src/test/groovy/hudson/model/AbstractProjectTest.groovy deleted file mode 100644 index a8941e0c66..0000000000 --- a/test/src/test/groovy/hudson/model/AbstractProjectTest.groovy +++ /dev/null @@ -1,596 +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.model; - -import com.gargoylesoftware.htmlunit.ElementNotFoundException -import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; -import com.gargoylesoftware.htmlunit.HttpMethod -import com.gargoylesoftware.htmlunit.WebRequest; -import hudson.tasks.BatchFile; -import hudson.tasks.BuildStepMonitor; -import hudson.tasks.Recorder; -import com.gargoylesoftware.htmlunit.html.HtmlPage -import hudson.maven.MavenModuleSet; -import hudson.security.*; -import hudson.tasks.Shell; -import hudson.scm.NullSCM; -import hudson.scm.SCM -import hudson.scm.SCMDescriptor -import hudson.Launcher; -import hudson.FilePath; -import hudson.Functions; -import hudson.Util; -import hudson.tasks.ArtifactArchiver -import hudson.triggers.SCMTrigger; -import hudson.triggers.TimerTrigger -import hudson.triggers.Trigger -import hudson.triggers.TriggerDescriptor; -import hudson.util.StreamTaskListener; -import hudson.util.OneShotEvent -import jenkins.model.Jenkins -import org.junit.Rule -import org.junit.Test; -import org.jvnet.hudson.test.Issue -import org.jvnet.hudson.test.JenkinsRule; -import org.jvnet.hudson.test.TestExtension; -import org.jvnet.hudson.test.recipes.PresetData; -import org.jvnet.hudson.test.recipes.PresetData.DataSet -import org.apache.commons.io.FileUtils; -import org.junit.Assume; -import org.jvnet.hudson.test.MockFolder -import org.kohsuke.args4j.CmdLineException - -import static org.junit.Assert.fail -import static org.junit.Assert.assertEquals; - -/** - * @author Kohsuke Kawaguchi - */ -public class AbstractProjectTest { - - @Rule public JenkinsRule j = new JenkinsRule(); - - @Test - public void testConfigRoundtrip() { - def project = j.createFreeStyleProject(); - def l = j.jenkins.getLabel("foo && bar"); - project.assignedLabel = l; - j.configRoundtrip((Item) project); - - assert l == project.getAssignedLabel(); - } - - /** - * Tests the workspace deletion. - */ - @Test - public void testWipeWorkspace() { - def project = j.createFreeStyleProject(); - project.buildersList.add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello")); - - def b = project.scheduleBuild2(0).get(); - - assert b.workspace.exists(): "Workspace should exist by now"; - - project.doDoWipeOutWorkspace(); - - assert !b.workspace.exists(): "Workspace should be gone by now"; - } - - /** - * Makes sure that the workspace deletion is protected. - */ - @Test - @PresetData(DataSet.NO_ANONYMOUS_READACCESS) - public void testWipeWorkspaceProtected() { - def project = j.createFreeStyleProject(); - project.getBuildersList().add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello")); - - def b = project.scheduleBuild2(0).get(); - - assert b.getWorkspace().exists(): "Workspace should exist by now"; - - // make sure that the action link is protected - com.gargoylesoftware.htmlunit.WebClient wc = j.createWebClient(); - try { - wc.getPage(new WebRequest(new URL(wc.getContextPath() + project.getUrl() + "doWipeOutWorkspace"), HttpMethod.POST)); - fail("Expected HTTP status code 403") - } catch (FailingHttpStatusCodeException e) { - assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getStatusCode()); - } - } - - /** - * Makes sure that the workspace deletion link is not provided - * when the user doesn't have an access. - */ - @Test - @PresetData(DataSet.ANONYMOUS_READONLY) - public void testWipeWorkspaceProtected2() { - ((GlobalMatrixAuthorizationStrategy) j.jenkins.getAuthorizationStrategy()).add(AbstractProject.WORKSPACE,"anonymous"); - - // make sure that the deletion is protected in the same way - testWipeWorkspaceProtected(); - - // there shouldn't be any "wipe out workspace" link for anonymous user - def webClient = j.createWebClient(); - HtmlPage page = webClient.getPage(j.jenkins.getItem("test0")); - - page = (HtmlPage)page.getAnchorByText("Workspace").click(); - try { - String wipeOutLabel = ResourceBundle.getBundle("hudson/model/AbstractProject/sidepanel").getString("Wipe Out Workspace"); - page.getAnchorByText(wipeOutLabel); - fail("shouldn't find a link"); - } catch (ElementNotFoundException e) { - // OK - } - } - - /** - * Tests the <optionalBlock @field> round trip behavior by using {@link AbstractProject#concurrentBuild} - */ - @Test - public void testOptionalBlockDataBindingRoundtrip() { - def p = j.createFreeStyleProject(); - [true,false].each { b -> - p.concurrentBuild = b; - j.submit(j.createWebClient().getPage(p,"configure").getFormByName("config")); - assert b==p.isConcurrentBuild(); - } - } - - /** - * Tests round trip configuration of the blockBuildWhenUpstreamBuilding field - */ - @Test - @Issue("JENKINS-4423") - public void testConfiguringBlockBuildWhenUpstreamBuildingRoundtrip() { - def p = j.createFreeStyleProject(); - p.blockBuildWhenUpstreamBuilding = false; - - def form = j.createWebClient().getPage(p, "configure").getFormByName("config"); - def input = form.getInputByName("blockBuildWhenUpstreamBuilding"); - assert !input.isChecked(): "blockBuildWhenUpstreamBuilding check box is checked."; - - input.setChecked(true); - j.submit(form); - assert p.blockBuildWhenUpstreamBuilding: "blockBuildWhenUpstreamBuilding was not updated from configuration form"; - - form = j.createWebClient().getPage(p, "configure").getFormByName("config"); - input = form.getInputByName("blockBuildWhenUpstreamBuilding"); - assert input.isChecked(): "blockBuildWhenUpstreamBuilding check box is not checked."; - } - - /** - * Unless the concurrent build option is enabled, polling and build should be mutually exclusive - * to avoid allocating unnecessary workspaces. - */ - @Test - @Issue("JENKINS-4202") - public void testPollingAndBuildExclusion() { - final OneShotEvent sync = new OneShotEvent(); - - final FreeStyleProject p = j.createFreeStyleProject(); - def b1 = j.buildAndAssertSuccess(p); - - p.scm = new NullSCM() { - @Override - public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener) { - try { - sync.block(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - return true; - } - - /** - * Don't write 'this', so that subtypes can be implemented as anonymous class. - */ - private Object writeReplace() { return new Object(); } - - @Override public boolean requiresWorkspaceForPolling() { - return true; - } - @Override public SCMDescriptor getDescriptor() { - return new SCMDescriptor(null) {}; - } - }; - Thread t = new Thread() { - @Override public void run() { - p.pollSCMChanges(StreamTaskListener.fromStdout()); - } - }; - try { - t.start(); - def f = p.scheduleBuild2(0); - - // add a bit of delay to make sure that the blockage is happening - Thread.sleep(3000); - - // release the polling - sync.signal(); - - def b2 = j.assertBuildStatusSuccess(f); - - // they should have used the same workspace. - assert b1.workspace == b2.workspace; - } finally { - t.interrupt(); - } - } - - @Test - @Issue("JENKINS-1986") - public void testBuildSymlinks() { - Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows()); - - def job = j.createFreeStyleProject(); - job.buildersList.add(new Shell("echo \"Build #\$BUILD_NUMBER\"\n")); - def build = job.scheduleBuild2(0, new Cause.UserCause()).get(); - File lastSuccessful = new File(job.rootDir, "lastSuccessful"), - lastStable = new File(job.rootDir, "lastStable"); - // First build creates links - assertSymlinkForBuild(lastSuccessful, 1); - assertSymlinkForBuild(lastStable, 1); - FreeStyleBuild build2 = job.scheduleBuild2(0, new Cause.UserCause()).get(); - // Another build updates links - assertSymlinkForBuild(lastSuccessful, 2); - assertSymlinkForBuild(lastStable, 2); - // Delete latest build should update links - build2.delete(); - assertSymlinkForBuild(lastSuccessful, 1); - assertSymlinkForBuild(lastStable, 1); - // Delete all builds should remove links - build.delete(); - assert !lastSuccessful.exists(): "lastSuccessful link should be removed"; - assert !lastStable.exists(): "lastStable link should be removed"; - } - - private static void assertSymlinkForBuild(File file, int buildNumber) - throws IOException, InterruptedException { - assert file.exists(): "should exist and point to something that exists"; - assert Util.isSymlink(file): "should be symlink"; - String s = FileUtils.readFileToString(new File(file, "log")); - assert s.contains("Build #" + buildNumber + "\n") : "link should point to build #$buildNumber, but link was: ${Util.resolveSymlink(file, TaskListener.NULL)}\nand log was:\n$s"; - } - - @Test - @Issue("JENKINS-2543") - public void testSymlinkForPostBuildFailure() { - Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows()); - - // Links should be updated after post-build actions when final build result is known - def job = j.createFreeStyleProject(); - job.buildersList.add(new Shell("echo \"Build #\$BUILD_NUMBER\"\n")); - def build = job.scheduleBuild2(0, new Cause.UserCause()).get(); - assert Result.SUCCESS == build.result; - File lastSuccessful = new File(job.rootDir, "lastSuccessful"), - lastStable = new File(job.rootDir, "lastStable"); - // First build creates links - assertSymlinkForBuild(lastSuccessful, 1); - assertSymlinkForBuild(lastStable, 1); - // Archive artifacts that don't exist to create failure in post-build action - job.publishersList.add(new ArtifactArchiver("*.foo", "", false, false)); - build = job.scheduleBuild2(0, new Cause.UserCause()).get(); - assert Result.FAILURE == build.getResult(); - // Links should not be updated since build failed - assertSymlinkForBuild(lastSuccessful, 1); - assertSymlinkForBuild(lastStable, 1); - } - - /* TODO too slow, seems capable of causing testWorkspaceLock to time out: - @Test - @Issue("JENKINS-15156") - public void testGetBuildAfterGC() { - FreeStyleProject job = j.createFreeStyleProject(); - job.scheduleBuild2(0, new Cause.UserIdCause()).get(); - j.jenkins.queue.clearLeftItems(); - MemoryAssert.assertGC(new WeakReference(job.getLastBuild())); - assert job.lastBuild != null; - } - */ - - @Test - @Issue("JENKINS-17137") - public void testExternalBuildDirectorySymlinks() { - Assume.assumeFalse(Functions.isWindows()); // symlinks may not be available - def form = j.createWebClient().goTo("configure").getFormByName("config"); - def builds = j.createTmpDir(); - form.getInputByName("_.rawBuildsDir").valueAttribute = builds.toString() + "/\${ITEM_FULL_NAME}"; - j.submit(form); - assert builds.toString() + "/\${ITEM_FULL_NAME}" == j.jenkins.getRawBuildsDir(); - def p = j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "p"); - def b1 = p.scheduleBuild2(0).get(); - def link = new File(p.rootDir, "lastStable"); - assert link.exists(); - assert b1.rootDir.absolutePath == resolveAll(link).absolutePath; - def b2 = p.scheduleBuild2(0).get(); - assert link.exists(); - assert b2.rootDir.absolutePath == resolveAll(link).absolutePath; - b2.delete(); - assert link.exists(); - assert b1.rootDir.absolutePath == resolveAll(link).absolutePath; - b1.delete(); - assert !link.exists(); - } - - private File resolveAll(File link) throws InterruptedException, IOException { - while (true) { - File f = Util.resolveSymlinkToFile(link); - if (f==null) return link; - link = f; - } - } - - @Test - @Issue("JENKINS-17138") - public void testExternalBuildDirectoryRenameDelete() { - def form = j.createWebClient().goTo("configure").getFormByName("config"); - def builds = j.createTmpDir(); - form.getInputByName("_.rawBuildsDir").setValueAttribute(builds.toString() + "/\${ITEM_FULL_NAME}"); - j.submit(form); - assert builds.toString() + "/\${ITEM_FULL_NAME}" == j.jenkins.rawBuildsDir; - def p = j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "prj"); - def b = p.scheduleBuild2(0).get(); - def oldBuildDir = new File(builds, "d/prj"); - assert new File(oldBuildDir, b.id) == b.rootDir; - assert b.getRootDir().isDirectory(); - p.renameTo("proj"); - def newBuildDir = new File(builds, "d/proj"); - assert new File(newBuildDir, b.id) == b.rootDir; - assert b.rootDir.isDirectory(); - p.delete(); - assert !b.rootDir.isDirectory(); - } - - @Test - @Issue("JENKINS-18678") - public void testRenameJobLostBuilds() throws Exception { - def p = j.createFreeStyleProject("initial"); - j.assertBuildStatusSuccess(p.scheduleBuild2(0)); - assertEquals(1, p.getBuilds().size()); - p.renameTo("edited"); - p._getRuns().purgeCache(); - assertEquals(1, p.getBuilds().size()); - def d = j.jenkins.createProject(MockFolder.class, "d"); - Items.move(p, d); - assertEquals(p, j.jenkins.getItemByFullName("d/edited")); - p._getRuns().purgeCache(); - assertEquals(1, p.getBuilds().size()); - d.renameTo("d2"); - p = j.jenkins.getItemByFullName("d2/edited"); - p._getRuns().purgeCache(); - assertEquals(1, p.getBuilds().size()); - } - - @Test - @Issue("JENKINS-17575") - public void testDeleteRedirect() { - j.createFreeStyleProject("j1"); - assert "" == deleteRedirectTarget("job/j1"); - j.createFreeStyleProject("j2"); - Jenkins.getInstance().addView(new AllView("v1")); - assert "view/v1/" == deleteRedirectTarget("view/v1/job/j2"); - MockFolder d = Jenkins.getInstance().createProject(MockFolder.class, "d"); - d.addView(new AllView("v2")); - ["j3","j4","j5"].each { n -> d.createProject(FreeStyleProject.class, n) } - assert "job/d/" == deleteRedirectTarget("job/d/job/j3"); - assert "job/d/view/v2/" == deleteRedirectTarget("job/d/view/v2/job/j4"); - assert "view/v1/job/d/" == deleteRedirectTarget("view/v1/job/d/job/j5"); - assert "view/v1/" == deleteRedirectTarget("view/v1/job/d"); // JENKINS-23375 - } - - private String deleteRedirectTarget(String job) { - def wc = j.createWebClient(); - String base = wc.getContextPath(); - String loc = wc.getPage(wc.addCrumb(new WebRequest(new URL(base + job + "/doDelete"), HttpMethod.POST))).getUrl().toString(); - assert loc.startsWith(base): loc; - return loc.substring(base.length()); - } - - @Test - @Issue("JENKINS-18407") - public void testQueueSuccessBehavior() { - // prevent any builds to test the behaviour - j.jenkins.numExecutors = 0; - - def p = j.createFreeStyleProject() - def f = p.scheduleBuild2(0) - assert f!=null; - def g = p.scheduleBuild2(0) - assert f==g; - - p.makeDisabled(true) - assert p.scheduleBuild2(0)==null - } - - /** - * Do the same as {@link #testQueueSuccessBehavior()} but over HTTP - */ - @Test - @Issue("JENKINS-18407") - public void testQueueSuccessBehaviorOverHTTP() { - // prevent any builds to test the behaviour - j.jenkins.numExecutors = 0; - - def p = j.createFreeStyleProject() - def wc = j.createWebClient(); - - def rsp = wc.getPage("${j.getURL()}${p.url}build").webResponse - assert rsp.statusCode==201; - assert rsp.getResponseHeaderValue("Location")!=null; - - def rsp2 = wc.getPage("${j.getURL()}${p.url}build").webResponse - assert rsp2.statusCode==201; - assert rsp.getResponseHeaderValue("Location")==rsp2.getResponseHeaderValue("Location") - - p.makeDisabled(true) - - try { - wc.getPage("${j.getURL()}${p.url}build") - fail(); - } catch (FailingHttpStatusCodeException e) { - // request should fail - } - } - - /** - * We used to store {@link AbstractProject#triggers} as {@link Vector}, so make sure - * we can still read back the configuration from that. - */ - @Test - public void testVectorTriggers() { - AbstractProject p = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml")) - assert p.triggers().size()==1 - def t = p.triggers()[0] - assert t.class==SCMTrigger.class; - assert t.spec=="*/10 * * * *" - } - - @Test - @Issue("JENKINS-18813") - public void testRemoveTrigger() { - AbstractProject p = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml")) - - TriggerDescriptor SCM_TRIGGER_DESCRIPTOR = j.jenkins.getDescriptorOrDie(SCMTrigger.class) - p.removeTrigger(SCM_TRIGGER_DESCRIPTOR); - assert p.triggers().size()==0 - } - - @Test - @Issue("JENKINS-18813") - public void testAddTriggerSameType() { - AbstractProject p = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml")) - - def newTrigger = new SCMTrigger("H/5 * * * *") - p.addTrigger(newTrigger); - - assert p.triggers().size()==1 - def t = p.triggers()[0] - assert t.class==SCMTrigger.class; - assert t.spec=="H/5 * * * *" - } - - @Test - @Issue("JENKINS-18813") - public void testAddTriggerDifferentType() { - AbstractProject p = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml")) - - def newTrigger = new TimerTrigger("20 * * * *") - p.addTrigger(newTrigger); - - assert p.triggers().size()==2 - def t = p.triggers()[1] - assert t == newTrigger - } - - /** - * Trying to POST to config.xml by a different job type should fail. - */ - @Test - public void testConfigDotXmlSubmissionToDifferentType() { - j.jenkins.pluginManager.installDetachedPlugin("javadoc") - j.jenkins.pluginManager.installDetachedPlugin("junit") - j.jenkins.pluginManager.installDetachedPlugin("display-url-api") - j.jenkins.pluginManager.installDetachedPlugin("mailer") - j.jenkins.pluginManager.installDetachedPlugin("maven-plugin") - - j.jenkins.crumbIssuer = null - def p = j.createFreeStyleProject() - - HttpURLConnection con = postConfigDotXml(p, "") - - // this should fail with a type mismatch error - // the error message should report both what was submitted and what was expected - assert con.responseCode == 500 - def msg = con.errorStream.text - println msg - assert msg.contains(FreeStyleProject.class.name) - assert msg.contains(MavenModuleSet.class.name) - - // control. this should work - con = postConfigDotXml(p, "") - assert con.responseCode == 200 - } - - private HttpURLConnection postConfigDotXml(FreeStyleProject p, String xml) { - HttpURLConnection con = new URL(j.getURL(), "job/${p.name}/config.xml").openConnection() - con.requestMethod = "POST" - con.setRequestProperty("Content-Type", "application/xml") - con.doOutput = true - con.outputStream.withStream { s -> - s.write(xml.bytes) - } - return con - } - - @Test - @Issue("JENKINS-27549") - public void testLoadingWithNPEOnTriggerStart() { - AbstractProject project = j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/npeTrigger.xml")) - - assert project.triggers().size() == 1 - } - - @Test - @Issue("JENKINS-30742") - public void testResolveForCLI() { - try { - AbstractProject not_found = AbstractProject.resolveForCLI("never_created"); - fail("Exception should occur before!"); - } catch (CmdLineException e) { - assert e.getMessage().contentEquals("No such job \u2018never_created\u2019 exists."); - } - - AbstractProject project = j.jenkins.createProject(FreeStyleProject.class, "never_created"); - try { - AbstractProject not_found = AbstractProject.resolveForCLI("never_created1"); - fail("Exception should occur before!"); - } catch (CmdLineException e) { - assert e.getMessage().contentEquals("No such job \u2018never_created1\u2019 exists. Perhaps you meant \u2018never_created\u2019?") - } - - } - - static class MockBuildTriggerThrowsNPEOnStart extends Trigger { - @Override - public void start(hudson.model.Item project, boolean newInstance) { throw new NullPointerException(); } - - @Override - public TriggerDescriptor getDescriptor() { - return DESCRIPTOR; - } - - public static final TriggerDescriptor DESCRIPTOR = new DescriptorImpl() - - @TestExtension("testLoadingWithNPEOnTriggerStart") - static class DescriptorImpl extends TriggerDescriptor { - - public boolean isApplicable(hudson.model.Item item) { - return false; - } - } - } -} diff --git a/test/src/test/java/hudson/model/AbstractProject2Test.java b/test/src/test/java/hudson/model/AbstractProject2Test.java deleted file mode 100644 index a5f330ca43..0000000000 --- a/test/src/test/java/hudson/model/AbstractProject2Test.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * The MIT License - * - * Copyright 2017 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 hudson.model; - -import hudson.tasks.BuildTrigger; -import java.util.Collections; -import jenkins.model.Jenkins; -import static org.hamcrest.Matchers.*; -import org.junit.Test; -import static org.junit.Assert.*; -import org.junit.Rule; -import org.jvnet.hudson.test.Issue; -import org.jvnet.hudson.test.JenkinsRule; -import org.jvnet.hudson.test.MockAuthorizationStrategy; - -// TODO merge with AbstractProjectTest when converted from Groovy to Java -public class AbstractProject2Test { - - @Rule - public JenkinsRule r = new JenkinsRule(); - - @Issue("SECURITY-617") - @Test - public void upstreamDownstreamExportApi() throws Exception { - FreeStyleProject us = r.createFreeStyleProject("upstream-project"); - FreeStyleProject ds = r.createFreeStyleProject("downstream-project"); - us.getPublishersList().add(new BuildTrigger(Collections.singleton(ds), Result.SUCCESS)); - r.jenkins.rebuildDependencyGraph(); - assertEquals(Collections.singletonList(ds), us.getDownstreamProjects()); - assertEquals(Collections.singletonList(us), ds.getUpstreamProjects()); - r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); - r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy(). - grant(Jenkins.READ).everywhere().toEveryone(). - grant(Item.READ).everywhere().to("alice"). - grant(Item.READ).onItems(us).to("bob"). - grant(Item.READ).onItems(ds).to("charlie")); - String api = r.createWebClient().login("alice").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); - System.out.println(api); - assertThat(api, containsString("downstream-project")); - api = r.createWebClient().login("alice").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); - System.out.println(api); - assertThat(api, containsString("upstream-project")); - api = r.createWebClient().login("bob").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); - System.out.println(api); - assertThat(api, not(containsString("downstream-project"))); - api = r.createWebClient().login("charlie").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); - System.out.println(api); - assertThat(api, not(containsString("upstream-project"))); - } - -} diff --git a/test/src/test/java/hudson/model/AbstractProjectTest.java b/test/src/test/java/hudson/model/AbstractProjectTest.java new file mode 100644 index 0000000000..308fc12993 --- /dev/null +++ b/test/src/test/java/hudson/model/AbstractProjectTest.java @@ -0,0 +1,646 @@ +/* + * 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.model; + +import com.gargoylesoftware.htmlunit.ElementNotFoundException; +import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlInput; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.FilePath; +import hudson.Functions; +import hudson.Launcher; +import hudson.Util; +import hudson.maven.MavenModuleSet; +import hudson.scm.NullSCM; +import hudson.scm.SCM; +import hudson.scm.SCMDescriptor; +import hudson.security.GlobalMatrixAuthorizationStrategy; +import hudson.tasks.ArtifactArchiver; +import hudson.tasks.BatchFile; +import hudson.tasks.BuildTrigger; +import hudson.tasks.Shell; +import hudson.triggers.SCMTrigger; +import hudson.triggers.TimerTrigger; +import hudson.triggers.Trigger; +import hudson.triggers.TriggerDescriptor; +import hudson.util.OneShotEvent; +import hudson.util.StreamTaskListener; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Collections; +import java.util.ResourceBundle; +import java.util.Vector; +import java.util.concurrent.Future; +import jenkins.model.Jenkins; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import org.junit.Assume; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.MockAuthorizationStrategy; +import org.jvnet.hudson.test.MockFolder; +import org.jvnet.hudson.test.TestExtension; +import org.jvnet.hudson.test.TestPluginManager; +import org.jvnet.hudson.test.recipes.PresetData; +import org.jvnet.hudson.test.recipes.PresetData.DataSet; +import org.kohsuke.args4j.CmdLineException; + +public class AbstractProjectTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + public void configRoundtrip() throws Exception { + FreeStyleProject project = j.createFreeStyleProject(); + Label l = j.jenkins.getLabel("foo && bar"); + project.setAssignedLabel(l); + j.configRoundtrip(project); + + assertEquals(l, project.getAssignedLabel()); + } + + /** + * Tests the workspace deletion. + */ + @Test + public void wipeWorkspace() throws Exception { + FreeStyleProject project = j.createFreeStyleProject(); + project.getBuildersList().add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello")); + + FreeStyleBuild b = project.scheduleBuild2(0).get(); + + assertTrue("Workspace should exist by now", b.getWorkspace().exists()); + + project.doDoWipeOutWorkspace(); + + assertFalse("Workspace should be gone by now", b.getWorkspace().exists()); + } + + /** + * Makes sure that the workspace deletion is protected. + */ + @Test + @PresetData(DataSet.NO_ANONYMOUS_READACCESS) + public void wipeWorkspaceProtected() throws Exception { + FreeStyleProject project = j.createFreeStyleProject(); + project.getBuildersList().add(Functions.isWindows() ? new BatchFile("echo hello") : new Shell("echo hello")); + + FreeStyleBuild b = project.scheduleBuild2(0).get(); + + assert b.getWorkspace().exists() : "Workspace should exist by now"; + + // make sure that the action link is protected + JenkinsRule.WebClient wc = j.createWebClient(); + try { + wc.getPage(new WebRequest(new URL(wc.getContextPath() + project.getUrl() + "doWipeOutWorkspace"), HttpMethod.POST)); + fail("Expected HTTP status code 403"); + } catch (FailingHttpStatusCodeException e) { + assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getStatusCode()); + } + } + + /** + * Makes sure that the workspace deletion link is not provided when the user + * doesn't have an access. + */ + @Test + @PresetData(DataSet.ANONYMOUS_READONLY) + public void wipeWorkspaceProtected2() throws Exception { + ((GlobalMatrixAuthorizationStrategy) j.jenkins.getAuthorizationStrategy()).add(AbstractProject.WORKSPACE, "anonymous"); + + // make sure that the deletion is protected in the same way + wipeWorkspaceProtected(); + + // there shouldn't be any "wipe out workspace" link for anonymous user + JenkinsRule.WebClient webClient = j.createWebClient(); + HtmlPage page = webClient.getPage(j.jenkins.getItem("test0")); + + page = (HtmlPage) page.getAnchorByText("Workspace").click(); + try { + String wipeOutLabel = ResourceBundle.getBundle("hudson/model/AbstractProject/sidepanel").getString("Wipe Out Workspace"); + page.getAnchorByText(wipeOutLabel); + fail("shouldn't find a link"); + } catch (ElementNotFoundException e) { + // OK + } + } + + /** + * Tests the <optionalBlock @field> round trip behavior by using + * {@link AbstractProject#concurrentBuild} + */ + @Test + public void optionalBlockDataBindingRoundtrip() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + for (boolean b : new boolean[] {true, false}) { + p.setConcurrentBuild(b); + j.submit(j.createWebClient().getPage(p, "configure").getFormByName("config")); + assertEquals(b, p.isConcurrentBuild()); + } + } + + /** + * Tests round trip configuration of the blockBuildWhenUpstreamBuilding + * field + */ + @Test + @Issue("JENKINS-4423") + public void configuringBlockBuildWhenUpstreamBuildingRoundtrip() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + p.setBlockBuildWhenUpstreamBuilding(false); + + HtmlForm form = j.createWebClient().getPage(p, "configure").getFormByName("config"); + HtmlInput input = form.getInputByName("blockBuildWhenUpstreamBuilding"); + assertFalse("blockBuildWhenUpstreamBuilding check box is checked.", input.isChecked()); + + input.setChecked(true); + j.submit(form); + assertTrue("blockBuildWhenUpstreamBuilding was not updated from configuration form", p.blockBuildWhenUpstreamBuilding()); + + form = j.createWebClient().getPage(p, "configure").getFormByName("config"); + input = form.getInputByName("blockBuildWhenUpstreamBuilding"); + assert input.isChecked() : "blockBuildWhenUpstreamBuilding check box is not checked."; + } + + /** + * Unless the concurrent build option is enabled, polling and build should + * be mutually exclusive to avoid allocating unnecessary workspaces. + */ + @Test + @Issue("JENKINS-4202") + public void pollingAndBuildExclusion() throws Exception { + final OneShotEvent sync = new OneShotEvent(); + + final FreeStyleProject p = j.createFreeStyleProject(); + FreeStyleBuild b1 = j.buildAndAssertSuccess(p); + + p.setScm(new NullSCM() { + @Override + public boolean pollChanges(AbstractProject project, Launcher launcher, FilePath workspace, TaskListener listener) { + try { + sync.block(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + } + + /** + * Don't write 'this', so that subtypes can be implemented as + * anonymous class. + */ + private Object writeReplace() { + return new Object(); + } + + @Override + public boolean requiresWorkspaceForPolling() { + return true; + } + @Override + public SCMDescriptor getDescriptor() { + return new SCMDescriptor(null) { + }; + } + }); + Thread t = new Thread() { + @Override + public void run() { + p.pollSCMChanges(StreamTaskListener.fromStdout()); + } + }; + try { + t.start(); + Future f = p.scheduleBuild2(0); + + // add a bit of delay to make sure that the blockage is happening + Thread.sleep(3000); + + // release the polling + sync.signal(); + + FreeStyleBuild b2 = j.assertBuildStatusSuccess(f); + + // they should have used the same workspace. + assertEquals(b1.getWorkspace(), b2.getWorkspace()); + } finally { + t.interrupt(); + } + } + + @Test + @Issue("JENKINS-1986") + public void buildSymlinks() throws Exception { + Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows()); + + FreeStyleProject job = j.createFreeStyleProject(); + job.getBuildersList().add(new Shell("echo \"Build #$BUILD_NUMBER\"\n")); + FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserCause()).get(); + File lastSuccessful = new File(job.getRootDir(), "lastSuccessful"), + lastStable = new File(job.getRootDir(), "lastStable"); + // First build creates links + assertSymlinkForBuild(lastSuccessful, 1); + assertSymlinkForBuild(lastStable, 1); + FreeStyleBuild build2 = job.scheduleBuild2(0, new Cause.UserCause()).get(); + // Another build updates links + assertSymlinkForBuild(lastSuccessful, 2); + assertSymlinkForBuild(lastStable, 2); + // Delete latest build should update links + build2.delete(); + assertSymlinkForBuild(lastSuccessful, 1); + assertSymlinkForBuild(lastStable, 1); + // Delete all builds should remove links + build.delete(); + assertFalse("lastSuccessful link should be removed", lastSuccessful.exists()); + assertFalse("lastStable link should be removed", lastStable.exists()); + } + + private static void assertSymlinkForBuild(File file, int buildNumber) + throws IOException, InterruptedException { + assert file.exists() : "should exist and point to something that exists"; + assert Util.isSymlink(file) : "should be symlink"; + String s = FileUtils.readFileToString(new File(file, "log")); + assert s.contains("Build #" + buildNumber + "\n") : "link should point to build #$buildNumber, but link was: ${Util.resolveSymlink(file, TaskListener.NULL)}\nand log was:\n$s"; + } + + @Test + @Issue("JENKINS-2543") + public void symlinkForPostBuildFailure() throws Exception { + Assume.assumeFalse("If we're on Windows, don't bother doing this", Functions.isWindows()); + + // Links should be updated after post-build actions when final build result is known + FreeStyleProject job = j.createFreeStyleProject(); + job.getBuildersList().add(new Shell("echo \"Build #$BUILD_NUMBER\"\n")); + FreeStyleBuild build = job.scheduleBuild2(0, new Cause.UserCause()).get(); + assert Result.SUCCESS == build.getResult(); + File lastSuccessful = new File(job.getRootDir(), "lastSuccessful"), + lastStable = new File(job.getRootDir(), "lastStable"); + // First build creates links + assertSymlinkForBuild(lastSuccessful, 1); + assertSymlinkForBuild(lastStable, 1); + // Archive artifacts that don't exist to create failure in post-build action + job.getPublishersList().add(new ArtifactArchiver("*.foo", "", false, false)); + build = job.scheduleBuild2(0, new Cause.UserCause()).get(); + assert Result.FAILURE == build.getResult(); + // Links should not be updated since build failed + assertSymlinkForBuild(lastSuccessful, 1); + assertSymlinkForBuild(lastStable, 1); + } + + /* TODO too slow, seems capable of causing testWorkspaceLock to time out: + @Test + @Issue("JENKINS-15156") + public void testGetBuildAfterGC() { + FreeStyleProject job = j.createFreeStyleProject(); + job.scheduleBuild2(0, new Cause.UserIdCause()).get(); + j.jenkins.queue.clearLeftItems(); + MemoryAssert.assertGC(new WeakReference(job.getLastBuild())); + assert job.lastBuild != null; + } + */ + @Test + @Issue("JENKINS-17137") + public void externalBuildDirectorySymlinks() throws Exception { + Assume.assumeFalse(Functions.isWindows()); // symlinks may not be available + HtmlForm form = j.createWebClient().goTo("configure").getFormByName("config"); + File builds = j.createTmpDir(); + form.getInputByName("_.rawBuildsDir").setValueAttribute(builds.toString() + "/${ITEM_FULL_NAME}"); + j.submit(form); + assertEquals(builds.toString() + "/${ITEM_FULL_NAME}", j.jenkins.getRawBuildsDir()); + FreeStyleProject p = j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "p"); + FreeStyleBuild b1 = p.scheduleBuild2(0).get(); + File link = new File(p.getRootDir(), "lastStable"); + assertTrue(link.exists()); + assertEquals(resolveAll(link).getAbsolutePath(), b1.getRootDir().getAbsolutePath()); + FreeStyleBuild b2 = p.scheduleBuild2(0).get(); + assertTrue(link.exists()); + assertEquals(resolveAll(link).getAbsolutePath(), b2.getRootDir().getAbsolutePath()); + b2.delete(); + assertTrue(link.exists()); + assertEquals(resolveAll(link).getAbsolutePath(), b1.getRootDir().getAbsolutePath()); + b1.delete(); + assertFalse(link.exists()); + } + + private File resolveAll(File link) throws InterruptedException, IOException { + while (true) { + File f = Util.resolveSymlinkToFile(link); + if (f == null) { + return link; + } + link = f; + } + } + + @Test + @Issue("JENKINS-17138") + public void externalBuildDirectoryRenameDelete() throws Exception { + HtmlForm form = j.createWebClient().goTo("configure").getFormByName("config"); + File builds = j.createTmpDir(); + form.getInputByName("_.rawBuildsDir").setValueAttribute(builds.toString() + "/${ITEM_FULL_NAME}"); + j.submit(form); + assertEquals(builds.toString() + "/${ITEM_FULL_NAME}", j.jenkins.getRawBuildsDir()); + FreeStyleProject p = j.jenkins.createProject(MockFolder.class, "d").createProject(FreeStyleProject.class, "prj"); + FreeStyleBuild b = p.scheduleBuild2(0).get(); + File oldBuildDir = new File(builds, "d/prj"); + assertEquals(new File(oldBuildDir, b.getId()), b.getRootDir()); + assertTrue(b.getRootDir().isDirectory()); + p.renameTo("proj"); + File newBuildDir = new File(builds, "d/proj"); + assertEquals(new File(newBuildDir, b.getId()), b.getRootDir()); + assertTrue(b.getRootDir().isDirectory()); + p.delete(); + assertFalse(b.getRootDir().isDirectory()); + } + + @Test + @Issue("JENKINS-18678") + public void renameJobLostBuilds() throws Exception { + FreeStyleProject p = j.createFreeStyleProject("initial"); + j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + assertEquals(1, p.getBuilds().size()); + p.renameTo("edited"); + p._getRuns().purgeCache(); + assertEquals(1, p.getBuilds().size()); + MockFolder d = j.jenkins.createProject(MockFolder.class, "d"); + Items.move(p, d); + assertEquals(p, j.jenkins.getItemByFullName("d/edited")); + p._getRuns().purgeCache(); + assertEquals(1, p.getBuilds().size()); + d.renameTo("d2"); + p = j.jenkins.getItemByFullName("d2/edited", FreeStyleProject.class); + p._getRuns().purgeCache(); + assertEquals(1, p.getBuilds().size()); + } + + @Test + @Issue("JENKINS-17575") + public void deleteRedirect() throws Exception { + j.createFreeStyleProject("j1"); + assertEquals("", deleteRedirectTarget("job/j1")); + j.createFreeStyleProject("j2"); + Jenkins.getInstance().addView(new AllView("v1")); + assertEquals("view/v1/", deleteRedirectTarget("view/v1/job/j2")); + MockFolder d = Jenkins.getInstance().createProject(MockFolder.class, "d"); + d.addView(new AllView("v2")); + for (String n : new String[] {"j3", "j4", "j5"}) { + d.createProject(FreeStyleProject.class, n); + } + assertEquals("job/d/", deleteRedirectTarget("job/d/job/j3")); + assertEquals("job/d/view/v2/", deleteRedirectTarget("job/d/view/v2/job/j4")); + assertEquals("view/v1/job/d/", deleteRedirectTarget("view/v1/job/d/job/j5")); + assertEquals("view/v1/", deleteRedirectTarget("view/v1/job/d")); // JENKINS-23375 + } + + private String deleteRedirectTarget(String job) throws Exception { + JenkinsRule.WebClient wc = j.createWebClient(); + String base = wc.getContextPath(); + String loc = wc.getPage(wc.addCrumb(new WebRequest(new URL(base + job + "/doDelete"), HttpMethod.POST))).getUrl().toString(); + assert loc.startsWith(base) : loc; + return loc.substring(base.length()); + } + + @Test + @Issue("JENKINS-18407") + public void queueSuccessBehavior() throws Exception { + // prevent any builds to test the behaviour + j.jenkins.setNumExecutors(0); + + FreeStyleProject p = j.createFreeStyleProject(); + Future f = p.scheduleBuild2(0); + assertNotNull(f); + Future g = p.scheduleBuild2(0); + assertEquals(f, g); + + p.makeDisabled(true); + assertNull(p.scheduleBuild2(0)); + } + + /** + * Do the same as {@link #testQueueSuccessBehavior()} but over HTTP + */ + @Test + @Issue("JENKINS-18407") + public void queueSuccessBehaviorOverHTTP() throws Exception { + // prevent any builds to test the behaviour + j.jenkins.setNumExecutors(0); + + FreeStyleProject p = j.createFreeStyleProject(); + JenkinsRule.WebClient wc = j.createWebClient(); + + WebResponse rsp = wc.getPage(j.getURL() + p.getUrl() + "build").getWebResponse(); + assertEquals(201, rsp.getStatusCode()); + assertNotNull(rsp.getResponseHeaderValue("Location")); + + WebResponse rsp2 = wc.getPage(j.getURL() + p.getUrl() + "build").getWebResponse(); + assertEquals(201, rsp2.getStatusCode()); + assertEquals(rsp.getResponseHeaderValue("Location"), rsp2.getResponseHeaderValue("Location")); + + p.makeDisabled(true); + + try { + wc.getPage(j.getURL() + p.getUrl() + "build"); + fail(); + } catch (FailingHttpStatusCodeException e) { + // request should fail + } + } + + /** + * We used to store {@link AbstractProject#triggers} as {@link Vector}, so + * make sure we can still read back the configuration from that. + */ + @Test + public void vectorTriggers() throws Exception { + AbstractProject p = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml")); + assertEquals(1, p.triggers().size()); + Trigger t = p.triggers().get(0); + assertEquals(SCMTrigger.class, t.getClass()); + assertEquals("*/10 * * * *", t.getSpec()); + } + + @Test + @Issue("JENKINS-18813") + public void removeTrigger() throws Exception { + AbstractProject p = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml")); + + TriggerDescriptor SCM_TRIGGER_DESCRIPTOR = (TriggerDescriptor) j.jenkins.getDescriptorOrDie(SCMTrigger.class); + p.removeTrigger(SCM_TRIGGER_DESCRIPTOR); + assertEquals(0, p.triggers().size()); + } + + @Test + @Issue("JENKINS-18813") + public void addTriggerSameType() throws Exception { + AbstractProject p = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml")); + + SCMTrigger newTrigger = new SCMTrigger("H/5 * * * *"); + p.addTrigger(newTrigger); + + assertEquals(1, p.triggers().size()); + Trigger t = p.triggers().get(0); + assertEquals(SCMTrigger.class, t.getClass()); + assertEquals("H/5 * * * *", t.getSpec()); + } + + @Test + @Issue("JENKINS-18813") + public void addTriggerDifferentType() throws Exception { + AbstractProject p = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/vectorTriggers.xml")); + + TimerTrigger newTrigger = new TimerTrigger("20 * * * *"); + p.addTrigger(newTrigger); + + assertEquals(2, p.triggers().size()); + Trigger t = p.triggers().get(1); + assertEquals(newTrigger, t); + } + + /** + * Trying to POST to config.xml by a different job type should fail. + */ + @Test + public void configDotXmlSubmissionToDifferentType() throws Exception { + TestPluginManager tpm = (TestPluginManager) j.jenkins.pluginManager; + tpm.installDetachedPlugin("javadoc"); + tpm.installDetachedPlugin("junit"); + tpm.installDetachedPlugin("display-url-api"); + tpm.installDetachedPlugin("mailer"); + tpm.installDetachedPlugin("maven-plugin"); + + j.jenkins.setCrumbIssuer(null); + FreeStyleProject p = j.createFreeStyleProject(); + + HttpURLConnection con = postConfigDotXml(p, ""); + + // this should fail with a type mismatch error + // the error message should report both what was submitted and what was expected + assertEquals(500, con.getResponseCode()); + String msg = IOUtils.toString(con.getErrorStream()); + System.out.println(msg); + assertThat(msg, allOf(containsString(FreeStyleProject.class.getName()), containsString(MavenModuleSet.class.getName()))); + + // control. this should work + con = postConfigDotXml(p, ""); + assertEquals(200, con.getResponseCode()); + } + + private HttpURLConnection postConfigDotXml(FreeStyleProject p, String xml) throws Exception { + HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(), "job/" + p.getName() + "/config.xml").openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/xml"); + con.setDoOutput(true); + try (OutputStream s = con.getOutputStream()) { + s.write(xml.getBytes()); + } + return con; + } + + @Test + @Issue("JENKINS-27549") + public void loadingWithNPEOnTriggerStart() throws Exception { + AbstractProject project = (AbstractProject) j.jenkins.createProjectFromXML("foo", getClass().getResourceAsStream("AbstractProjectTest/npeTrigger.xml")); + + assertEquals(1, project.triggers().size()); + } + + @Test + @Issue("JENKINS-30742") + public void resolveForCLI() throws Exception { + try { + AbstractProject not_found = AbstractProject.resolveForCLI("never_created"); + fail("Exception should occur before!"); + } catch (CmdLineException e) { + assertEquals("No such job \u2018never_created\u2019 exists.", e.getMessage()); + } + + AbstractProject project = j.jenkins.createProject(FreeStyleProject.class, "never_created"); + try { + AbstractProject not_found = AbstractProject.resolveForCLI("never_created1"); + fail("Exception should occur before!"); + } catch (CmdLineException e) { + assertEquals("No such job \u2018never_created1\u2019 exists. Perhaps you meant \u2018never_created\u2019?", e.getMessage()); + } + + } + + public static class MockBuildTriggerThrowsNPEOnStart extends Trigger { + @Override + public void start(hudson.model.Item project, boolean newInstance) { + throw new NullPointerException(); + } + + @TestExtension("loadingWithNPEOnTriggerStart") + public static class DescriptorImpl extends TriggerDescriptor { + + @Override + public boolean isApplicable(hudson.model.Item item) { + return false; + } + } + + } + + @Issue("SECURITY-617") + @Test + public void upstreamDownstreamExportApi() throws Exception { + FreeStyleProject us = j.createFreeStyleProject("upstream-project"); + FreeStyleProject ds = j.createFreeStyleProject("downstream-project"); + us.getPublishersList().add(new BuildTrigger(Collections.singleton(ds), Result.SUCCESS)); + j.jenkins.rebuildDependencyGraph(); + assertEquals(Collections.singletonList(ds), us.getDownstreamProjects()); + assertEquals(Collections.singletonList(us), ds.getUpstreamProjects()); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy(). + grant(Jenkins.READ).everywhere().toEveryone(). + grant(Item.READ).everywhere().to("alice"). + grant(Item.READ).onItems(us).to("bob"). + grant(Item.READ).onItems(ds).to("charlie")); + String api = j.createWebClient().login("alice").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); + System.out.println(api); + assertThat(api, containsString("downstream-project")); + api = j.createWebClient().login("alice").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); + System.out.println(api); + assertThat(api, containsString("upstream-project")); + api = j.createWebClient().login("bob").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); + System.out.println(api); + assertThat(api, not(containsString("downstream-project"))); + api = j.createWebClient().login("charlie").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); + System.out.println(api); + assertThat(api, not(containsString("upstream-project"))); + } + +} -- GitLab From b81a8ec736931d6346fea31a40d4dc11550dc01a Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 17:41:42 -0500 Subject: [PATCH 0123/1380] EnvironmentContributorTest --- .../model/EnvironmentContributorTest.groovy | 41 ------------------- .../model/EnvironmentContributorTest.java | 40 ++++++++++++++++++ 2 files changed, 40 insertions(+), 41 deletions(-) delete mode 100644 test/src/test/groovy/hudson/model/EnvironmentContributorTest.groovy create mode 100644 test/src/test/java/hudson/model/EnvironmentContributorTest.java diff --git a/test/src/test/groovy/hudson/model/EnvironmentContributorTest.groovy b/test/src/test/groovy/hudson/model/EnvironmentContributorTest.groovy deleted file mode 100644 index fab4c865f6..0000000000 --- a/test/src/test/groovy/hudson/model/EnvironmentContributorTest.groovy +++ /dev/null @@ -1,41 +0,0 @@ -package hudson.model - -import hudson.EnvVars -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.CaptureEnvironmentBuilder -import org.jvnet.hudson.test.JenkinsRule -import org.jvnet.hudson.test.TestExtension - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class EnvironmentContributorTest { - @Rule - public JenkinsRule j = new JenkinsRule() - - /** - * Makes sure that the project-scoped environment variables are getting consulted. - */ - @Test - public void testProjectScoped() { - def p = j.createFreeStyleProject() - def c = new CaptureEnvironmentBuilder() - p.buildersList.add(c) - p.description = "Issac Newton"; - j.assertBuildStatusSuccess(p.scheduleBuild2(0)) - - assert c.envVars["ABC"]=="Issac Newton"; - assert c.envVars["NODE_NAME"]=="master"; - } - - @TestExtension("testProjectScoped") - public static class JobScopedInjection extends EnvironmentContributor { - @Override - void buildEnvironmentFor(Job j, EnvVars envs, TaskListener listener) { - envs.put("ABC",j.description) - } - } -} diff --git a/test/src/test/java/hudson/model/EnvironmentContributorTest.java b/test/src/test/java/hudson/model/EnvironmentContributorTest.java new file mode 100644 index 0000000000..f7a99814e8 --- /dev/null +++ b/test/src/test/java/hudson/model/EnvironmentContributorTest.java @@ -0,0 +1,40 @@ +package hudson.model; + +import hudson.EnvVars; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.CaptureEnvironmentBuilder; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; + +public class EnvironmentContributorTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + /** + * Makes sure that the project-scoped environment variables are getting + * consulted. + */ + @Test + public void projectScoped() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + CaptureEnvironmentBuilder c = new CaptureEnvironmentBuilder(); + p.getBuildersList().add(c); + p.setDescription("Issac Newton"); + j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + + assertEquals("Issac Newton", c.getEnvVars().get("ABC")); + assertEquals("master", c.getEnvVars().get("NODE_NAME")); + } + + @TestExtension("projectScoped") + public static class JobScopedInjection extends EnvironmentContributor { + @Override + public void buildEnvironmentFor(Job j, EnvVars envs, TaskListener listener) { + envs.put("ABC", j.getDescription()); + } + } + +} -- GitLab From 54a44b7d587affb86e9f881b76afa70fe317eb6b Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 17:52:43 -0500 Subject: [PATCH 0124/1380] SlaveTest --- .../test/groovy/hudson/model/SlaveTest.groovy | 104 --------------- .../model/{Slave2Test.java => SlaveTest.java} | 123 +++++++++++++----- 2 files changed, 90 insertions(+), 137 deletions(-) delete mode 100644 test/src/test/groovy/hudson/model/SlaveTest.groovy rename test/src/test/java/hudson/model/{Slave2Test.java => SlaveTest.java} (69%) diff --git a/test/src/test/groovy/hudson/model/SlaveTest.groovy b/test/src/test/groovy/hudson/model/SlaveTest.groovy deleted file mode 100644 index 35299a7e4f..0000000000 --- a/test/src/test/groovy/hudson/model/SlaveTest.groovy +++ /dev/null @@ -1,104 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2004-2009, Sun Microsystems, 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 hudson.model - -import static hudson.util.FormValidation.Kind.ERROR -import static hudson.util.FormValidation.Kind.WARNING -import static org.junit.Assert.assertNotNull -import static org.junit.Assert.assertEquals - -import hudson.security.csrf.CrumbIssuer -import hudson.slaves.DumbSlave -import hudson.slaves.JNLPLauncher -import hudson.util.FormValidation -import org.junit.Rule -import org.junit.Test -import org.apache.commons.io.IOUtils -import org.jvnet.hudson.test.GroovyJenkinsRule - -/** - * @author Kohsuke Kawaguchi - */ -class SlaveTest { - - @Rule - public GroovyJenkinsRule j = new GroovyJenkinsRule() - - /** - * Makes sure that a form validation method gets inherited. - */ - @Test - void formValidation() { - j.executeOnServer { - assertNotNull(j.jenkins.getDescriptor(DumbSlave).getCheckUrl("remoteFS")) - } - } - - /** - * Programmatic config.xml submission. - */ - @Test - void slaveConfigDotXml() { - DumbSlave s = j.createSlave() - def wc = j.createWebClient() - def p = wc.goTo("computer/${s.name}/config.xml", "application/xml") - def xml = p.webResponse.contentAsString - new XmlSlurper().parseText(xml) // verify that it is XML - - // make sure it survives the roundtrip - post("computer/${s.name}/config.xml",xml); - - assertNotNull(j.jenkins.getNode(s.name)) - - xml = IOUtils.toString(getClass().getResource("SlaveTest/slave.xml").openStream()); - xml = xml.replace("NAME",s.name) - post("computer/${s.name}/config.xml",xml); - - s = j.jenkins.getNode(s.name) - assertNotNull(s) - assertEquals("some text",s.nodeDescription) - assertEquals(JNLPLauncher.class,s.launcher.class) - } - - def post(url,String xml) { - HttpURLConnection con = new URL(j.getURL(),url).openConnection(); - con.requestMethod = "POST" - con.setRequestProperty("Content-Type","application/xml;charset=UTF-8") - con.setRequestProperty(CrumbIssuer.DEFAULT_CRUMB_NAME,"test") - con.doOutput = true; - con.outputStream.write(xml.getBytes("UTF-8")) - con.outputStream.close(); - IOUtils.copy(con.inputStream,System.out) - } - - @Test - void remoteFsCheck() { - def d = j.jenkins.getDescriptorByType(DumbSlave.DescriptorImpl.class) - assert d.doCheckRemoteFS("c:\\")==FormValidation.ok(); - assert d.doCheckRemoteFS("/tmp")==FormValidation.ok(); - assert d.doCheckRemoteFS("relative/path").kind==WARNING; - assert d.doCheckRemoteFS("/net/foo/bar/zot").kind==WARNING; - assert d.doCheckRemoteFS("\\\\machine\\folder\\foo").kind==WARNING; - } -} diff --git a/test/src/test/java/hudson/model/Slave2Test.java b/test/src/test/java/hudson/model/SlaveTest.java similarity index 69% rename from test/src/test/java/hudson/model/Slave2Test.java rename to test/src/test/java/hudson/model/SlaveTest.java index 6ab40e3eed..76823820c3 100644 --- a/test/src/test/java/hudson/model/Slave2Test.java +++ b/test/src/test/java/hudson/model/SlaveTest.java @@ -1,7 +1,7 @@ /* * The MIT License * - * Copyright (c) 2015 CloudBees, Inc. + * Copyright (c) 2004-2009, Sun Microsystems, 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 @@ -23,59 +23,117 @@ */ package hudson.model; +import com.gargoylesoftware.htmlunit.Page; +import groovy.util.XmlSlurper; import hudson.DescriptorExtensionList; import hudson.ExtensionList; +import hudson.security.csrf.CrumbIssuer; import hudson.slaves.ComputerLauncher; import hudson.slaves.DumbSlave; +import hudson.slaves.JNLPLauncher; import hudson.slaves.NodeProperty; import hudson.slaves.NodePropertyDescriptor; import hudson.slaves.RetentionStrategy; +import hudson.util.FormValidation; +import static hudson.util.FormValidation.Kind.WARNING; +import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.URL; import java.net.URLEncoder; import java.util.HashSet; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; -import org.junit.Rule; -import org.junit.Test; -import org.jvnet.hudson.test.JenkinsRule; - +import org.apache.commons.io.IOUtils; import static org.hamcrest.Matchers.anyOf; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeThat; - +import org.junit.Rule; +import org.junit.Test; import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.TestExtension; -/** - * Tests for the {@link Slave} class. - * There is also a Groovy implementation of such test file, hence the class name - * has an index. - * @author Oleg Nenashev - */ -public class Slave2Test { - +public class SlaveTest { + @Rule - public JenkinsRule rule = new JenkinsRule(); - + public JenkinsRule j = new JenkinsRule(); + + /** + * Makes sure that a form validation method gets inherited. + */ + @Test + public void formValidation() throws Exception { + j.executeOnServer(() -> { + assertNotNull(j.jenkins.getDescriptor(DumbSlave.class).getCheckUrl("remoteFS")); + return null; + }); + } + + /** + * Programmatic config.xml submission. + */ + @Test + public void slaveConfigDotXml() throws Exception { + DumbSlave s = j.createSlave(); + JenkinsRule.WebClient wc = j.createWebClient(); + Page p = wc.goTo("computer/" + s.getNodeName() + "/config.xml", "application/xml"); + String xml = p.getWebResponse().getContentAsString(); + new XmlSlurper().parseText(xml); // verify that it is XML + + // make sure it survives the roundtrip + post("computer/" + s.getNodeName() + "/config.xml", xml); + + assertNotNull(j.jenkins.getNode(s.getNodeName())); + + xml = IOUtils.toString(getClass().getResource("SlaveTest/slave.xml").openStream()); + xml = xml.replace("NAME", s.getNodeName()); + post("computer/" + s.getNodeName() + "/config.xml", xml); + + s = (DumbSlave) j.jenkins.getNode(s.getNodeName()); + assertNotNull(s); + assertEquals("some text", s.getNodeDescription()); + assertEquals(JNLPLauncher.class, s.getLauncher().getClass()); + } + + private void post(String url, String xml) throws Exception { + HttpURLConnection con = (HttpURLConnection) new URL(j.getURL(), url).openConnection(); + con.setRequestMethod("POST"); + con.setRequestProperty("Content-Type", "application/xml;charset=UTF-8"); + con.setRequestProperty(CrumbIssuer.DEFAULT_CRUMB_NAME, "test"); + con.setDoOutput(true); + con.getOutputStream().write(xml.getBytes("UTF-8")); + con.getOutputStream().close(); + IOUtils.copy(con.getInputStream(), System.out); + } + + @Test + public void remoteFsCheck() throws Exception { + DumbSlave.DescriptorImpl d = j.jenkins.getDescriptorByType(DumbSlave.DescriptorImpl.class); + assertEquals(FormValidation.ok(), d.doCheckRemoteFS("c:\\")); + assertEquals(FormValidation.ok(), d.doCheckRemoteFS("/tmp")); + assertEquals(WARNING, d.doCheckRemoteFS("relative/path").kind); + assertEquals(WARNING, d.doCheckRemoteFS("/net/foo/bar/zot").kind); + assertEquals(WARNING, d.doCheckRemoteFS("\\\\machine\\folder\\foo").kind); + } + @Test @Issue("SECURITY-195") public void shouldNotEscapeJnlpSlavesResources() throws Exception { - Slave slave = rule.createSlave(); - + Slave slave = j.createSlave(); + // Spot-check correct requests assertJnlpJarUrlIsAllowed(slave, "agent.jar"); assertJnlpJarUrlIsAllowed(slave, "slave.jar"); assertJnlpJarUrlIsAllowed(slave, "remoting.jar"); assertJnlpJarUrlIsAllowed(slave, "jenkins-cli.jar"); assertJnlpJarUrlIsAllowed(slave, "hudson-cli.jar"); - + // Check that requests to other WEB-INF contents fail assertJnlpJarUrlFails(slave, "web.xml"); assertJnlpJarUrlFails(slave, "web.xml"); @@ -83,7 +141,7 @@ public class Slave2Test { assertJnlpJarUrlFails(slave, "classes/dependencies.txt"); assertJnlpJarUrlFails(slave, "plugins/ant.hpi"); assertJnlpJarUrlFails(slave, "nonexistentfolder/something.txt"); - + // Try various kinds of folder escaping (SECURITY-195) assertJnlpJarUrlFails(slave, "../"); assertJnlpJarUrlFails(slave, ".."); @@ -93,7 +151,7 @@ public class Slave2Test { assertJnlpJarUrlFails(slave, "foo/../../bar"); assertJnlpJarUrlFails(slave, "./../foo/bar"); } - + private void assertJnlpJarUrlFails(@Nonnull Slave slave, @Nonnull String url) throws Exception { // Raw access to API Slave.JnlpJar jnlpJar = slave.getComputer().getJnlpJars(url); @@ -105,15 +163,15 @@ public class Slave2Test { } fail("Expected the MalformedURLException for " + url); } - + private void assertJnlpJarUrlIsAllowed(@Nonnull Slave slave, @Nonnull String url) throws Exception { // Raw access to API Slave.JnlpJar jnlpJar = slave.getComputer().getJnlpJars(url); assertNotNull(jnlpJar.getURL()); - - + + // Access from a Web client - JenkinsRule.WebClient client = rule.createWebClient(); + JenkinsRule.WebClient client = j.createWebClient(); assertEquals(200, client.getPage(client.getContextPath() + "jnlpJars/" + URLEncoder.encode(url, "UTF-8")).getWebResponse().getStatusCode()); assertEquals(200, client.getPage(jnlpJar.getURL()).getWebResponse().getStatusCode()); } @@ -122,9 +180,9 @@ public class Slave2Test { @Issue("JENKINS-36280") public void launcherFiltering() throws Exception { DumbSlave.DescriptorImpl descriptor = - rule.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class); + j.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class); DescriptorExtensionList> descriptors = - rule.getInstance().getDescriptorList(ComputerLauncher.class); + j.getInstance().getDescriptorList(ComputerLauncher.class); assumeThat("we need at least two launchers to test this", descriptors.size(), not(anyOf(is(0), is(1)))); assertThat(descriptor.computerLauncherDescriptors(null), containsInAnyOrder(descriptors.toArray(new Descriptor[descriptors.size()]))); @@ -140,7 +198,7 @@ public class Slave2Test { @Issue("JENKINS-36280") public void retentionFiltering() throws Exception { DumbSlave.DescriptorImpl descriptor = - rule.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class); + j.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class); DescriptorExtensionList, Descriptor>> descriptors = RetentionStrategy.all(); assumeThat("we need at least two retention strategies to test this", descriptors.size(), not(anyOf(is(0), is(1)))); assertThat(descriptor.retentionStrategyDescriptors(null), containsInAnyOrder(descriptors.toArray(new Descriptor[descriptors.size()]))); @@ -157,7 +215,7 @@ public class Slave2Test { @Issue("JENKINS-36280") public void propertyFiltering() throws Exception { DumbSlave.DescriptorImpl descriptor = - rule.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class); + j.getInstance().getDescriptorByType(DumbSlave.DescriptorImpl.class); DescriptorExtensionList, NodePropertyDescriptor> descriptors = NodeProperty.all(); assumeThat("we need at least two node properties to test this", descriptors.size(), not(anyOf(is(0), is(1)))); assertThat(descriptor.nodePropertyDescriptors(null), containsInAnyOrder(descriptors.toArray(new Descriptor[descriptors.size()]))); @@ -170,8 +228,6 @@ public class Slave2Test { assertThat(descriptor.nodePropertyDescriptors(null), hasItem(victim)); } - - @TestExtension public static class DynamicFilter extends DescriptorVisibilityFilter { @@ -191,4 +247,5 @@ public class Slave2Test { return !descriptors.contains(descriptor); } } + } -- GitLab From ef8568c40ad84ec43ee2b77285eefd7db0cb79b0 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 18:02:08 -0500 Subject: [PATCH 0125/1380] TokenBasedRememberMeServices2Test --- .../TokenBasedRememberMeServices2Test.groovy | 129 ----------------- .../TokenBasedRememberMeServices2Test.java | 130 ++++++++++++++++++ 2 files changed, 130 insertions(+), 129 deletions(-) delete mode 100644 test/src/test/groovy/hudson/security/TokenBasedRememberMeServices2Test.groovy create mode 100644 test/src/test/java/hudson/security/TokenBasedRememberMeServices2Test.java diff --git a/test/src/test/groovy/hudson/security/TokenBasedRememberMeServices2Test.groovy b/test/src/test/groovy/hudson/security/TokenBasedRememberMeServices2Test.groovy deleted file mode 100644 index d0c6ae2880..0000000000 --- a/test/src/test/groovy/hudson/security/TokenBasedRememberMeServices2Test.groovy +++ /dev/null @@ -1,129 +0,0 @@ -package hudson.security - -import jenkins.model.Jenkins -import org.acegisecurity.AuthenticationException -import org.acegisecurity.BadCredentialsException -import org.acegisecurity.GrantedAuthority -import org.acegisecurity.GrantedAuthorityImpl -import org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices -import org.acegisecurity.userdetails.User -import org.acegisecurity.userdetails.UserDetails -import org.acegisecurity.userdetails.UsernameNotFoundException -import org.junit.Before -import com.gargoylesoftware.htmlunit.util.Cookie -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.JenkinsRule -import org.jvnet.hudson.test.LoggerRule -import org.springframework.dao.DataAccessException - -import java.util.logging.Handler -import java.util.logging.LogRecord -import java.util.logging.Logger - -import static java.util.logging.Level.FINEST - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class TokenBasedRememberMeServices2Test { - @Rule - public JenkinsRule j = new JenkinsRule(); - @Rule - public LoggerRule logging = new LoggerRule() - - private static boolean failureInduced; - - @Before - public void resetFailureInduced() {failureInduced = false} - - @Test - public void rememberMeAutoLoginFailure() { - j.jenkins.securityRealm = new InvalidUserWhenLoggingBackInRealm() - - def wc = j.createWebClient() - wc.login("alice","alice",true) - - // we should see a remember me cookie - def c = getRememberMeCookie(wc) - assert c!=null - - // start a new session and attempt to access Jenkins, - // which should cause autoLogin failures - wc = j.createWebClient() - wc.cookieManager.addCookie(c); - - // even if SecurityRealm chokes, it shouldn't kill the page - logging.capture(1000).record(TokenBasedRememberMeServices.class, FINEST) - wc.goTo("") - - // make sure that the server recorded this failure - assert failureInduced - assert logging.messages.find { it.contains("contained username 'alice' but was not found")}!=null - // and the problematic cookie should have been removed - assert getRememberMeCookie(wc)==null - } - - private Cookie getRememberMeCookie(JenkinsRule.WebClient wc) { - wc.cookieManager.getCookie(TokenBasedRememberMeServices2.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY) - } - - private static class InvalidUserWhenLoggingBackInRealm extends AbstractPasswordBasedSecurityRealm { - @Override - protected UserDetails authenticate(String username, String password) throws AuthenticationException { - if (username==password) - return new User(username,password,true,[new GrantedAuthorityImpl("myteam")] as GrantedAuthority[]) - throw new BadCredentialsException(username); - } - - @Override - GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException { - throw new UnsupportedOperationException() - } - - @Override - UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { - failureInduced = true - throw new UsernameNotFoundException("intentionally not working"); - } - } - - - @Test - public void basicFlow() { - j.jenkins.securityRealm = new StupidRealm() - - def wc = j.createWebClient() - wc.login("bob","bob",true) - - // we should see a remember me cookie - def c = getRememberMeCookie(wc) - assert c!=null - - // start a new session and attempt to access Jenkins, - wc = j.createWebClient() - wc.cookieManager.addCookie(c); - - // this will trigger remember me - wc.goTo("") - - // make sure that our security realm failed to report the info correctly - assert failureInduced - // but we should have logged in - wc.executeOnServer { - def a = Jenkins.getAuthentication() - assert a.name=="bob" - assert a.authorities*.authority.join(":")=="authenticated:myteam" - } - } - - private static class StupidRealm extends InvalidUserWhenLoggingBackInRealm { - @Override - UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { - failureInduced = true - throw new UserMayOrMayNotExistException("I cannot tell"); - } - } -} diff --git a/test/src/test/java/hudson/security/TokenBasedRememberMeServices2Test.java b/test/src/test/java/hudson/security/TokenBasedRememberMeServices2Test.java new file mode 100644 index 0000000000..35bea92686 --- /dev/null +++ b/test/src/test/java/hudson/security/TokenBasedRememberMeServices2Test.java @@ -0,0 +1,130 @@ +package hudson.security; + +import com.gargoylesoftware.htmlunit.util.Cookie; +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import static java.util.logging.Level.FINEST; +import java.util.stream.Collectors; +import jenkins.model.Jenkins; +import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationException; +import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.GrantedAuthorityImpl; +import org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices; +import org.acegisecurity.userdetails.User; +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UsernameNotFoundException; +import static org.junit.Assert.*; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.LoggerRule; +import org.springframework.dao.DataAccessException; + +public class TokenBasedRememberMeServices2Test { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Rule + public LoggerRule logging = new LoggerRule(); + + private static boolean failureInduced; + + @Before + public void resetFailureInduced() { + failureInduced = false; + } + + @Test + public void rememberMeAutoLoginFailure() throws Exception { + j.jenkins.setSecurityRealm(new InvalidUserWhenLoggingBackInRealm()); + + JenkinsRule.WebClient wc = j.createWebClient(); + wc.login("alice", "alice", true); + + // we should see a remember me cookie + Cookie c = getRememberMeCookie(wc); + assertNotNull(c); + + // start a new session and attempt to access Jenkins, + // which should cause autoLogin failures + wc = j.createWebClient(); + wc.getCookieManager().addCookie(c); + + // even if SecurityRealm chokes, it shouldn't kill the page + logging.capture(1000).record(TokenBasedRememberMeServices.class, FINEST); + wc.goTo(""); + + // make sure that the server recorded this failure + assertTrue(failureInduced); + assertTrue(logging.getMessages().stream().anyMatch(m -> m.contains("contained username 'alice' but was not found"))); + // and the problematic cookie should have been removed + assertNull(getRememberMeCookie(wc)); + } + + private Cookie getRememberMeCookie(JenkinsRule.WebClient wc) { + return wc.getCookieManager().getCookie(TokenBasedRememberMeServices2.ACEGI_SECURITY_HASHED_REMEMBER_ME_COOKIE_KEY); + } + + private static class InvalidUserWhenLoggingBackInRealm extends AbstractPasswordBasedSecurityRealm { + @Override + protected UserDetails authenticate(String username, String password) throws AuthenticationException { + if (username.equals(password)) { + return new User(username, password, true, new GrantedAuthority[] {new GrantedAuthorityImpl("myteam")}); + } + throw new BadCredentialsException(username); + } + + @Override + public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException { + throw new UnsupportedOperationException(); + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { + failureInduced = true; + throw new UsernameNotFoundException("intentionally not working"); + } + } + + @Test + public void basicFlow() throws Exception { + j.jenkins.setSecurityRealm(new StupidRealm()); + + JenkinsRule.WebClient wc = j.createWebClient(); + wc.login("bob", "bob", true); + + // we should see a remember me cookie + Cookie c = getRememberMeCookie(wc); + assertNotNull(c); + + // start a new session and attempt to access Jenkins, + wc = j.createWebClient(); + wc.getCookieManager().addCookie(c); + + // this will trigger remember me + wc.goTo(""); + + // make sure that our security realm failed to report the info correctly + assertTrue(failureInduced); + // but we should have logged in + wc.executeOnServer(() -> { + Authentication a = Jenkins.getAuthentication(); + assertEquals("bob", a.getName()); + assertEquals(ImmutableList.of("authenticated", "myteam"), Arrays.asList(a.getAuthorities()).stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())); + return null; + }); + } + + private static class StupidRealm extends InvalidUserWhenLoggingBackInRealm { + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException { + failureInduced = true; + throw new UserMayOrMayNotExistException("I cannot tell"); + } + } + +} -- GitLab From d6dfd42a47cba0aea783c4020a62f516d3b6ce9d Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 18:15:28 -0500 Subject: [PATCH 0126/1380] TextFileTest --- .../test/java/hudson/util/TextFileTest.java | 97 +++++++++++++++++ .../src/test/resources/hudson/util/ascii.txt | 0 .../groovy/hudson/util/TextFileTest.groovy | 102 ------------------ 3 files changed, 97 insertions(+), 102 deletions(-) create mode 100644 core/src/test/java/hudson/util/TextFileTest.java rename {test => core}/src/test/resources/hudson/util/ascii.txt (100%) delete mode 100644 test/src/test/groovy/hudson/util/TextFileTest.groovy diff --git a/core/src/test/java/hudson/util/TextFileTest.java b/core/src/test/java/hudson/util/TextFileTest.java new file mode 100644 index 0000000000..cb689a080f --- /dev/null +++ b/core/src/test/java/hudson/util/TextFileTest.java @@ -0,0 +1,97 @@ +package hudson.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang.StringUtils; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class TextFileTest { + + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + @Test + public void head() throws Exception { + File f = tmp.newFile(); + FileUtils.copyURLToFile(getClass().getResource("ascii.txt"), f); + + TextFile t = new TextFile(f); + String first35 = "Lorem ipsum dolor sit amet, consect"; + assertEquals(35, first35.length()); + assertEquals(first35, t.head(35)); + } + + @Test + public void shortHead() throws Exception { + File f = tmp.newFile(); + FileUtils.write(f, "hello"); + + TextFile t = new TextFile(f); + assertEquals("hello", t.head(35)); + } + + @Test + public void tail() throws Exception { + File f = tmp.newFile(); + FileUtils.copyURLToFile(getClass().getResource("ascii.txt"), f); + + TextFile t = new TextFile(f); + String tailStr = "la, vitae interdum quam rutrum id." + System.lineSeparator(); + assertEquals(34 + System.lineSeparator().length(), tailStr.length()); + assertEquals(tailStr, t.fastTail(tailStr.length())); + } + + @Test + public void shortTail() throws Exception { + File f = tmp.newFile(); + FileUtils.write(f, "hello"); + + TextFile t = new TextFile(f); + assertEquals("hello", t.fastTail(35)); + } + + /** + * Shift JIS is a multi-byte character encoding. + * + * In it, 0x82 0x83 is \u30e2, and 0x83 0x82 is \uFF43. So if aren't + * careful, we'll parse the text incorrectly. + */ + @Test + public void tailShiftJIS() throws Exception { + File f = tmp.newFile(); + + TextFile t = new TextFile(f); + + try (OutputStream o = new FileOutputStream(f)) { + for (int i = 0; i < 80; i++) { + for (int j = 0; j < 40; j++) { + o.write(0x83); + o.write(0x82); + } + o.write(0x0A); + } + } + + String tail = t.fastTail(35, Charset.forName("Shift_JIS")); + assertEquals(StringUtils.repeat("\u30e2", 34) + "\n", tail); + assertEquals(35, tail.length()); + + // add one more byte to force fastTail to read from one byte ahead + // between this and the previous case, it should start parsing text incorrectly, until it hits NL + // where it comes back in sync + try (OutputStream o = new FileOutputStream(f, true)) { + o.write(0x0A); + } + + tail = t.fastTail(35, Charset.forName("Shift_JIS")); + assertEquals(StringUtils.repeat("\u30e2", 33) + "\n\n", tail); + assertEquals(35, tail.length()); + } + +} diff --git a/test/src/test/resources/hudson/util/ascii.txt b/core/src/test/resources/hudson/util/ascii.txt similarity index 100% rename from test/src/test/resources/hudson/util/ascii.txt rename to core/src/test/resources/hudson/util/ascii.txt diff --git a/test/src/test/groovy/hudson/util/TextFileTest.groovy b/test/src/test/groovy/hudson/util/TextFileTest.groovy deleted file mode 100644 index 0299132fc8..0000000000 --- a/test/src/test/groovy/hudson/util/TextFileTest.groovy +++ /dev/null @@ -1,102 +0,0 @@ -package hudson.util - -import org.junit.After -import org.junit.Test - -import java.nio.charset.Charset - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class TextFileTest { - List files = []; - - @After - void tearDown() { - files*.delete() - } - - @Test - public void head() { - def f = newFile() - f.text = getClass().getResource("ascii.txt").text - - def t = new TextFile(f) - def first35 = "Lorem ipsum dolor sit amet, consect" - assert t.head(35).equals(first35) - assert first35.length()==35 - } - - @Test - public void shortHead() { - def f = newFile() - f.text = "hello" - - def t = new TextFile(f) - assert t.head(35).equals("hello") - } - - @Test - public void tail() { - def f = newFile() - f.text = getClass().getResource("ascii.txt").text - - def t = new TextFile(f) - def tailStr = "la, vitae interdum quam rutrum id." + System.lineSeparator() - assert t.fastTail(tailStr.length()).equals(tailStr) - assert tailStr.length()==(34 + System.lineSeparator().length()) - } - - @Test - public void shortTail() { - def f = newFile() - f.text = "hello" - - def t = new TextFile(f) - assert t.fastTail(35).equals("hello") - } - - /** - * Shift JIS is a multi-byte character encoding. - * - * In it, 0x82 0x83 is \u30e2, and 0x83 0x82 is \uFF43. - * So if aren't careful, we'll parse the text incorrectly. - */ - @Test - public void tailShiftJIS() { - def f = newFile() - - def t = new TextFile(f) - - f.withOutputStream { o -> - (1..80).each { - (1..40).each { - o.write(0x83) - o.write(0x82) - } - o.write(0x0A); - } - } - - def tail = t.fastTail(35, Charset.forName("Shift_JIS")) - assert tail.equals("\u30e2"*34+"\n") - assert tail.length()==35 - - // add one more byte to force fastTail to read from one byte ahead - // between this and the previous case, it should start parsing text incorrectly, until it hits NL - // where it comes back in sync - f.append([0x0A] as byte[]) - - tail = t.fastTail(35, Charset.forName("Shift_JIS")) - assert tail.equals("\u30e2"*33+"\n\n") - assert tail.length()==35 - } - - def newFile() { - def f = File.createTempFile("foo", "txt") - files.add(f) - return f - } -} -- GitLab From 6a32a20fcd71576f850110182ed0c06d9305b43d Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 18:17:58 -0500 Subject: [PATCH 0127/1380] HistoryWidgetTest --- .../hudson/widgets/HistoryWidgetTest.groovy | 28 ------------------- .../hudson/widgets/HistoryWidgetTest.java | 27 ++++++++++++++++++ 2 files changed, 27 insertions(+), 28 deletions(-) delete mode 100644 test/src/test/groovy/hudson/widgets/HistoryWidgetTest.groovy create mode 100644 test/src/test/java/hudson/widgets/HistoryWidgetTest.java diff --git a/test/src/test/groovy/hudson/widgets/HistoryWidgetTest.groovy b/test/src/test/groovy/hudson/widgets/HistoryWidgetTest.groovy deleted file mode 100644 index 9d32acfa6b..0000000000 --- a/test/src/test/groovy/hudson/widgets/HistoryWidgetTest.groovy +++ /dev/null @@ -1,28 +0,0 @@ -package hudson.widgets - -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.Issue -import org.jvnet.hudson.test.JenkinsRule - -/** - * @author Kohsuke Kawaguchi - */ -class HistoryWidgetTest { - - @Rule - public JenkinsRule j = new JenkinsRule() - - @Test - @Issue("JENKINS-15499") - void moreLink() { - def p = j.createFreeStyleProject() - for (x in 1..3) { - j.assertBuildStatusSuccess(p.scheduleBuild2(0)) - } - - def wc = j.createWebClient() - wc.javaScriptEnabled = false - wc.goTo("job/${p.name}/buildHistory/all") - } -} diff --git a/test/src/test/java/hudson/widgets/HistoryWidgetTest.java b/test/src/test/java/hudson/widgets/HistoryWidgetTest.java new file mode 100644 index 0000000000..fd28e63f19 --- /dev/null +++ b/test/src/test/java/hudson/widgets/HistoryWidgetTest.java @@ -0,0 +1,27 @@ +package hudson.widgets; + +import hudson.model.FreeStyleProject; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +public class HistoryWidgetTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + @Issue("JENKINS-15499") + public void moreLink() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + for (int x = 0; x < 3; x++) { + j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + } + + JenkinsRule.WebClient wc = j.createWebClient(); + wc.setJavaScriptEnabled(false); + wc.goTo("job/" + p.getName() + "/buildHistory/all"); + } + +} -- GitLab From 7bcb30cb8e85af0e66a7f632cbb3a1314caf09d4 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 18:31:02 -0500 Subject: [PATCH 0128/1380] Jenkins19124Test --- .../jenkins/bugs/Jenkins19124Test.groovy | 89 ------------------ .../java/jenkins/bugs/Jenkins19124Test.java | 94 +++++++++++++++++++ 2 files changed, 94 insertions(+), 89 deletions(-) delete mode 100644 test/src/test/groovy/jenkins/bugs/Jenkins19124Test.groovy create mode 100644 test/src/test/java/jenkins/bugs/Jenkins19124Test.java diff --git a/test/src/test/groovy/jenkins/bugs/Jenkins19124Test.groovy b/test/src/test/groovy/jenkins/bugs/Jenkins19124Test.groovy deleted file mode 100644 index 352b3144f0..0000000000 --- a/test/src/test/groovy/jenkins/bugs/Jenkins19124Test.groovy +++ /dev/null @@ -1,89 +0,0 @@ -package jenkins.bugs - -import com.gargoylesoftware.htmlunit.WebClientUtil -import hudson.Launcher -import hudson.model.AbstractBuild -import hudson.model.AbstractProject -import hudson.model.BuildListener -import hudson.tasks.BuildStepDescriptor -import hudson.tasks.Builder -import hudson.util.FormValidation -import hudson.util.ListBoxModel -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.Issue -import org.jvnet.hudson.test.JenkinsRule -import org.jvnet.hudson.test.TestExtension -import org.kohsuke.stapler.QueryParameter - -import javax.inject.Inject - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class Jenkins19124Test { - @Rule - public JenkinsRule j = new JenkinsRule(); - - @Inject - DescriptorImpl d; - - @Issue("JENKINS-19124") - @Test - public void interrelatedFormValidation() { - j.jenkins.injector.injectMembers(this); - - def p = j.createFreeStyleProject(); - p.buildersList.add(new Foo()); - - def wc = j.createWebClient(); - def c = wc.getPage(p, "configure"); - c.getElementByName("_.alpha").valueAttribute = "hello"; - WebClientUtil.waitForJSExec(wc); - assert d.alpha=="hello"; - assert d.bravo=="2"; - - c.getElementByName("_.bravo").setSelectedAttribute("1",true); - WebClientUtil.waitForJSExec(wc); - assert d.alpha=="hello"; - assert d.bravo=="1"; - } - - public static class Foo extends Builder { - String getAlpha() { return "alpha"; } - String getBravo() { return "2"; } - - @Override - boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { - return true; - } - - private Object writeReplace() { return new Object(); } - } - - @TestExtension - public static class DescriptorImpl extends BuildStepDescriptor { - String alpha,bravo; - - public DescriptorImpl() { - super(Foo.class) - } - - FormValidation doCheckAlpha(@QueryParameter String value, @QueryParameter String bravo) { - this.alpha = value; - this.bravo = bravo; - return FormValidation.ok(); - } - - ListBoxModel doFillBravoItems() { - return new ListBoxModel().add("1").add("2").add("3"); - } - - @Override - boolean isApplicable(Class jobType) { - return true; - } - } -} diff --git a/test/src/test/java/jenkins/bugs/Jenkins19124Test.java b/test/src/test/java/jenkins/bugs/Jenkins19124Test.java new file mode 100644 index 0000000000..4f976f85ba --- /dev/null +++ b/test/src/test/java/jenkins/bugs/Jenkins19124Test.java @@ -0,0 +1,94 @@ +package jenkins.bugs; + +import com.gargoylesoftware.htmlunit.WebClientUtil; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlSelect; +import com.gargoylesoftware.htmlunit.html.HtmlTextInput; +import hudson.Launcher; +import hudson.model.AbstractBuild; +import hudson.model.AbstractProject; +import hudson.model.BuildListener; +import hudson.model.FreeStyleProject; +import hudson.tasks.BuildStepDescriptor; +import hudson.tasks.Builder; +import hudson.util.FormValidation; +import hudson.util.ListBoxModel; +import java.io.IOException; +import javax.inject.Inject; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.QueryParameter; + +public class Jenkins19124Test { + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Inject + public Foo.DescriptorImpl d; + + @Issue("JENKINS-19124") + @Test + public void interrelatedFormValidation() throws Exception { + j.jenkins.getInjector().injectMembers(this); + + FreeStyleProject p = j.createFreeStyleProject(); + p.getBuildersList().add(new Foo()); + + JenkinsRule.WebClient wc = j.createWebClient(); + HtmlPage c = wc.getPage(p, "configure"); + HtmlTextInput alpha = c.getElementByName("_.alpha"); + alpha.setValueAttribute("hello"); + WebClientUtil.waitForJSExec(wc); + assertEquals("hello", d.alpha); + assertEquals("2", d.bravo); + + HtmlSelect bravo = c.getElementByName("_.bravo"); + bravo.setSelectedAttribute("1", true); + WebClientUtil.waitForJSExec(wc); + assertEquals("hello", d.alpha); + assertEquals("1", d.bravo); + } + + public static class Foo extends Builder { + + public String getAlpha() { + return "alpha"; + } + public String getBravo() { + return "2"; + } + + @Override + public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { + return true; + } + + @TestExtension + public static class DescriptorImpl extends BuildStepDescriptor { + + String alpha, bravo; + + public FormValidation doCheckAlpha(@QueryParameter String value, @QueryParameter String bravo) { + this.alpha = value; + this.bravo = bravo; + return FormValidation.ok(); + } + + public ListBoxModel doFillBravoItems() { + return new ListBoxModel().add("1").add("2").add("3"); + } + + @Override + public boolean isApplicable(Class jobType) { + return true; + } + + } + + } + +} -- GitLab From fcedf647d7c439e08f7ffb975f8af6d822034c6c Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 18:37:28 -0500 Subject: [PATCH 0129/1380] PeepholePermalinkTest --- .../model/PeepholePermalinkTest.groovy | 100 ------------------ .../jenkins/model/PeepholePermalinkTest.java | 99 +++++++++++++++++ 2 files changed, 99 insertions(+), 100 deletions(-) delete mode 100644 test/src/test/groovy/jenkins/model/PeepholePermalinkTest.groovy create mode 100644 test/src/test/java/jenkins/model/PeepholePermalinkTest.java diff --git a/test/src/test/groovy/jenkins/model/PeepholePermalinkTest.groovy b/test/src/test/groovy/jenkins/model/PeepholePermalinkTest.groovy deleted file mode 100644 index a500677a9f..0000000000 --- a/test/src/test/groovy/jenkins/model/PeepholePermalinkTest.groovy +++ /dev/null @@ -1,100 +0,0 @@ -package jenkins.model - -import static org.junit.Assert.assertTrue - -import org.junit.Assume; - -import hudson.Functions -import hudson.Util -import hudson.model.Run -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.FailureBuilder -import org.jvnet.hudson.test.Issue -import org.jvnet.hudson.test.JenkinsRule - -/** - * @author Kohsuke Kawaguchi - */ -class PeepholePermalinkTest { - - @Rule - public JenkinsRule j = new JenkinsRule() - - /** - * Basic operation of the permalink generation. - */ - @Test - void basics() { - Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows()); - - def p = j.createFreeStyleProject() - def b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0)) - - def lsb = new File(p.buildDir, "lastSuccessfulBuild") - def lfb = new File(p.buildDir, "lastFailedBuild") - - assertLink(lsb,b1) - - // now another build that fails - p.buildersList.add(new FailureBuilder()) - def b2 = p.scheduleBuild2(0).get() - - assertLink(lsb,b1) - assertLink(lfb,b2) - - // one more build and this time it succeeds - p.buildersList.clear() - def b3 = j.assertBuildStatusSuccess(p.scheduleBuild2(0)) - - assertLink(lsb,b3) - assertLink(lfb,b2) - - // delete b3 and symlinks should update properly - b3.delete() - assertLink(lsb,b1) - assertLink(lfb,b2) - - b1.delete() - assertLink(lsb,null) - assertLink(lfb,b2) - - b2.delete() - assertLink(lsb,null) - assertLink(lfb,null) - } - - def assertLink(File symlink, Run build) { - assert Util.resolveSymlink(symlink)==(build==null ? "-1" : build.number as String); - } - - /** - * job/JOBNAME/lastStable and job/JOBNAME/lastSuccessful symlinks that we used to generate should still work - */ - @Test - void legacyCompatibility() { - Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows()); - - def p = j.createFreeStyleProject() - def b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0)) - - ["lastStable","lastSuccessful"].each { n -> - // test if they both point to b1 - assert new File(p.rootDir,"$n/build.xml").length() == new File(b1.rootDir,"build.xml").length() - } - } - - @Test - @Issue("JENKINS-19034") - void rebuildBuildNumberPermalinks() { - def p = j.createFreeStyleProject() - def b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)) - File f = new File(p.getBuildDir(), "1") - // assertTrue(Util.isSymlink(f)) - f.delete() - PeepholePermalink link = p.getPermalinks().find({l -> l instanceof PeepholePermalink}) - println(link) - link.updateCache(p, b) - assertTrue("build symlink hasn't been restored", f.exists()) - } -} diff --git a/test/src/test/java/jenkins/model/PeepholePermalinkTest.java b/test/src/test/java/jenkins/model/PeepholePermalinkTest.java new file mode 100644 index 0000000000..c25e276b4e --- /dev/null +++ b/test/src/test/java/jenkins/model/PeepholePermalinkTest.java @@ -0,0 +1,99 @@ +package jenkins.model; + +import hudson.Functions; +import hudson.Util; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import hudson.model.Run; +import java.io.File; +import static org.junit.Assert.*; +import org.junit.Assume; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.FailureBuilder; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +public class PeepholePermalinkTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + /** + * Basic operation of the permalink generation. + */ + @Test + public void basics() throws Exception { + Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows()); + + FreeStyleProject p = j.createFreeStyleProject(); + FreeStyleBuild b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + + File lsb = new File(p.getBuildDir(), "lastSuccessfulBuild"); + File lfb = new File(p.getBuildDir(), "lastFailedBuild"); + + assertLink(lsb, b1); + + // now another build that fails + p.getBuildersList().add(new FailureBuilder()); + FreeStyleBuild b2 = p.scheduleBuild2(0).get(); + + assertLink(lsb, b1); + assertLink(lfb, b2); + + // one more build and this time it succeeds + p.getBuildersList().clear(); + FreeStyleBuild b3 = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + + assertLink(lsb, b3); + assertLink(lfb, b2); + + // delete b3 and symlinks should update properly + b3.delete(); + assertLink(lsb, b1); + assertLink(lfb, b2); + + b1.delete(); + assertLink(lsb, null); + assertLink(lfb, b2); + + b2.delete(); + assertLink(lsb, null); + assertLink(lfb, null); + } + + private void assertLink(File symlink, Run build) throws Exception { + assertEquals(build == null ? "-1" : Integer.toString(build.getNumber()), Util.resolveSymlink(symlink)); + } + + /** + * job/JOBNAME/lastStable and job/JOBNAME/lastSuccessful symlinks that we + * used to generate should still work + */ + @Test + public void legacyCompatibility() throws Exception { + Assume.assumeFalse("can't run on windows because we rely on symlinks", Functions.isWindows()); + + FreeStyleProject p = j.createFreeStyleProject(); + FreeStyleBuild b1 = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + + for (String n : new String[] {"lastStable", "lastSuccessful"}) { + // test if they both point to b1 + assertEquals(new File(p.getRootDir(), n + "/build.xml").length(), new File(b1.getRootDir(), "build.xml").length()); + } + } + + @Test + @Issue("JENKINS-19034") + public void rebuildBuildNumberPermalinks() throws Exception { + FreeStyleProject p = j.createFreeStyleProject(); + FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + File f = new File(p.getBuildDir(), "1"); + // assertTrue(Util.isSymlink(f)) + f.delete(); + PeepholePermalink link = (PeepholePermalink) p.getPermalinks().stream().filter(l -> l instanceof PeepholePermalink).findAny().get(); + link.updateCache(p, b); + assertTrue("build symlink hasn't been restored", f.exists()); + } + +} -- GitLab From 26e2001cf2d92cd54425c89e1e400990e61a7f32 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 18:40:13 -0500 Subject: [PATCH 0130/1380] ApplyButtonTest --- .../groovy/lib/form/ApplyButtonTest.groovy | 37 ---------------- .../test/java/lib/form/ApplyButtonTest.java | 42 +++++++++++++++++++ 2 files changed, 42 insertions(+), 37 deletions(-) delete mode 100644 test/src/test/groovy/lib/form/ApplyButtonTest.groovy create mode 100644 test/src/test/java/lib/form/ApplyButtonTest.java diff --git a/test/src/test/groovy/lib/form/ApplyButtonTest.groovy b/test/src/test/groovy/lib/form/ApplyButtonTest.groovy deleted file mode 100644 index ceae5ed940..0000000000 --- a/test/src/test/groovy/lib/form/ApplyButtonTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package lib.form - -import hudson.markup.RawHtmlMarkupFormatter -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.Issue -import org.jvnet.hudson.test.JenkinsRule - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class ApplyButtonTest { - @Rule public JenkinsRule j = new JenkinsRule(); - - /** - * Editing code mirror should still gets reflected when you click apply. - */ - @Test @Issue("JENKINS-18436") - public void editDescription() { - j.jenkins.markupFormatter = RawHtmlMarkupFormatter.INSTANCE // need something using CodeMirror - def p = j.createFreeStyleProject() - def b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)) - - def config = j.createWebClient().getPage(b, "configure") - def form = config.getFormByName("config") - // HtmlUnit doesn't have JSON, so we need to emulate one - config.executeJavaScript(getClass().getResource("JSON.js").text) - // it's hard to emulate the keytyping, so we just set the value into codemirror and test if this gets - // reflected back into TEXTAREA - config.executeJavaScript("document.getElementsByTagName('TEXTAREA')[0].codemirrorObject.setLine(0,'foobar')") - j.getButtonByCaption(form,"Apply").click() - - assert "foobar"==b.description - } -} diff --git a/test/src/test/java/lib/form/ApplyButtonTest.java b/test/src/test/java/lib/form/ApplyButtonTest.java new file mode 100644 index 0000000000..028e135da0 --- /dev/null +++ b/test/src/test/java/lib/form/ApplyButtonTest.java @@ -0,0 +1,42 @@ +package lib.form; + +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.markup.RawHtmlMarkupFormatter; +import hudson.model.FreeStyleBuild; +import hudson.model.FreeStyleProject; +import org.apache.commons.io.IOUtils; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +public class ApplyButtonTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + /** + * Editing code mirror should still gets reflected when you click apply. + */ + @Test + @Issue("JENKINS-18436") + public void editDescription() throws Exception { + j.jenkins.setMarkupFormatter(RawHtmlMarkupFormatter.INSTANCE); // need something using CodeMirror + FreeStyleProject p = j.createFreeStyleProject(); + FreeStyleBuild b = j.assertBuildStatusSuccess(p.scheduleBuild2(0)); + + HtmlPage config = j.createWebClient().getPage(b, "configure"); + HtmlForm form = config.getFormByName("config"); + // HtmlUnit doesn't have JSON, so we need to emulate one + config.executeJavaScript(IOUtils.toString(ApplyButtonTest.class.getResource("JSON.js"))); + // it's hard to emulate the keytyping, so we just set the value into codemirror and test if this gets + // reflected back into TEXTAREA + config.executeJavaScript("document.getElementsByTagName('TEXTAREA')[0].codemirrorObject.setLine(0,'foobar')"); + j.getButtonByCaption(form, "Apply").click(); + + assertEquals("foobar", b.getDescription()); + } + +} -- GitLab From 0bc7b46055e7e6d0201b902c440b12afc293045f Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 8 Dec 2017 18:49:22 -0500 Subject: [PATCH 0131/1380] Last trace of gmaven gone! --- test/pom.xml | 49 ------- .../test/groovy/lib/form/TextAreaTest.groovy | 128 ----------------- .../jenkins/bugs/Jenkins41511Test.java | 0 test/src/test/java/lib/form/TextAreaTest.java | 129 ++++++++++++++++++ 4 files changed, 129 insertions(+), 177 deletions(-) delete mode 100644 test/src/test/groovy/lib/form/TextAreaTest.groovy rename test/src/test/{groovy => java}/jenkins/bugs/Jenkins41511Test.java (100%) create mode 100644 test/src/test/java/lib/form/TextAreaTest.java diff --git a/test/pom.xml b/test/pom.xml index 6d70dd8805..22df2c180e 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -241,55 +241,6 @@ THE SOFTWARE. ${concurrency} - - org.codehaus.gmaven - gmaven-plugin - 1.5-jenkins-3 - - - default - - - generateTestStubs - testCompile - - - - - - org.apache.ant - ant - 1.8.0 - - - - org.apache.ant - ant-launcher - 1.8.0 - - - org.apache.ant - ant-junit - 1.7.0 - - - - javax.servlet - javax.servlet-api - 3.1.0 - - - org.codehaus.gmaven.runtime - gmaven-runtime-2.0 - 1.5-jenkins-3 - - - - - 2.0 - - maven-deploy-plugin diff --git a/test/src/test/groovy/lib/form/TextAreaTest.groovy b/test/src/test/groovy/lib/form/TextAreaTest.groovy deleted file mode 100644 index f5edc5cac3..0000000000 --- a/test/src/test/groovy/lib/form/TextAreaTest.groovy +++ /dev/null @@ -1,128 +0,0 @@ -package lib.form - -import hudson.model.AbstractProject -import hudson.tasks.BuildStepDescriptor -import hudson.tasks.Builder -import hudson.util.FormValidation -import org.junit.Rule -import org.junit.Test -import org.jvnet.hudson.test.Issue -import org.jvnet.hudson.test.JenkinsRule -import org.jvnet.hudson.test.TestExtension -import org.kohsuke.stapler.DataBoundConstructor -import org.kohsuke.stapler.QueryParameter - -import javax.inject.Inject - -import static org.junit.Assert.* - -/** - * - * - * @author Kohsuke Kawaguchi - */ -class TextAreaTest { - @Rule - public JenkinsRule j = new JenkinsRule(); - - @Inject - TestBuilder.DescriptorImpl d; - - @Test @Issue("JENKINS-19457") - public void validation() { - j.jenkins.injector.injectMembers(this) - def p = j.createFreeStyleProject() - p.buildersList.add(new TestBuilder()) - j.configRoundtrip(p) - assert d.text1=="This is text1" - assert d.text2=="Received This is text1" - } - - public static class TestBuilder extends Builder { - @DataBoundConstructor - TestBuilder() { - } - - public String getText1() { return "This is text1" } - public String getText2() { return "This is text2" } - - @TestExtension - public static class DescriptorImpl extends BuildStepDescriptor { - def text1,text2; - - @Override - boolean isApplicable(Class jobType) { - return true; - } - - FormValidation doCheckText1(@QueryParameter String value) { - this.text1 = value; - return FormValidation.ok(); - } - - FormValidation doCheckText2(@QueryParameter String text1) { - this.text2 = "Received "+text1; - return FormValidation.ok(); - } - } - - } - - @Issue("JENKINS-27505") - @Test - public void testText() { - T1:{ - def TEXT_TO_TEST = "some\nvalue\n"; - def p = j.createFreeStyleProject(); - def target = new TextareaTestBuilder(TEXT_TO_TEST); - p.buildersList.add(target); - j.configRoundtrip(p); - j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class)); - } - - // test for a textarea beginning with a empty line. - T2:{ - def TEXT_TO_TEST = "\nbegin\n\nwith\nempty\nline\n\n"; - def p = j.createFreeStyleProject(); - def target = new TextareaTestBuilder(TEXT_TO_TEST); - p.buildersList.add(target); - j.configRoundtrip(p); - j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class)); - } - - // test for a textarea beginning with two empty lines. - T3:{ - def TEXT_TO_TEST = "\n\nbegin\n\nwith\ntwo\nempty\nline\n\n"; - def p = j.createFreeStyleProject(); - def target = new TextareaTestBuilder(TEXT_TO_TEST); - p.buildersList.add(target); - j.configRoundtrip(p); - j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class)); - } - } - - public static class TextareaTestBuilder extends Builder { - private text; - - @DataBoundConstructor - TextareaTestBuilder(String text) { - this.text = text; - } - - public String getText() { return text; } - - @TestExtension - public static class DescriptorImpl extends BuildStepDescriptor { - @Override - boolean isApplicable(Class jobType) { - return true; - } - - @Override - String getDisplayName() { - return this.class.name; - } - } - - } -} diff --git a/test/src/test/groovy/jenkins/bugs/Jenkins41511Test.java b/test/src/test/java/jenkins/bugs/Jenkins41511Test.java similarity index 100% rename from test/src/test/groovy/jenkins/bugs/Jenkins41511Test.java rename to test/src/test/java/jenkins/bugs/Jenkins41511Test.java diff --git a/test/src/test/java/lib/form/TextAreaTest.java b/test/src/test/java/lib/form/TextAreaTest.java new file mode 100644 index 0000000000..9324554af0 --- /dev/null +++ b/test/src/test/java/lib/form/TextAreaTest.java @@ -0,0 +1,129 @@ +package lib.form; + +import hudson.model.AbstractProject; +import hudson.model.FreeStyleProject; +import hudson.tasks.BuildStepDescriptor; +import hudson.tasks.Builder; +import hudson.util.FormValidation; +import javax.inject.Inject; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; + +public class TextAreaTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Inject + public TestBuilder.DescriptorImpl d; + + @Test + @Issue("JENKINS-19457") + public void validation() throws Exception { + j.jenkins.getInjector().injectMembers(this); + FreeStyleProject p = j.createFreeStyleProject(); + p.getBuildersList().add(new TestBuilder()); + j.configRoundtrip(p); + assertEquals("This is text1", d.text1); + assertEquals("Received This is text1", d.text2); + } + + public static class TestBuilder extends Builder { + + @DataBoundConstructor + public TestBuilder() {} + + public String getText1() { + return "This is text1"; + } + public String getText2() { + return "This is text2"; + } + + @TestExtension + public static class DescriptorImpl extends BuildStepDescriptor { + + String text1, text2; + + @Override + public boolean isApplicable(Class jobType) { + return true; + } + + public FormValidation doCheckText1(@QueryParameter String value) { + this.text1 = value; + return FormValidation.ok(); + } + + public FormValidation doCheckText2(@QueryParameter String text1) { + this.text2 = "Received " + text1; + return FormValidation.ok(); + } + } + + } + + @Issue("JENKINS-27505") + @Test + public void text() throws Exception { + T1: { + String TEXT_TO_TEST = "some\nvalue\n"; + FreeStyleProject p = j.createFreeStyleProject(); + TextareaTestBuilder target = new TextareaTestBuilder(TEXT_TO_TEST); + p.getBuildersList().add(target); + j.configRoundtrip(p); + j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class)); + } + + // test for a textarea beginning with a empty line. + T2: { + String TEXT_TO_TEST = "\nbegin\n\nwith\nempty\nline\n\n"; + FreeStyleProject p = j.createFreeStyleProject(); + TextareaTestBuilder target = new TextareaTestBuilder(TEXT_TO_TEST); + p.getBuildersList().add(target); + j.configRoundtrip(p); + j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class)); + } + + // test for a textarea beginning with two empty lines. + T3: { + String TEXT_TO_TEST = "\n\nbegin\n\nwith\ntwo\nempty\nline\n\n"; + FreeStyleProject p = j.createFreeStyleProject(); + TextareaTestBuilder target = new TextareaTestBuilder(TEXT_TO_TEST); + p.getBuildersList().add(target); + j.configRoundtrip(p); + j.assertEqualDataBoundBeans(target, p.getBuildersList().get(TextareaTestBuilder.class)); + } + } + + public static class TextareaTestBuilder extends Builder { + + private String text; + + @DataBoundConstructor + public TextareaTestBuilder(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + @TestExtension + public static class DescriptorImpl extends BuildStepDescriptor { + @Override + public boolean isApplicable(Class jobType) { + return true; + } + + } + + } + +} -- GitLab From e20b0496149669f3a0f05cabd1a06eb3a469e935 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Sun, 10 Dec 2017 02:42:20 +0100 Subject: [PATCH 0132/1380] [JENKINS-34254] Fix RequirePOST form --- core/pom.xml | 2 +- .../hudson/security/csrf/CrumbFilter.java | 8 ++++ .../security/csrf/CrumbFilter/retry.jelly | 42 +++++++++++++++++++ .../csrf/CrumbFilter/retry.properties | 4 ++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly create mode 100644 core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties diff --git a/core/pom.xml b/core/pom.xml index a4b053eee5..2a06499268 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -39,7 +39,7 @@ THE SOFTWARE. true - 1.253 + 1.254-SNAPSHOT 2.5.6.SEC03 2.4.11 diff --git a/core/src/main/java/hudson/security/csrf/CrumbFilter.java b/core/src/main/java/hudson/security/csrf/CrumbFilter.java index e2b2790ef0..bcaca93132 100644 --- a/core/src/main/java/hudson/security/csrf/CrumbFilter.java +++ b/core/src/main/java/hudson/security/csrf/CrumbFilter.java @@ -8,6 +8,8 @@ package hudson.security.csrf; import hudson.util.MultipartFormDataParser; import jenkins.model.Jenkins; import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; +import org.kohsuke.stapler.ForwardToView; +import org.kohsuke.stapler.interceptor.RequirePOST; import java.io.IOException; import java.util.Enumeration; @@ -42,6 +44,12 @@ public class CrumbFilter implements Filter { } public void init(FilterConfig filterConfig) throws ServletException { + RequirePOST.Processor.registerErrorHandler(new RequirePOST.ErrorHandler() { + @Override + public ForwardToView getForwardView() { + return new ForwardToView(CrumbFilter.this, "retry"); + } + }); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { diff --git a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly new file mode 100644 index 0000000000..bcd3e5a61c --- /dev/null +++ b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly @@ -0,0 +1,42 @@ + + + + + + + + +

${%This URL requires POST}

+

+ ${%blurb} +

+

${requestURL}

+

${%warning}

+ + + + + + diff --git a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties new file mode 100644 index 0000000000..0e47fdc8d5 --- /dev/null +++ b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties @@ -0,0 +1,4 @@ +blurb = The URL you're trying to access requires that requests be sent using POST (like a form submission). \ + The button below allows you to retry accessing this URL using POST. \ + URL being accessed: +warning = If you were sent here from an untrusted source, please proceed with caution. \ No newline at end of file -- GitLab From 30ab4481f286a5c33499489dfcb9b3df6587ff38 Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Sun, 10 Dec 2017 05:27:36 +0100 Subject: [PATCH 0133/1380] [FIXES JENKINS-47439] Setup wizard does not resume after restart (#3166) * [JENKINS-47439] Add a failing test On first startup, the setup wizard goes into state NEW and the filter to force display the setup wizard is installed. On second startup, the setup wizard goes into state RESTART (which assumes the setup wizard is done), and the setup wizard is skipped completely. This test expects that state NEW is retained upon restart when nothing is done. * [JENKINS-47439] Persist InstallState In some cases, the heuristics to determine the current setup wizard state are fragile. It is safer to persist the install state so that upon restart, the setup wizard can resume where it was left off. * Missing javadoc and since for new public methods * s/XXX/FIXME/ * Missed that one * Setup wizard filter should be removed when entering a state where setup is complete * Use parameterized logging * Improvements over previous impl * Removed static isUsingSecurityToken. Now only determined from install state. * Call onInstallStateUpdate before InstallState#initializeState as the latter can update state. * Triggering a new build --- .../java/hudson/util/PluginServletFilter.java | 19 +++++ .../java/jenkins/install/InstallState.java | 13 ++- .../java/jenkins/install/InstallUtil.java | 1 - .../java/jenkins/install/SetupWizard.java | 81 ++++++++++++++----- core/src/main/java/jenkins/model/Jenkins.java | 9 ++- .../install/SetupWizardRestartTest.java | 50 ++++++++++++ 6 files changed, 143 insertions(+), 30 deletions(-) create mode 100644 test/src/test/java/jenkins/install/SetupWizardRestartTest.java diff --git a/core/src/main/java/hudson/util/PluginServletFilter.java b/core/src/main/java/hudson/util/PluginServletFilter.java index 9b311aa790..aa2c53973e 100644 --- a/core/src/main/java/hudson/util/PluginServletFilter.java +++ b/core/src/main/java/hudson/util/PluginServletFilter.java @@ -113,6 +113,25 @@ public class PluginServletFilter implements Filter, ExtensionPoint { } } + /** + * Checks whether the given filter is already registered in the chain. + * @param filter the filter to check. + * @return true if the filter is already registered in the chain. + * @since FIXME + */ + public static boolean hasFilter(Filter filter) { + Jenkins j = Jenkins.getInstanceOrNull(); + PluginServletFilter container = null; + if(j != null) { + container = getInstance(j.servletContext); + } + if (j == null || container == null) { + return LEGACY.contains(filter); + } else { + return container.list.contains(filter); + } + } + public static void removeFilter(Filter filter) throws ServletException { Jenkins j = Jenkins.getInstanceOrNull(); if (j==null || getInstance(j.servletContext) == null) { diff --git a/core/src/main/java/jenkins/install/InstallState.java b/core/src/main/java/jenkins/install/InstallState.java index 71790d3c54..939c45f828 100644 --- a/core/src/main/java/jenkins/install/InstallState.java +++ b/core/src/main/java/jenkins/install/InstallState.java @@ -49,7 +49,12 @@ public class InstallState implements ExtensionPoint { * Need InstallState != NEW for tests by default */ @Extension - public static final InstallState UNKNOWN = new InstallState("UNKNOWN", true); + public static final InstallState UNKNOWN = new InstallState("UNKNOWN", true) { + @Override + public void initializeState() { + InstallUtil.proceedToNextStateFrom(this); + } + }; /** * After any setup / restart / etc. hooks are done, states should be running @@ -94,7 +99,7 @@ public class InstallState implements ExtensionPoint { */ @Extension public static final InstallState INITIAL_PLUGINS_INSTALLING = new InstallState("INITIAL_PLUGINS_INSTALLING", false); - + /** * Security setup for a new Jenkins install. */ @@ -106,7 +111,7 @@ public class InstallState implements ExtensionPoint { } catch (Exception e) { throw new RuntimeException(e); } - + InstallUtil.proceedToNextStateFrom(INITIAL_SECURITY_SETUP); } }; @@ -116,7 +121,7 @@ public class InstallState implements ExtensionPoint { */ @Extension public static final InstallState NEW = new InstallState("NEW", false); - + /** * Restart of an existing Jenkins install. */ diff --git a/core/src/main/java/jenkins/install/InstallUtil.java b/core/src/main/java/jenkins/install/InstallUtil.java index e4868e95c1..20d9a68c76 100644 --- a/core/src/main/java/jenkins/install/InstallUtil.java +++ b/core/src/main/java/jenkins/install/InstallUtil.java @@ -92,7 +92,6 @@ public class InstallUtil { */ public static void proceedToNextStateFrom(InstallState prior) { InstallState next = getNextInstallState(prior); - if (Main.isDevelopmentMode) LOGGER.info("Install state transitioning from: " + prior + " to: " + next); if (next != null) { Jenkins.getInstance().setInstallState(next); } diff --git a/core/src/main/java/jenkins/install/SetupWizard.java b/core/src/main/java/jenkins/install/SetupWizard.java index 0f04f903db..f15b468eed 100644 --- a/core/src/main/java/jenkins/install/SetupWizard.java +++ b/core/src/main/java/jenkins/install/SetupWizard.java @@ -64,7 +64,6 @@ import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.jenkinsci.remoting.engine.JnlpProtocol4Handler; import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.stapler.interceptor.RequirePOST; @@ -77,6 +76,10 @@ import org.kohsuke.stapler.interceptor.RequirePOST; @Restricted(NoExternalUse.class) @Extension public class SetupWizard extends PageDecorator { + public SetupWizard() { + checkFilter(); + } + /** * The security token parameter name */ @@ -84,11 +87,6 @@ public class SetupWizard extends PageDecorator { private static final Logger LOGGER = Logger.getLogger(SetupWizard.class.getName()); - /** - * Used to determine if this was a new install (vs. an upgrade, restart, or otherwise) - */ - private static boolean isUsingSecurityToken = false; - /** * Initialize the setup wizard, this will process any current state initializations */ @@ -168,17 +166,8 @@ public class SetupWizard extends PageDecorator { + "*************************************************************" + ls + "*************************************************************" + ls); } - - try { - PluginServletFilter.addFilter(FORCE_SETUP_WIZARD_FILTER); - // if we're not using security defaults, we should not show the security token screen - // users will likely be sent to a login screen instead - isUsingSecurityToken = isUsingSecurityDefaults(); - } catch (ServletException e) { - throw new RuntimeException("Unable to add PluginServletFilter for the SetupWizard", e); - } } - + try { // Make sure plugin metadata is up to date UpdateCenter.updateDefaultSite(); @@ -186,14 +175,34 @@ public class SetupWizard extends PageDecorator { LOGGER.log(Level.WARNING, e.getMessage(), e); } } - + + private void setUpFilter() { + try { + if (!PluginServletFilter.hasFilter(FORCE_SETUP_WIZARD_FILTER)) { + PluginServletFilter.addFilter(FORCE_SETUP_WIZARD_FILTER); + } + } catch (ServletException e) { + throw new RuntimeException("Unable to add PluginServletFilter for the SetupWizard", e); + } + } + + private void tearDownFilter() { + try { + if (PluginServletFilter.hasFilter(FORCE_SETUP_WIZARD_FILTER)) { + PluginServletFilter.removeFilter(FORCE_SETUP_WIZARD_FILTER); + } + } catch (ServletException e) { + throw new RuntimeException("Unable to remove PluginServletFilter for the SetupWizard", e); + } + } + /** * Indicates a generated password should be used - e.g. this is a new install, no security realm set up */ + @SuppressWarnings("unused") // used by jelly public boolean isUsingSecurityToken() { try { - return isUsingSecurityToken // only ever show the unlock page if using the security token - && !Jenkins.getInstance().getInstallState().isSetupComplete() + return !Jenkins.getInstance().getInstallState().isSetupComplete() && isUsingSecurityDefaults(); } catch (Exception e) { // ignore @@ -487,8 +496,6 @@ public class SetupWizard extends PageDecorator { Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER); InstallUtil.saveLastExecVersion(); setCurrentLevel(Jenkins.getVersion()); - PluginServletFilter.removeFilter(FORCE_SETUP_WIZARD_FILTER); - isUsingSecurityToken = false; // this should not be considered new anymore InstallUtil.proceedToNextStateFrom(InstallState.INITIAL_SETUP_COMPLETED); } @@ -508,7 +515,28 @@ public class SetupWizard extends PageDecorator { } return InstallState.valueOf(name); } - + + /** + * Called upon install state update. + * @param state the new install state. + * @since FIXME + */ + public void onInstallStateUpdate(InstallState state) { + if (state.isSetupComplete()) { + tearDownFilter(); + } else { + setUpFilter(); + } + } + + /** + * Returns whether the setup wizard filter is currently registered. + * @since FIXME + */ + public boolean hasSetupWizardFilter() { + return PluginServletFilter.hasFilter(FORCE_SETUP_WIZARD_FILTER); + } + /** * This filter will validate that the security token is provided */ @@ -544,4 +572,13 @@ public class SetupWizard extends PageDecorator { public void destroy() { } }; + + /** + * Sets up the Setup Wizard filter if the current state requires it. + */ + private void checkFilter() { + if (!Jenkins.getInstance().getInstallState().isSetupComplete()) { + setUpFilter(); + } + } } diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 1e61a80d0a..f14ff58b7e 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -328,7 +328,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve /** * The Jenkins instance startup type i.e. NEW, UPGRADE etc */ - private transient InstallState installState = InstallState.UNKNOWN; + private InstallState installState; /** * If we're in the process of an initial setup, @@ -924,7 +924,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve System.exit(0); setupWizard = new SetupWizard(); - InstallUtil.proceedToNextStateFrom(InstallState.UNKNOWN); + getInstallState().initializeState(); launchTcpSlaveAgentListener(); @@ -1033,9 +1033,12 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve public void setInstallState(@Nonnull InstallState newState) { InstallState prior = installState; installState = newState; - if (!prior.equals(newState)) { + LOGGER.log(Main.isDevelopmentMode ? Level.INFO : Level.FINE, "Install state transitioning from: {0} to : {1}", new Object[] { prior, installState }); + if (!newState.equals(prior)) { + getSetupWizard().onInstallStateUpdate(newState); newState.initializeState(); } + saveQuietly(); } /** diff --git a/test/src/test/java/jenkins/install/SetupWizardRestartTest.java b/test/src/test/java/jenkins/install/SetupWizardRestartTest.java new file mode 100644 index 0000000000..474c1772f9 --- /dev/null +++ b/test/src/test/java/jenkins/install/SetupWizardRestartTest.java @@ -0,0 +1,50 @@ +package jenkins.install; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runners.model.Statement; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.RestartableJenkinsRule; + +import hudson.Main; +import jenkins.model.Jenkins; + +public class SetupWizardRestartTest { + @Rule + public RestartableJenkinsRule rr = new RestartableJenkinsRule(); + + @Issue("JENKINS-47439") + @Test + public void restartKeepsSetupWizardState() { + rr.addStep(new Statement() { + @Override + public void evaluate() throws IOException { + // Modify state so that we get into the same conditions as a real start + Main.isUnitTest = false; + FileUtils.write(InstallUtil.getLastExecVersionFile(), ""); + Jenkins j = rr.j.getInstance(); + // Re-evaluate current state based on the new context + InstallUtil.proceedToNextStateFrom(InstallState.UNKNOWN); + assertEquals("Unexpected install state", InstallState.NEW, j.getInstallState()); + assertTrue("Expecting setup wizard filter to be up", j.getSetupWizard().hasSetupWizardFilter()); + InstallUtil.saveLastExecVersion(); + } + }); + // Check that the state is retained after a restart + rr.addStep(new Statement() { + @Override + public void evaluate() { + Jenkins j = rr.j.getInstance(); + assertEquals("Unexpected install state", InstallState.NEW, j.getInstallState()); + assertTrue("Expecting setup wizard filter to be up after restart", j.getSetupWizard().hasSetupWizardFilter()); + } + }); + } + +} -- GitLab From b27bb928a28ee578122eb4b076c8a3d8d68d878c Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Sun, 10 Dec 2017 10:35:17 +0100 Subject: [PATCH 0134/1380] [JENKINS-34254] Set HTTP status code for view --- .../main/resources/hudson/security/csrf/CrumbFilter/retry.jelly | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly index bcd3e5a61c..163e015556 100644 --- a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly +++ b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly @@ -24,7 +24,7 @@ THE SOFTWARE. - + -- GitLab From 065b874a4784c5d0bcfd9731c0ad4139c5e61c78 Mon Sep 17 00:00:00 2001 From: t-hall Date: Thu, 16 Nov 2017 08:18:43 -0800 Subject: [PATCH 0135/1380] [JENKINS-34138] Fix maven installs from stepping on each other (#3042) * [JENKINS-34138] Adding equals/hashCode methods so that installs don't step on each other * [JENKINS-34138] Added issue reference to unit tests * [JENKINS-34138] - changed the order of equals / hashcode (cherry picked from commit d688c154907d17e75ea31067c8fc2525aa679584) --- core/src/main/java/hudson/tasks/Maven.java | 21 +++++++ .../src/test/java/hudson/tasks/MavenTest.java | 62 ++++++++++++------- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/hudson/tasks/Maven.java b/core/src/main/java/hudson/tasks/Maven.java index 61354ed1c1..c1e4a610fe 100644 --- a/core/src/main/java/hudson/tasks/Maven.java +++ b/core/src/main/java/hudson/tasks/Maven.java @@ -709,6 +709,27 @@ public class Maven extends Builder { return ((MavenInstallation)obj).mavenHome; } } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final MavenInstallation that = (MavenInstallation) o; + + if (getHome() != null ? !getHome().equals(that.getHome()) : that.getHome() != null) return false; + if (getName() != null ? !getName().equals(that.getName()) : that.getName() != null) return false; + return true; + } + + @Override + public int hashCode() { + int result = getHome() != null ? getHome().hashCode() : 0; + result = 31 * result + (getName() != null ? getName().hashCode() : 0); + //result = 31 * result + (getProperties() != null ? getProperties().hashCode() : 0); + return result; + } + } /** diff --git a/test/src/test/java/hudson/tasks/MavenTest.java b/test/src/test/java/hudson/tasks/MavenTest.java index 5b1c933a23..411f3d3434 100644 --- a/test/src/test/java/hudson/tasks/MavenTest.java +++ b/test/src/test/java/hudson/tasks/MavenTest.java @@ -23,50 +23,48 @@ */ package hudson.tasks; +import com.gargoylesoftware.htmlunit.html.HtmlButton; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.EnvVars; import hudson.model.Build; +import hudson.model.Cause.LegacyCodeCause; +import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; -import jenkins.mvn.DefaultGlobalSettingsProvider; -import jenkins.mvn.DefaultSettingsProvider; -import jenkins.mvn.FilePathGlobalSettingsProvider; -import jenkins.mvn.FilePathSettingsProvider; -import jenkins.mvn.GlobalMavenConfig; import hudson.model.JDK; +import hudson.model.ParametersAction; import hudson.model.ParametersDefinitionProperty; +import hudson.model.PasswordParameterDefinition; import hudson.model.Result; import hudson.model.StringParameterDefinition; -import hudson.model.ParametersAction; import hudson.model.StringParameterValue; -import hudson.model.Cause.LegacyCodeCause; import hudson.slaves.EnvironmentVariablesNodeProperty; import hudson.slaves.EnvironmentVariablesNodeProperty.Entry; import hudson.tasks.Maven.MavenInstallation; -import hudson.tasks.Maven.MavenInstaller; import hudson.tasks.Maven.MavenInstallation.DescriptorImpl; +import hudson.tasks.Maven.MavenInstaller; +import hudson.tools.InstallSourceProperty; import hudson.tools.ToolProperty; import hudson.tools.ToolPropertyDescriptor; -import hudson.tools.InstallSourceProperty; import hudson.util.DescribableList; - -import java.util.Collections; - -import javax.xml.transform.Source; -import javax.xml.transform.stream.StreamSource; - -import com.gargoylesoftware.htmlunit.html.HtmlForm; -import com.gargoylesoftware.htmlunit.html.HtmlPage; -import com.gargoylesoftware.htmlunit.html.HtmlButton; -import hudson.EnvVars; -import hudson.model.FreeStyleBuild; -import hudson.model.PasswordParameterDefinition; -import org.jvnet.hudson.test.Issue; -import static org.junit.Assert.*; - +import jenkins.mvn.DefaultGlobalSettingsProvider; +import jenkins.mvn.DefaultSettingsProvider; +import jenkins.mvn.FilePathGlobalSettingsProvider; +import jenkins.mvn.FilePathSettingsProvider; +import jenkins.mvn.GlobalMavenConfig; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.ExtractResourceSCM; +import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.ToolInstallations; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import java.util.Collections; + +import static org.junit.Assert.*; + /** * @author Kohsuke Kawaguchi */ @@ -356,4 +354,20 @@ public class MavenTest { assertTrue("Properties should always be injected, even when build variables injection is enabled", log.contains("-DTEST_PROP1=VAL1") && log.contains("-DTEST_PROP2=VAL2")); } + + @Issue("JENKINS-34138") + @Test public void checkMavenInstallationEquals() throws Exception { + MavenInstallation maven = ToolInstallations.configureMaven3(); + MavenInstallation maven2 = ToolInstallations.configureMaven3(); + assertEquals(maven.hashCode(), maven2.hashCode()); + assertTrue(maven.equals(maven2)); + } + + @Issue("JENKINS-34138") + @Test public void checkMavenInstallationNotEquals() throws Exception { + MavenInstallation maven3 = ToolInstallations.configureMaven3(); + MavenInstallation maven2 = ToolInstallations.configureDefaultMaven(); + assertNotEquals(maven3.hashCode(), maven2.hashCode()); + assertFalse(maven3.equals(maven2)); + } } -- GitLab From 6be05b801454820caf3c6159185669f4392f25de Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sun, 26 Nov 2017 15:51:46 +0300 Subject: [PATCH 0136/1380] [JENKINS-48157] - Risk of NPE when migrating MyViewProperty without PrimaryView (#3156) * [JENKINS-48157] - Reproduce the issue in test * [JENKINS-48157] - Annotate and document nullness conditions in MyViewsProperty and ViewGroupMixIn * [FIXED JENKINS-48157] - Prevent NPEs when using public API and when using null primaryViewName * [JENKINS-48157] - Fix typo in Javadoc (cherry picked from commit 211b57e8b002b4da92743dfac3a1b902835e7652) --- .../java/hudson/model/MyViewsProperty.java | 25 ++++++++++++++--- .../java/hudson/model/ViewGroupMixIn.java | 27 +++++++++++++++---- .../hudson/model/MyViewsPropertyTest.java | 16 +++++++++++ 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/hudson/model/MyViewsProperty.java b/core/src/main/java/hudson/model/MyViewsProperty.java index 68ff6e2513..379b37d15d 100644 --- a/core/src/main/java/hudson/model/MyViewsProperty.java +++ b/core/src/main/java/hudson/model/MyViewsProperty.java @@ -39,6 +39,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import javax.annotation.CheckForNull; import javax.servlet.ServletException; import jenkins.model.Jenkins; @@ -61,6 +62,12 @@ import org.kohsuke.stapler.interceptor.RequirePOST; * @author Tom Huybrechts */ public class MyViewsProperty extends UserProperty implements ModifiableViewGroup, Action, StaplerFallback { + + /** + * Name of the primary view defined by the user. + * {@code null} means that the View is not defined. + */ + @CheckForNull private String primaryViewName; /** @@ -71,12 +78,13 @@ public class MyViewsProperty extends UserProperty implements ModifiableViewGroup private transient ViewGroupMixIn viewGroupMixIn; @DataBoundConstructor - public MyViewsProperty(String primaryViewName) { + public MyViewsProperty(@CheckForNull String primaryViewName) { this.primaryViewName = primaryViewName; + readResolve(); // initialize fields } private MyViewsProperty() { - readResolve(); + this(null); } public Object readResolve() { @@ -88,7 +96,10 @@ public class MyViewsProperty extends UserProperty implements ModifiableViewGroup // preserve the non-empty invariant views.add(new AllView(AllView.DEFAULT_VIEW_NAME, this)); } - primaryViewName = AllView.migrateLegacyPrimaryAllViewLocalizedName(views, primaryViewName); + if (primaryViewName != null) { + // It may happen when the default constructor is invoked + primaryViewName = AllView.migrateLegacyPrimaryAllViewLocalizedName(views, primaryViewName); + } viewGroupMixIn = new ViewGroupMixIn(this) { protected List views() { return views; } @@ -99,11 +110,17 @@ public class MyViewsProperty extends UserProperty implements ModifiableViewGroup return this; } + @CheckForNull public String getPrimaryViewName() { return primaryViewName; } - public void setPrimaryViewName(String primaryViewName) { + /** + * Sets the primary view. + * @param primaryViewName Name of the primary view to be set. + * {@code null} to make the primary view undefined. + */ + public void setPrimaryViewName(@CheckForNull String primaryViewName) { this.primaryViewName = primaryViewName; } diff --git a/core/src/main/java/hudson/model/ViewGroupMixIn.java b/core/src/main/java/hudson/model/ViewGroupMixIn.java index 359f68f1c5..313e07eceb 100644 --- a/core/src/main/java/hudson/model/ViewGroupMixIn.java +++ b/core/src/main/java/hudson/model/ViewGroupMixIn.java @@ -36,6 +36,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; /** * Implements {@link ViewGroup} to be used as a "mix-in". @@ -66,27 +67,40 @@ public abstract class ViewGroupMixIn { private final ViewGroup owner; /** - * Returns all the views. This list must be concurrently iterable. + * Returns all views in the group. This list must be modifiable and concurrently iterable. */ + @Nonnull protected abstract List views(); + + /** + * Gets primary view of the mix-in. + * @return Name of the primary view, {@code null} if there is no primary one defined. + */ + @CheckForNull protected abstract String primaryView(); + + /** + * Sets the primary view. + * @param newName Name of the primary view to be set. + * {@code null} to make the primary view undefined. + */ protected abstract void primaryView(String newName); protected ViewGroupMixIn(ViewGroup owner) { this.owner = owner; } - public void addView(View v) throws IOException { + public void addView(@Nonnull View v) throws IOException { v.owner = owner; views().add(v); owner.save(); } - public boolean canDelete(View view) { + public boolean canDelete(@Nonnull View view) { return !view.isDefault(); // Cannot delete primary view } - public synchronized void deleteView(View view) throws IOException { + public synchronized void deleteView(@Nonnull View view) throws IOException { if (views().size() <= 1) throw new IllegalStateException("Cannot delete last view"); views().remove(view); @@ -101,11 +115,14 @@ public abstract class ViewGroupMixIn { */ @CheckForNull public View getView(@CheckForNull String name) { + if (name == null) { + return null; + } for (View v : views()) { if(v.getViewName().equals(name)) return v; } - if (name != null && !name.equals(primaryView())) { + if (!name.equals(primaryView())) { // Fallback to subview of primary view if it is a ViewGroup View pv = getPrimaryView(); if (pv instanceof ViewGroup) diff --git a/test/src/test/java/hudson/model/MyViewsPropertyTest.java b/test/src/test/java/hudson/model/MyViewsPropertyTest.java index 84d798aa67..eab0579941 100644 --- a/test/src/test/java/hudson/model/MyViewsPropertyTest.java +++ b/test/src/test/java/hudson/model/MyViewsPropertyTest.java @@ -33,6 +33,7 @@ import java.io.IOException; import jenkins.model.Jenkins; import org.acegisecurity.AccessDeniedException; import org.acegisecurity.context.SecurityContextHolder; +import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import static org.junit.Assert.*; /** @@ -288,5 +289,20 @@ public class MyViewsPropertyTest { auth.add(Jenkins.ADMINISTER, "User2"); assertTrue("User User2 should configure permission for user User",property.hasPermission(Permission.CONFIGURE)); } + + @Test + @Issue("JENKINS-48157") + public void shouldNotFailWhenMigratingLegacyViewsWithoutPrimaryOne() throws IOException { + rule.jenkins.setSecurityRealm(rule.createDummySecurityRealm()); + User user = User.get("User"); + + // Emulates creation of a new object with Reflection in User#load() does. + MyViewsProperty property = new MyViewsProperty(null); + user.addProperty(property); + + // At AllView with non-default to invoke NPE path in AllView.migrateLegacyPrimaryAllViewLocalizedName() + property.addView(new AllView("foobar")); + property.readResolve(); + } } -- GitLab From b450e9bdb60f6b3a0d492219a026438bf7b3b9d6 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 16 Nov 2017 22:10:57 -0500 Subject: [PATCH 0137/1380] [JENKINS-47429] User.getLegacyConfigFilesFor no longer seems to be necessary. (cherry picked from commit 3853b3813967bd3d46e4dde7741eb97fa628345c) --- core/src/main/java/hudson/model/User.java | 55 ++++------------------- 1 file changed, 9 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index 6b6bc612eb..ce6d49bdd0 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -432,6 +432,10 @@ public class User extends AbstractModelObject implements AccessControlled, Descr * {@code create} is false. */ private static @Nullable User getOrCreate(@Nonnull String id, @Nonnull String fullName, boolean create) { + return getOrCreate(id, fullName, create, getUnsanitizedLegacyConfigFileFor(id)); + } + + private static @Nullable User getOrCreate(@Nonnull String id, @Nonnull String fullName, boolean create, File unsanitizedLegacyConfigFile) { String idkey = idStrategy().keyFor(id); byNameLock.readLock().lock(); @@ -442,49 +446,17 @@ public class User extends AbstractModelObject implements AccessControlled, Descr byNameLock.readLock().unlock(); } final File configFile = getConfigFileFor(id); - if (u == null && !configFile.isFile() && !configFile.getParentFile().isDirectory()) { - // check for legacy users and migrate if safe to do so. - File[] legacy = getLegacyConfigFilesFor(id); - if (legacy != null && legacy.length > 0) { - for (File legacyUserDir : legacy) { - final XmlFile legacyXml = new XmlFile(XSTREAM, new File(legacyUserDir, "config.xml")); - try { - Object o = legacyXml.read(); - if (o instanceof User) { - if (idStrategy().equals(id, legacyUserDir.getName()) && !idStrategy().filenameOf(legacyUserDir.getName()) - .equals(legacyUserDir.getName())) { - if (legacyUserDir.renameTo(configFile.getParentFile())) { - LOGGER.log(Level.INFO, "Migrated user record from {0} to {1}", new Object[] {legacyUserDir, configFile.getParentFile()}); - } else { - LOGGER.log(Level.WARNING, "Failed to migrate user record from {0} to {1}", - new Object[]{legacyUserDir, configFile.getParentFile()}); - } - break; - } - } else { - LOGGER.log(Level.FINE, "Unexpected object loaded from {0}: {1}", - new Object[]{ legacyUserDir, o }); - } - } catch (IOException e) { - LOGGER.log(Level.FINE, String.format("Exception trying to load user from %s: %s", - new Object[]{ legacyUserDir, e.getMessage() }), e); - } - } - } - } - - File unsanitizedLegacyConfigFile = getUnsanitizedLegacyConfigFileFor(id); if (unsanitizedLegacyConfigFile.exists() && !unsanitizedLegacyConfigFile.equals(configFile)) { File ancestor = unsanitizedLegacyConfigFile.getParentFile(); if (!configFile.exists()) { try { Files.createDirectory(configFile.getParentFile().toPath()); Files.move(unsanitizedLegacyConfigFile.toPath(), configFile.toPath()); - LOGGER.log(Level.INFO, "Migrated unsafe user record from {0} to {1}", new Object[] {unsanitizedLegacyConfigFile, configFile}); + LOGGER.log(Level.INFO, "Migrated user record from {0} to {1}", new Object[] {unsanitizedLegacyConfigFile, configFile}); } catch (IOException | InvalidPathException e) { LOGGER.log( Level.WARNING, - String.format("Failed to migrate user record from %s to %s, see SECURITY-499 for more information", idStrategy().legacyFilenameOf(id), idStrategy().filenameOf(id)), + String.format("Failed to migrate user record from %s to %s", unsanitizedLegacyConfigFile, configFile), e); } } @@ -726,16 +698,6 @@ public class User extends AbstractModelObject implements AccessControlled, Descr return new File(getRootDir(), idStrategy().filenameOf(id) +"/config.xml"); } - private static final File[] getLegacyConfigFilesFor(final String id) { - return getRootDir().listFiles(new FileFilter() { - @Override - public boolean accept(File pathname) { - return pathname.isDirectory() && new File(pathname, "config.xml").isFile() && idStrategy().equals( - pathname.getName(), id); - } - }); - } - private static File getUnsanitizedLegacyConfigFileFor(String id) { return new File(getRootDir(), idStrategy().legacyFilenameOf(id) + "/config.xml"); } @@ -1056,9 +1018,10 @@ public class User extends AbstractModelObject implements AccessControlled, Descr File[] subdirs = getRootDir().listFiles((FileFilter) DirectoryFileFilter.INSTANCE); if (subdirs != null) { for (File subdir : subdirs) { - if (new File(subdir, "config.xml").exists()) { + File configFile = new File(subdir, "config.xml"); + if (configFile.exists()) { String name = strategy.idFromFilename(subdir.getName()); - getOrCreate(name, /* calls load(), probably clobbering this anyway */name, true); + getOrCreate(name, /* calls load(), probably clobbering this anyway */name, true, configFile); } } } -- GitLab From afea0567daf4d063390dd1b6488144c86066aa1f Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Mon, 11 Dec 2017 16:45:32 +0100 Subject: [PATCH 0138/1380] =?UTF-8?q?-=20also=20translate=20Item=20=3D>=20?= =?UTF-8?q?"=C3=A9l=C3=A9ment"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/hudson/model/View/newJob_fr.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/resources/hudson/model/View/newJob_fr.properties b/core/src/main/resources/hudson/model/View/newJob_fr.properties index e7d3a7f85f..aa6c85705f 100644 --- a/core/src/main/resources/hudson/model/View/newJob_fr.properties +++ b/core/src/main/resources/hudson/model/View/newJob_fr.properties @@ -26,7 +26,7 @@ CopyExisting=Copier un {0} existant ItemName.help=Champ obligatoire ItemName.label=Saisissez un nom ItemName.validation.required=Ce champ ne peut pas \u00eatre vide. Veuillez saisir un nom valide et appuyer sur OK. -ItemType.validation.required=Veuillez choisir un type d\u2019Item +ItemType.validation.required=Veuillez choisir un type d\u2019\u00e9l\u00e9ment CopyOption.placeholder=Taper pour autocompl\u00e9tion -CopyOption.description=Si vous voulez cr\u00e9er un nouvel item \u00e0 partir d\u2019un autre, vous pouvez utiliser cette option : +CopyOption.description=Si vous voulez cr\u00e9er un nouvel \u00e9l\u00e9ment \u00e0 partir d\u2019un autre, vous pouvez utiliser cette option : CopyOption.label=Copier depuis -- GitLab From a4608a21ced998f1da374961bc3d7537c9c35b0b Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 11 Dec 2017 13:34:19 -0800 Subject: [PATCH 0139/1380] [maven-release-plugin] prepare release jenkins-2.94 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index b6771c7daf..905cad44b5 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.94-SNAPSHOT + 2.94 cli diff --git a/core/pom.xml b/core/pom.xml index fbc89c67de..1a7057dfd4 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.94-SNAPSHOT + 2.94 jenkins-core diff --git a/pom.xml b/pom.xml index 06e7f7efef..e62d01371e 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.94-SNAPSHOT + 2.94 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.94 diff --git a/test/pom.xml b/test/pom.xml index 3d0f103d4a..a5c6b5fc22 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.94-SNAPSHOT + 2.94 test diff --git a/war/pom.xml b/war/pom.xml index effbb6ae30..208c8a92b6 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.94-SNAPSHOT + 2.94 jenkins-war -- GitLab From 78adf02addc47ccb2629c9bb9101a8dd630b22a5 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 11 Dec 2017 13:34:20 -0800 Subject: [PATCH 0140/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 905cad44b5..0fca80941a 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.94 + 2.95-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 1a7057dfd4..a3a7bc6bff 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.94 + 2.95-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index e62d01371e..498b67a88e 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.94 + 2.95-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.94 + HEAD diff --git a/test/pom.xml b/test/pom.xml index a5c6b5fc22..f38b1d2a09 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.94 + 2.95-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 208c8a92b6..f9bb67064b 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.94 + 2.95-SNAPSHOT jenkins-war -- GitLab From b6088dc456e88a369a617fd90b267d15a1a9fe6b Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 11 Dec 2017 17:44:55 -0500 Subject: [PATCH 0141/1380] Deprecating Service in favor of ServiceLoader. --- core/src/main/java/hudson/PluginManager.java | 4 +++- core/src/main/java/hudson/init/InitStrategy.java | 14 ++++++++------ core/src/main/java/hudson/util/Service.java | 6 +++--- core/src/main/java/jenkins/InitReactorRunner.java | 5 +++-- .../java/jenkins/security/ConfidentialStore.java | 14 ++++++++------ 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index c816805ba6..87153180f6 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -120,7 +120,6 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -144,6 +143,7 @@ import hudson.util.FormValidation; import java.io.ByteArrayInputStream; import java.net.JarURLConnection; import java.net.URLConnection; +import java.util.ServiceLoader; import java.util.jar.JarEntry; import static java.util.logging.Level.FINE; @@ -1195,7 +1195,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas /** * Discover all the service provider implementations of the given class, * via META-INF/services. + * @deprecated Use {@link ServiceLoader} instead, or (more commonly) {@link ExtensionList}. */ + @Deprecated public Collection> discover( Class spi ) { Set> result = new HashSet>(); diff --git a/core/src/main/java/hudson/init/InitStrategy.java b/core/src/main/java/hudson/init/InitStrategy.java index 0d1e037e6a..9d7d287c43 100644 --- a/core/src/main/java/hudson/init/InitStrategy.java +++ b/core/src/main/java/hudson/init/InitStrategy.java @@ -17,7 +17,8 @@ import hudson.PluginManager; import jenkins.util.SystemProperties; import hudson.util.DirScanner; import hudson.util.FileVisitor; -import hudson.util.Service; +import java.util.Iterator; +import java.util.ServiceLoader; /** * Strategy pattern of the various key decision making during the Jenkins initialization. @@ -113,11 +114,12 @@ public class InitStrategy { * Obtains the instance to be used. */ public static InitStrategy get(ClassLoader cl) throws IOException { - List r = Service.loadInstances(cl, InitStrategy.class); - if (r.isEmpty()) return new InitStrategy(); // default - - InitStrategy s = r.get(0); - LOGGER.fine("Using "+s+" as InitStrategy"); + Iterator it = ServiceLoader.load(InitStrategy.class, cl).iterator(); + if (!it.hasNext()) { + return new InitStrategy(); // default + } + InitStrategy s = it.next(); + LOGGER.log(Level.FINE, "Using {0} as InitStrategy", s); return s; } diff --git a/core/src/main/java/hudson/util/Service.java b/core/src/main/java/hudson/util/Service.java index 6275b07e3f..a979324f33 100644 --- a/core/src/main/java/hudson/util/Service.java +++ b/core/src/main/java/hudson/util/Service.java @@ -31,6 +31,7 @@ import java.util.Collection; import java.util.Enumeration; import java.util.List; import java.util.ArrayList; +import java.util.ServiceLoader; import java.util.logging.Level; import java.util.logging.Logger; import static java.util.logging.Level.WARNING; @@ -39,11 +40,10 @@ import static java.util.logging.Level.WARNING; * Load classes by looking up META-INF/services. * * @author Kohsuke Kawaguchi + * @deprecated use {@link ServiceLoader} instead. */ +@Deprecated public class Service { - /** - * Poorman's clone of JDK6 ServiceLoader. - */ public static List loadInstances(ClassLoader classLoader, Class type) throws IOException { List result = new ArrayList(); diff --git a/core/src/main/java/jenkins/InitReactorRunner.java b/core/src/main/java/jenkins/InitReactorRunner.java index c5c2bfb844..aa6ef83ab7 100644 --- a/core/src/main/java/jenkins/InitReactorRunner.java +++ b/core/src/main/java/jenkins/InitReactorRunner.java @@ -1,11 +1,11 @@ package jenkins; +import com.google.common.collect.Lists; import jenkins.util.SystemProperties; import hudson.init.InitMilestone; import hudson.init.InitReactorListener; import hudson.util.DaemonThreadFactory; import hudson.util.NamingThreadFactory; -import hudson.util.Service; import jenkins.model.Configuration; import jenkins.model.Jenkins; import org.jvnet.hudson.reactor.Milestone; @@ -16,6 +16,7 @@ import org.jvnet.hudson.reactor.Task; import java.io.IOException; import java.util.List; +import java.util.ServiceLoader; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; @@ -59,7 +60,7 @@ public class InitReactorRunner { * As such there's no way for plugins to participate into this process. */ private ReactorListener buildReactorListener() throws IOException { - List r = (List) Service.loadInstances(Thread.currentThread().getContextClassLoader(), InitReactorListener.class); + List r = Lists.newArrayList(ServiceLoader.load(InitReactorListener.class, Thread.currentThread().getContextClassLoader())); r.add(new ReactorListener() { final Level level = Level.parse( Configuration.getStringConfigParameter("initLogLevel", "FINE") ); public void onTaskStarted(Task t) { diff --git a/core/src/main/java/jenkins/security/ConfidentialStore.java b/core/src/main/java/jenkins/security/ConfidentialStore.java index 6e79d3d70a..1a8a152f82 100644 --- a/core/src/main/java/jenkins/security/ConfidentialStore.java +++ b/core/src/main/java/jenkins/security/ConfidentialStore.java @@ -4,7 +4,6 @@ import hudson.Extension; import hudson.Lookup; import hudson.init.InitMilestone; import hudson.util.Secret; -import hudson.util.Service; import jenkins.model.Jenkins; import org.kohsuke.MetaInfServices; @@ -12,7 +11,9 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import java.io.IOException; import java.security.SecureRandom; -import java.util.List; +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; import java.util.logging.Level; import java.util.logging.Logger; @@ -68,10 +69,11 @@ public abstract class ConfidentialStore { ConfidentialStore cs = lookup.get(ConfidentialStore.class); if (cs==null) { try { - List r = (List) Service.loadInstances(ConfidentialStore.class.getClassLoader(), ConfidentialStore.class); - if (!r.isEmpty()) - cs = r.get(0); - } catch (IOException e) { + Iterator it = ServiceLoader.load(ConfidentialStore.class, ConfidentialStore.class.getClassLoader()).iterator(); + if (it.hasNext()) { + cs = it.next(); + } + } catch (ServiceConfigurationError e) { LOGGER.log(Level.WARNING, "Failed to list up ConfidentialStore implementations",e); // fall through } -- GitLab From 2f45a2332b96a133ef269e2b621617016c98fdfa Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Tue, 12 Dec 2017 00:12:23 +0100 Subject: [PATCH 0142/1380] [JENKINS-34254] Adapt to upstream change using ServiceLoader --- .../java/hudson/security/csrf/CrumbFilter.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/security/csrf/CrumbFilter.java b/core/src/main/java/hudson/security/csrf/CrumbFilter.java index bcaca93132..437d220952 100644 --- a/core/src/main/java/hudson/security/csrf/CrumbFilter.java +++ b/core/src/main/java/hudson/security/csrf/CrumbFilter.java @@ -8,6 +8,9 @@ package hudson.security.csrf; import hudson.util.MultipartFormDataParser; import jenkins.model.Jenkins; import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; +import org.kohsuke.MetaInfServices; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.ForwardToView; import org.kohsuke.stapler.interceptor.RequirePOST; @@ -43,13 +46,16 @@ public class CrumbFilter implements Filter { return h.getCrumbIssuer(); } + @Restricted(NoExternalUse.class) + @MetaInfServices + public static class ErrorCustomizer implements RequirePOST.ErrorCustomizer { + @Override + public ForwardToView getForwardView() { + return new ForwardToView(CrumbFilter.class, "retry"); + } + } + public void init(FilterConfig filterConfig) throws ServletException { - RequirePOST.Processor.registerErrorHandler(new RequirePOST.ErrorHandler() { - @Override - public ForwardToView getForwardView() { - return new ForwardToView(CrumbFilter.this, "retry"); - } - }); } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { -- GitLab From 0c59df3d46fa5c4ac018ad6c26bc38cf5a58d98d Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 12 Dec 2017 04:03:22 -0500 Subject: [PATCH 0143/1380] [JENKINS-48480] Deleting DeprecatedAgentProtocolMonitor.initializerCheck. (#3190) --- .../DeprecatedAgentProtocolMonitor.java | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java b/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java index 096468e7b7..f519e4ecb6 100644 --- a/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java +++ b/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java @@ -24,14 +24,10 @@ package jenkins.slaves; import hudson.Extension; -import hudson.init.InitMilestone; -import hudson.init.Initializer; import hudson.model.AdministrativeMonitor; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.annotation.CheckForNull; import jenkins.AgentProtocol; import jenkins.model.Jenkins; @@ -53,8 +49,6 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; @Restricted(NoExternalUse.class) public class DeprecatedAgentProtocolMonitor extends AdministrativeMonitor { - private static final Logger LOGGER = Logger.getLogger(DeprecatedAgentProtocolMonitor.class.getName()); - public DeprecatedAgentProtocolMonitor() { super(); } @@ -97,17 +91,4 @@ public class DeprecatedAgentProtocolMonitor extends AdministrativeMonitor { } return StringUtils.join(deprecatedProtocols, ','); } - - @Initializer(after = InitMilestone.JOB_LOADED) - @Restricted(NoExternalUse.class) - public static void initializerCheck() { - String protocols = getDeprecatedProtocolsString(); - if(protocols != null) { - LOGGER.log(Level.WARNING, "This Jenkins instance uses deprecated Remoting protocols: {0}" - + "It may impact stability of the instance. " - + "If newer protocol versions are supported by all system components " - + "(agents, CLI and other clients), " - + "it is highly recommended to disable the deprecated protocols.", protocols); - } - } } -- GitLab From 6bf700827a12bb1348c95944cfc52d47a74371d2 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Tue, 12 Dec 2017 10:38:20 -0500 Subject: [PATCH 0144/1380] Do not use InstallState in PluginManager when loading detached plugins --- core/src/main/java/hudson/PluginManager.java | 5 ++--- core/src/main/java/jenkins/install/InstallUtil.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index c816805ba6..7fb2ed8712 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -679,9 +679,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas * */ protected void loadDetachedPlugins() { - InstallState installState = Jenkins.getActiveInstance().getInstallState(); - if (InstallState.UPGRADE.equals(installState)) { - VersionNumber lastExecVersion = new VersionNumber(InstallUtil.getLastExecVersion()); + VersionNumber lastExecVersion = new VersionNumber(InstallUtil.getLastExecVersion()); + if (lastExecVersion.isNewerThan(InstallUtil.NEW_INSTALL_VERSION) && lastExecVersion.isOlderThan(Jenkins.getVersion())) { LOGGER.log(INFO, "Upgrading Jenkins. The last running version was {0}. This Jenkins is version {1}.", new Object[] {lastExecVersion, Jenkins.VERSION}); diff --git a/core/src/main/java/jenkins/install/InstallUtil.java b/core/src/main/java/jenkins/install/InstallUtil.java index e4868e95c1..d89cac2df5 100644 --- a/core/src/main/java/jenkins/install/InstallUtil.java +++ b/core/src/main/java/jenkins/install/InstallUtil.java @@ -70,7 +70,7 @@ public class InstallUtil { private static final Logger LOGGER = Logger.getLogger(InstallUtil.class.getName()); // tests need this to be 1.0 - private static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0"); + public static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0"); private static final VersionNumber FORCE_NEW_INSTALL_VERSION = new VersionNumber("0.0"); /** -- GitLab From 0518a290596fdb9fca5b63e5782a6d38c6182d5d Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Tue, 12 Dec 2017 10:44:56 -0500 Subject: [PATCH 0145/1380] Add test for installing detached plugins after upgrading Jenkins --- .../test/java/hudson/PluginManagerUtil.java | 11 +++ .../install/LoadDetachedPluginsTest.java | 86 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java diff --git a/test/src/test/java/hudson/PluginManagerUtil.java b/test/src/test/java/hudson/PluginManagerUtil.java index 2263622b4c..423213b7d6 100644 --- a/test/src/test/java/hudson/PluginManagerUtil.java +++ b/test/src/test/java/hudson/PluginManagerUtil.java @@ -26,6 +26,8 @@ package hudson; import jenkins.RestartRequiredException; import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; +import org.junit.runner.Description; +import org.jvnet.hudson.test.RestartableJenkinsRule; import org.jvnet.hudson.test.JenkinsRule; import java.io.File; @@ -47,6 +49,15 @@ public class PluginManagerUtil { }; } + public static RestartableJenkinsRule newRestartableJenkinsRule() { + return new RestartableJenkinsRule() { + @Override + public JenkinsRule createJenkinsRule(Description description) { + return newJenkinsRule(); + } + }; + } + public static void dynamicLoad(String plugin, Jenkins jenkins) throws IOException, InterruptedException, RestartRequiredException { dynamicLoad(plugin, jenkins, false); } diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java new file mode 100644 index 0000000000..74caf55781 --- /dev/null +++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java @@ -0,0 +1,86 @@ +/* + * The MIT License + * + * Copyright 2017 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.install; + +import hudson.ClassicPluginStrategy; +import hudson.ClassicPluginStrategy.DetachedPlugin; +import hudson.PluginManager; +import hudson.PluginManagerUtil; +import hudson.PluginWrapper; +import hudson.util.VersionNumber; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.RestartableJenkinsRule; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class LoadDetachedPluginsTest { + + @Rule public RestartableJenkinsRule rr = PluginManagerUtil.newRestartableJenkinsRule(); + + @Issue("JENKINS-48365") + @Test + public void detachedPluginsInstalledAfterUpgrade() throws IOException { + VersionNumber since = new VersionNumber("1.1"); + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + assertFalse("Many plugins have been detached since the given version", detachedPlugins.isEmpty()); + assertTrue("Detached plugins should not be installed on a new instance", + getInstalledDetachedPlugins(r, detachedPlugins).isEmpty()); + // Trick PluginManager into thinking an upgrade happened when Jenkins restarts. + InstallUtil.saveLastExecVersion(since.toString()); + }); + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + assertThat("Plugins have been detached since the pre-upgrade version", + detachedPlugins.size(), greaterThan(15)); + assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", + getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); + }); + } + + private List getInstalledDetachedPlugins(JenkinsRule r, List detachedPlugins) { + PluginManager pluginManager = r.jenkins.getPluginManager(); + List installedPlugins = new ArrayList<>(); + for (DetachedPlugin plugin : detachedPlugins) { + PluginWrapper wrapper = pluginManager.getPlugin(plugin.getShortName()); + if (wrapper != null) { + installedPlugins.add(wrapper); + assertTrue(wrapper.isActive()); + } + } + return installedPlugins; + } + +} -- GitLab From efd8b7153b8b7b29b8af6b73236e61d041b251cc Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Tue, 12 Dec 2017 17:16:30 -0500 Subject: [PATCH 0146/1380] Use LocalData to test upgrades from before and after 2.0 --- .../install/LoadDetachedPluginsTest.java | 43 +++++++++++++++---- .../upgradeFromJenkins1/config.xml | 34 +++++++++++++++ ...enkins.install.InstallUtil.lastExecVersion | 1 + 3 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml create mode 100644 test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java index 74caf55781..f70b4d3a93 100644 --- a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java +++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java @@ -38,10 +38,12 @@ import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.RestartableJenkinsRule; +import org.jvnet.hudson.test.recipes.LocalData; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; -import static org.junit.Assert.assertFalse; +import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -51,25 +53,48 @@ public class LoadDetachedPluginsTest { @Issue("JENKINS-48365") @Test - public void detachedPluginsInstalledAfterUpgrade() throws IOException { - VersionNumber since = new VersionNumber("1.1"); + @LocalData + public void upgradeFromJenkins1() throws IOException { + VersionNumber since = new VersionNumber("1.550"); rr.then(r -> { List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); - assertFalse("Many plugins have been detached since the given version", detachedPlugins.isEmpty()); - assertTrue("Detached plugins should not be installed on a new instance", - getInstalledDetachedPlugins(r, detachedPlugins).isEmpty()); - // Trick PluginManager into thinking an upgrade happened when Jenkins restarts. - InstallUtil.saveLastExecVersion(since.toString()); + assertThat("Plugins have been detached since the pre-upgrade version", + detachedPlugins.size(), greaterThan(4)); + assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", + getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); }); + } + + @Issue("JENKINS-48365") + @Test + @LocalData + public void upgradeFromJenkins2() { + VersionNumber since = new VersionNumber("2.0"); rr.then(r -> { List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); assertThat("Plugins have been detached since the pre-upgrade version", - detachedPlugins.size(), greaterThan(15)); + detachedPlugins.size(), greaterThan(1)); assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); }); } + @Test + public void newInstallation() { + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(); + assertThat("Detached plugins should exist", detachedPlugins, not(empty())); + assertThat("Detached plugins should not be installed on a new instance", + getInstalledDetachedPlugins(r, detachedPlugins), empty()); + }); + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(); + assertThat("Detached plugins should exist", detachedPlugins, not(empty())); + assertThat("Detached plugins should not be installed after restarting", + getInstalledDetachedPlugins(r, detachedPlugins), empty()); + }); + } + private List getInstalledDetachedPlugins(JenkinsRule r, List detachedPlugins) { PluginManager pluginManager = r.jenkins.getPluginManager(); List installedPlugins = new ArrayList<>(); diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml new file mode 100644 index 0000000000..6fcea351c2 --- /dev/null +++ b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml @@ -0,0 +1,34 @@ + + + + 1.550 + 2 + NORMAL + true + + + false + + ${JENKINS_HOME}/workspace/${ITEM_FULLNAME} + ${ITEM_ROOTDIR}/builds + + + + + 5 + 0 + + + + All + false + false + + + + All + 0 + + + + \ No newline at end of file diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion new file mode 100644 index 0000000000..415b19fc36 --- /dev/null +++ b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion @@ -0,0 +1 @@ +2.0 \ No newline at end of file -- GitLab From cfbc34ed729e0564f75c1497f49cdffcefaeebe3 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Tue, 12 Dec 2017 17:37:25 -0500 Subject: [PATCH 0147/1380] Assert that detached plugins and their dependencies load successfully --- .../jenkins/install/LoadDetachedPluginsTest.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java index f70b4d3a93..eecc1e2b49 100644 --- a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java +++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java @@ -62,6 +62,7 @@ public class LoadDetachedPluginsTest { detachedPlugins.size(), greaterThan(4)); assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); + assertNoFailedPlugins(r); }); } @@ -76,6 +77,7 @@ public class LoadDetachedPluginsTest { detachedPlugins.size(), greaterThan(1)); assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); + assertNoFailedPlugins(r); }); } @@ -86,12 +88,14 @@ public class LoadDetachedPluginsTest { assertThat("Detached plugins should exist", detachedPlugins, not(empty())); assertThat("Detached plugins should not be installed on a new instance", getInstalledDetachedPlugins(r, detachedPlugins), empty()); + assertNoFailedPlugins(r); }); rr.then(r -> { List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(); assertThat("Detached plugins should exist", detachedPlugins, not(empty())); assertThat("Detached plugins should not be installed after restarting", getInstalledDetachedPlugins(r, detachedPlugins), empty()); + assertNoFailedPlugins(r); }); } @@ -102,10 +106,16 @@ public class LoadDetachedPluginsTest { PluginWrapper wrapper = pluginManager.getPlugin(plugin.getShortName()); if (wrapper != null) { installedPlugins.add(wrapper); - assertTrue(wrapper.isActive()); + assertTrue("Detached plugins should be active if installed", wrapper.isActive()); + assertThat("Detached plugins should not have dependency errors", wrapper.getDependencyErrors(), empty()); } } return installedPlugins; } + private void assertNoFailedPlugins(JenkinsRule r) { + assertThat("Detached plugins and their dependencies should not fail to install", + r.jenkins.getPluginManager().getFailedPlugins(), empty()); + } + } -- GitLab From bf4c8e6fdfdd7ff0ba514877422f55622f15c15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20F=C3=BCrbacher?= Date: Wed, 13 Dec 2017 15:06:53 +0100 Subject: [PATCH 0148/1380] Fixed typo in German translation. --- core/src/main/resources/lib/hudson/scriptConsole_de.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/hudson/scriptConsole_de.properties b/core/src/main/resources/lib/hudson/scriptConsole_de.properties index 4db58a7382..6505dca93e 100644 --- a/core/src/main/resources/lib/hudson/scriptConsole_de.properties +++ b/core/src/main/resources/lib/hudson/scriptConsole_de.properties @@ -26,7 +26,7 @@ Run=Ausf\u00FChren description=Geben Sie ein beliebiges Groovy-Skript \ ein und f\u00FChren Sie dieses auf dem Server aus. Dies ist n\u00FCtzlich bei der Fehlersuche und zur Diagnostik. \ Verwenden Sie den println-Befehl, um Ausgaben sichtbar zu machen (wenn Sie System.out \ - verwenden, gehen die Ausgaben auf die Standardausgabe (STDOUT) des Servers, die schwieriger + verwenden, gehen die Ausgaben auf die Standardausgabe (STDOUT) des Servers, die schwieriger \ einzusehen ist). Beispiel: description2=Alle Klassen aller Plugins sind verf\u00FCgbar. jenkins.*, jenkins.model.*, hudson.* sowie hudson.model.* werden standardm\u00E4\u00DFig importiert. It\ is\ not\ possible\ to\ run\ scripts\ when\ agent\ is\ offline.=Es ist nicht m\u00F6glich, Skripte auf einem Agenten, der offline ist, auszuf\u00FChren -- GitLab From ccc374a7176d7704941fb494589790b7673efe2e Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 13 Dec 2017 12:59:04 -0500 Subject: [PATCH 0149/1380] [SECURITY-667] Ensure all tasks have completed before we attain COMPLETED. --- core/src/main/java/jenkins/model/Jenkins.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 8d16eaed99..b1f64dd9c5 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -3097,7 +3097,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve }); } - g.requires(JOB_LOADED).add("Cleaning up obsolete items deleted from the disk", new Executable() { + g.requires(JOB_LOADED).attains(COMPLETED).add("Cleaning up obsolete items deleted from the disk", new Executable() { public void run(Reactor reactor) throws Exception { // anything we didn't load from disk, throw them away. // doing this after loading from disk allows newly loaded items @@ -3112,7 +3112,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve } }); - g.requires(JOB_LOADED).add("Finalizing set up",new Executable() { + g.requires(JOB_LOADED).attains(COMPLETED).add("Finalizing set up",new Executable() { public void run(Reactor session) throws Exception { rebuildDependencyGraph(); -- GitLab From 51b0f2f5d31cd8cbec5a11fb070b02cb3a077fe3 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 13 Dec 2017 17:17:16 -0500 Subject: [PATCH 0150/1380] Redesigned fix to run from XStream2.unmarshal. --- core/src/main/java/hudson/model/View.java | 2 + .../java/hudson/util/ReflectionUtils.java | 19 +++-- .../util/RobustReflectionConverter.java | 77 ------------------- core/src/main/java/hudson/util/XStream2.java | 61 ++++++++++++++- .../hudson/model/AbstractProjectTest.java | 12 +++ test/src/test/java/hudson/model/ViewTest.java | 20 ++++- 6 files changed, 105 insertions(+), 86 deletions(-) diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index fa3914af44..b38c2b96d1 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -1213,6 +1213,7 @@ public abstract class View extends AbstractModelObject implements AccessControll // Do not allow overwriting view name as it might collide with another // view in same ViewGroup and might not satisfy Jenkins.checkGoodName. String oldname = name; + ViewGroup oldOwner = owner; // oddly, this field is not transient Object o = Jenkins.XSTREAM.unmarshal(new Xpp3Driver().createReader(in), this); if (!o.getClass().equals(getClass())) { // ensure that we've got the same view type. extending this code to support updating @@ -1222,6 +1223,7 @@ public abstract class View extends AbstractModelObject implements AccessControll "the view with the new view type."); } name = oldname; + owner = oldOwner; } catch (StreamException | ConversionException | Error e) {// mostly reflection errors throw new IOException("Unable to read",e); } diff --git a/core/src/main/java/hudson/util/ReflectionUtils.java b/core/src/main/java/hudson/util/ReflectionUtils.java index f491b59d87..faa6afb99a 100644 --- a/core/src/main/java/hudson/util/ReflectionUtils.java +++ b/core/src/main/java/hudson/util/ReflectionUtils.java @@ -37,6 +37,7 @@ import java.util.AbstractList; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.CheckForNull; /** * Utility code for reflection. @@ -205,15 +206,23 @@ public class ReflectionUtils extends org.springframework.util.ReflectionUtils { /** * Given the primitive type, returns the VM default value for that type in a boxed form. + * @return null unless {@link Class#isPrimitive} */ - public static Object getVmDefaultValueForPrimitiveType(Class type) { + public static @CheckForNull Object getVmDefaultValueForPrimitiveType(Class type) { return defaultPrimitiveValue.get(type); } - private static final Map defaultPrimitiveValue = new HashMap(); + // TODO the version in org.kohsuke.stapler is incomplete + private static final Map, Object> defaultPrimitiveValue = new HashMap<>(); static { - defaultPrimitiveValue.put(boolean.class,false); - defaultPrimitiveValue.put(int.class,0); - defaultPrimitiveValue.put(long.class,0L); + defaultPrimitiveValue.put(boolean.class, false); + defaultPrimitiveValue.put(char.class, '\0'); + defaultPrimitiveValue.put(byte.class, (byte) 0); + defaultPrimitiveValue.put(short.class, (short) 0); + defaultPrimitiveValue.put(int.class, 0); + defaultPrimitiveValue.put(long.class, 0L); + defaultPrimitiveValue.put(float.class, (float) 0); + defaultPrimitiveValue.put(double.class, (double) 0); + defaultPrimitiveValue.put(void.class, null); // FWIW } } diff --git a/core/src/main/java/hudson/util/RobustReflectionConverter.java b/core/src/main/java/hudson/util/RobustReflectionConverter.java index a65c7f4e17..3ec65ec994 100644 --- a/core/src/main/java/hudson/util/RobustReflectionConverter.java +++ b/core/src/main/java/hudson/util/RobustReflectionConverter.java @@ -271,71 +271,8 @@ public class RobustReflectionConverter implements Converter { return serializationMethodInvoker.callReadResolve(result); } - private static final class FieldExpectation { - private final Class definingClass; - private final String name; - - public FieldExpectation(Class definingClass, String name) { - this.definingClass = definingClass; - this.name = name; - } - - public Class getDefiningClass() { - return definingClass; - } - - public String getName() { - return name; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - FieldExpectation that = (FieldExpectation) o; - - if (definingClass != null ? !definingClass.equals(that.definingClass) : that.definingClass != null) { - return false; - } - return name.equals(that.name); - } - - @Override - public int hashCode() { - int result = definingClass != null ? definingClass.hashCode() : 0; - result = 31 * result + name.hashCode(); - return result; - } - - @Override - public String toString() { - return "FieldExpectation{" + - "definingClass=" + definingClass + - ", name='" + name + '\'' + - '}'; - } - - - } - public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) { final SeenFields seenFields = new SeenFields(); - final boolean existingObject = context.currentObject() != null; - final Map expectedFields = existingObject ? new HashMap() : null; - final Object cleanInstance = existingObject ? reflectionProvider.newInstance(result.getClass()) : null; - if (existingObject) { - reflectionProvider.visitSerializableFields(cleanInstance, new ReflectionProvider.Visitor() { - @Override - public void visit(String name, Class type, Class definedIn, Object value) { - expectedFields.put(new FieldExpectation(definedIn, name), value); - } - }); - } Iterator it = reader.getAttributeNames(); // Remember outermost Saveable encountered, for reporting below if (result instanceof Saveable && context.get("Saveable") == null) @@ -364,10 +301,6 @@ public class RobustReflectionConverter implements Converter { } reflectionProvider.writeField(result, attrName, value, classDefiningField); seenFields.add(classDefiningField, attrName); - if (existingObject) { - expectedFields.remove(new FieldExpectation( - classDefiningField == null ? result.getClass() : classDefiningField, attrName)); - } } } } @@ -409,10 +342,6 @@ public class RobustReflectionConverter implements Converter { LOGGER.warning("Cannot convert type " + value.getClass().getName() + " to type " + type.getName()); // behave as if we didn't see this element } else { - if (existingObject) { - expectedFields.remove(new FieldExpectation( - classDefiningField == null ? result.getClass() : classDefiningField, fieldName)); - } if (fieldExistsInClass) { reflectionProvider.writeField(result, fieldName, value, classDefiningField); seenFields.add(classDefiningField, fieldName); @@ -442,12 +371,6 @@ public class RobustReflectionConverter implements Converter { OldDataMonitor.report((Saveable)result, (ArrayList)context.get("ReadError")); context.put("ReadError", null); } - if (existingObject) { - for (Map.Entry entry : expectedFields.entrySet()) { - reflectionProvider.writeField(result, entry.getKey().getName(), entry.getValue(), - entry.getKey().getDefiningClass()); - } - } return result; } diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 6b17761cb6..de64a7e82e 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -39,9 +39,11 @@ import com.thoughtworks.xstream.converters.SingleValueConverterWrapper; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.converters.extended.DynamicProxyConverter; import com.thoughtworks.xstream.core.JVM; +import com.thoughtworks.xstream.core.util.Fields; import com.thoughtworks.xstream.io.HierarchicalStreamDriver; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import com.thoughtworks.xstream.io.ReaderWrapper; import com.thoughtworks.xstream.mapper.CannotResolveClassException; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.PluginManager; @@ -63,19 +65,25 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.nio.charset.Charset; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.CheckForNull; -import org.kohsuke.accmod.Restricted; -import org.kohsuke.accmod.restrictions.NoExternalUse; /** * {@link XStream} enhanced for additional Java5 support and improved robustness. * @author Kohsuke Kawaguchi */ public class XStream2 extends XStream { + + private static final Logger LOGGER = Logger.getLogger(XStream2.class.getName()); + private RobustReflectionConverter reflectionConverter; private final ThreadLocal oldData = new ThreadLocal(); private final @CheckForNull ClassOwnership classOwnership; @@ -111,7 +119,54 @@ public class XStream2 extends XStream { setClassLoader(h.pluginManager.uberClassLoader); } - Object o = super.unmarshal(reader,root,dataHolder); + Object o; + if (root == null) { + o = super.unmarshal(reader, null, dataHolder); + } else { + Set topLevelFields = new HashSet<>(); + o = super.unmarshal(new ReaderWrapper(reader) { + int depth; + @Override + public void moveUp() { + if (--depth == 0) { + topLevelFields.add(getNodeName()); + } + super.moveUp(); + } + @Override + public void moveDown() { + try { + super.moveDown(); + } finally { + depth++; + } + } + }, root, dataHolder); + if (o == root && getConverterLookup().lookupConverterForType(o.getClass()) instanceof RobustReflectionConverter) { + getReflectionProvider().visitSerializableFields(o, (String name, Class type, Class definedIn, Object value) -> { + if (topLevelFields.contains(name)) { + return; + } + Field f = Fields.find(definedIn, name); + Object v; + if (type.isPrimitive()) { + // oddly not in com.thoughtworks.xstream.core.util.Primitives + v = ReflectionUtils.getVmDefaultValueForPrimitiveType(type); + if (v.equals(value)) { + return; + } + } else { + if (value == null) { + return; + } + v = null; + } + LOGGER.log(Level.FINE, "JENKINS-21017: nulling out {0} in {1}", new Object[] {f, o}); + Fields.write(f, o, v); + }); + } + } + if (oldData.get()!=null) { oldData.remove(); if (o instanceof Saveable) OldDataMonitor.report((Saveable)o, "1.106"); diff --git a/test/src/test/java/hudson/model/AbstractProjectTest.java b/test/src/test/java/hudson/model/AbstractProjectTest.java index 308fc12993..c6fddb6a97 100644 --- a/test/src/test/java/hudson/model/AbstractProjectTest.java +++ b/test/src/test/java/hudson/model/AbstractProjectTest.java @@ -569,6 +569,18 @@ public class AbstractProjectTest { return con; } + @Issue("JENKINS-21017") + @Test public void doConfigDotXmlReset() throws Exception { + j.jenkins.setCrumbIssuer(null); + FreeStyleProject p = j.createFreeStyleProject(); + p.setAssignedLabel(Label.get("whatever")); + assertEquals("whatever", p.getAssignedLabelString()); + assertThat(p.getConfigFile().asString(), containsString("whatever")); + assertEquals(200, postConfigDotXml(p, "").getResponseCode()); + assertNull(p.getAssignedLabelString()); // did not work + assertThat(p.getConfigFile().asString(), not(containsString(""))); // actually did work anyway + } + @Test @Issue("JENKINS-27549") public void loadingWithNPEOnTriggerStart() throws Exception { diff --git a/test/src/test/java/hudson/model/ViewTest.java b/test/src/test/java/hudson/model/ViewTest.java index 7a70d87df2..f3bd6f70a6 100644 --- a/test/src/test/java/hudson/model/ViewTest.java +++ b/test/src/test/java/hudson/model/ViewTest.java @@ -62,7 +62,7 @@ import java.util.logging.LogRecord; import jenkins.model.ProjectNamingStrategy; import jenkins.security.NotReallyRoleSensitiveCallable; -import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import org.junit.Ignore; import org.junit.Rule; @@ -243,6 +243,24 @@ public class ViewTest { assertTrue(xml, xml.contains("two")); } + @Issue("JENKINS-21017") + @Test public void doConfigDotXmlReset() throws Exception { + ListView view = listView("v"); + view.description = "one"; + WebClient wc = j.createWebClient(); + String xml = wc.goToXml("view/v/config.xml").getWebResponse().getContentAsString(); + assertThat(xml, containsString("one")); + xml = xml.replace("one", ""); + WebRequest req = new WebRequest(wc.createCrumbedUrl("view/v/config.xml"), HttpMethod.POST); + req.setRequestBody(xml); + req.setEncodingType(null); + wc.getPage(req); + assertEquals(null, view.getDescription()); // did not work + xml = new XmlFile(Jenkins.XSTREAM, new File(j.jenkins.getRootDir(), "config.xml")).asString(); + assertThat(xml, not(containsString(""))); // did not work + assertEquals(j.jenkins, view.getOwner()); + } + @Test public void testGetQueueItems() throws IOException, Exception{ ListView view1 = listView("view1"); -- GitLab From b6443aacbbc3fffec1f8f45ce1fb9fb322f5030f Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 13 Dec 2017 16:41:56 -0800 Subject: [PATCH 0151/1380] [maven-release-plugin] prepare release jenkins-2.89.2 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 1fc896f74f..8d2ebc729a 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.89.2-SNAPSHOT + 2.89.2 cli diff --git a/core/pom.xml b/core/pom.xml index 6278a3fead..8fbad1e747 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.2-SNAPSHOT + 2.89.2 jenkins-core diff --git a/pom.xml b/pom.xml index 4e57abc3bf..d08c38ad14 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.2-SNAPSHOT + 2.89.2 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.89.2 diff --git a/test/pom.xml b/test/pom.xml index 839aed6dbd..9474fc02e0 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.2-SNAPSHOT + 2.89.2 test diff --git a/war/pom.xml b/war/pom.xml index a7f48bb18a..13f910f9f6 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.2-SNAPSHOT + 2.89.2 jenkins-war -- GitLab From 8ae7df076e15cf6961902e2e632a9da9a8341ea8 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 13 Dec 2017 16:41:56 -0800 Subject: [PATCH 0152/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 8d2ebc729a..a6f850b032 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.89.2 + 2.89.3-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 8fbad1e747..77438d3623 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.2 + 2.89.3-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index d08c38ad14..803856390a 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.2 + 2.89.3-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.89.2 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 9474fc02e0..40e66224ae 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.2 + 2.89.3-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 13f910f9f6..4405af0e96 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.2 + 2.89.3-SNAPSHOT jenkins-war -- GitLab From 0f029f39e6edaa18a26f9244c1db42e801e90c92 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 13 Dec 2017 17:58:00 -0800 Subject: [PATCH 0153/1380] [maven-release-plugin] prepare release jenkins-2.95 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 0fca80941a..fda944b19a 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.95-SNAPSHOT + 2.95 cli diff --git a/core/pom.xml b/core/pom.xml index a3a7bc6bff..731f6265b8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.95-SNAPSHOT + 2.95 jenkins-core diff --git a/pom.xml b/pom.xml index 498b67a88e..f8505941fb 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.95-SNAPSHOT + 2.95 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.95 diff --git a/test/pom.xml b/test/pom.xml index f38b1d2a09..7d714bd526 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.95-SNAPSHOT + 2.95 test diff --git a/war/pom.xml b/war/pom.xml index f9bb67064b..277328007a 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.95-SNAPSHOT + 2.95 jenkins-war -- GitLab From 4c75250ad4b0d99496950ae83f4ba6aa2255c1c6 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 13 Dec 2017 17:58:00 -0800 Subject: [PATCH 0154/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index fda944b19a..1cb344f3bf 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.95 + 2.96-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 731f6265b8..0db9130a23 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.95 + 2.96-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index f8505941fb..9bde65a91d 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.95 + 2.96-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.95 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 7d714bd526..578d451007 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.95 + 2.96-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 277328007a..13086ab727 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.95 + 2.96-SNAPSHOT jenkins-war -- GitLab From 67476c7920c9c004bd97b9d12beae8c4f9f406cd Mon Sep 17 00:00:00 2001 From: Nicolas De loof Date: Mon, 11 Dec 2017 14:34:42 +0100 Subject: [PATCH 0155/1380] upgrade access-modifier to 1.12 with 1.12 @Restricted annotation are kept for runtime, which is a requirement for [configuration-as-code](https://github.com/jenkinsci/configuration-as-code-plugin) to detect technical attribute which should not be exposed (typically : `Jenkins#setInstallState()`) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9bde65a91d..61644dbd00 100644 --- a/pom.xml +++ b/pom.xml @@ -94,7 +94,7 @@ THE SOFTWARE. ${skipTests} 3.0.4 true - 1.11 + 1.12 ${access-modifier.version} ${access-modifier.version} -- GitLab From ddb9ad68b4e74deb85f916d8d9ebe0ba5c044517 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 14 Dec 2017 13:16:42 -0500 Subject: [PATCH 0156/1380] Making nullOut behavior opt-in, so limiting to AbstractItem and View (and in particular not Jenkins). --- core/src/main/java/hudson/XmlFile.java | 17 +++++++++- .../main/java/hudson/model/AbstractItem.java | 2 +- core/src/main/java/hudson/model/View.java | 2 +- core/src/main/java/hudson/util/XStream2.java | 26 ++++++++++++++-- .../test/java/hudson/util/XStream2Test.java | 31 +++++++++++-------- 5 files changed, 60 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/hudson/XmlFile.java b/core/src/main/java/hudson/XmlFile.java index 16644262b9..bde1ac12a4 100644 --- a/core/src/main/java/hudson/XmlFile.java +++ b/core/src/main/java/hudson/XmlFile.java @@ -159,10 +159,25 @@ public final class XmlFile { * if the XML representation is completely new. */ public Object unmarshal( Object o ) throws IOException { + return unmarshal(o, false); + } + + /** + * Variant of {@link #unmarshal(Object)} applying {@link XStream2#unmarshal(HierarchicalStreamReader, Object, DataHolder, boolean)}. + * @since FIXME + */ + public Object unmarshalNullingOut(Object o) throws IOException { + return unmarshal(o, true); + } + private Object unmarshal(Object o, boolean nullOut) throws IOException { try (InputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()))) { // TODO: expose XStream the driver from XStream - return xs.unmarshal(DEFAULT_DRIVER.createReader(in), o); + if (nullOut) { + return ((XStream2) xs).unmarshal(DEFAULT_DRIVER.createReader(in), o, null, true); + } else { + return xs.unmarshal(DEFAULT_DRIVER.createReader(in), o); + } } catch (RuntimeException | Error e) { throw new IOException("Unable to read "+file,e); } diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index 4c026d5f31..e06a3f1988 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -752,7 +752,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet } // try to reflect the changes by reloading - Object o = new XmlFile(Items.XSTREAM, out.getTemporaryFile()).unmarshal(this); + Object o = new XmlFile(Items.XSTREAM, out.getTemporaryFile()).unmarshalNullingOut(this); if (o!=this) { // ensure that we've got the same job type. extending this code to support updating // to different job type requires destroying & creating a new job type diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index b38c2b96d1..83a3682503 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -1214,7 +1214,7 @@ public abstract class View extends AbstractModelObject implements AccessControll // view in same ViewGroup and might not satisfy Jenkins.checkGoodName. String oldname = name; ViewGroup oldOwner = owner; // oddly, this field is not transient - Object o = Jenkins.XSTREAM.unmarshal(new Xpp3Driver().createReader(in), this); + Object o = Jenkins.XSTREAM2.unmarshal(new Xpp3Driver().createReader(in), this, null, true); if (!o.getClass().equals(getClass())) { // ensure that we've got the same view type. extending this code to support updating // to different view type requires destroying & creating a new view type diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index de64a7e82e..9d4ef9b99b 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -48,6 +48,7 @@ import com.thoughtworks.xstream.mapper.CannotResolveClassException; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.PluginManager; import hudson.PluginWrapper; +import hudson.XmlFile; import hudson.diagnosis.OldDataMonitor; import hudson.remoting.ClassFilter; import hudson.util.xstream.ImmutableSetConverter; @@ -75,6 +76,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; /** * {@link XStream} enhanced for additional Java5 support and improved robustness. @@ -112,6 +114,26 @@ public class XStream2 extends XStream { @Override public Object unmarshal(HierarchicalStreamReader reader, Object root, DataHolder dataHolder) { + return unmarshal(reader, root, dataHolder, false); + } + + /** + * Variant of {@link #unmarshal(HierarchicalStreamReader, Object, DataHolder)} that nulls out non-{@code transient} instance fields not defined in the source when unmarshaling into an existing object. + *

Typically useful when loading user-supplied XML files in place (non-null {@code root}) + * where some reference-valued fields of the root object may have legitimate reasons for being null. + * Without this mode, it is impossible to clear such fields in an existing instance, + * since XStream has no notation for a null field value. + * Even for primitive-valued fields, it is useful to guarantee + * that unmarshaling will produce the same result as creating a new instance. + *

Do not use in cases where the root objects defines fields (typically {@code final}) + * which it expects to be {@link Nonnull} unless you are prepared to restore default values for those fields. + * @param nullOut whether to perform this special behavior; + * false to use the stock XStream behavior of leaving unmentioned {@code root} fields untouched + * @see XmlFile#unmarshalNullingOut + * @see JENKINS-21017 + * @since FIXME + */ + public Object unmarshal(HierarchicalStreamReader reader, Object root, DataHolder dataHolder, boolean nullOut) { // init() is too early to do this // defensive because some use of XStream happens before plugins are initialized. Jenkins h = Jenkins.getInstanceOrNull(); @@ -120,8 +142,8 @@ public class XStream2 extends XStream { } Object o; - if (root == null) { - o = super.unmarshal(reader, null, dataHolder); + if (root == null || !nullOut) { + o = super.unmarshal(reader, root, dataHolder); } else { Set topLevelFields = new HashSet<>(); o = super.unmarshal(new ReaderWrapper(reader) { diff --git a/core/src/test/java/hudson/util/XStream2Test.java b/core/src/test/java/hudson/util/XStream2Test.java index ac1eb1c01a..f5a038ae79 100644 --- a/core/src/test/java/hudson/util/XStream2Test.java +++ b/core/src/test/java/hudson/util/XStream2Test.java @@ -29,11 +29,11 @@ import static org.junit.Assert.*; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.thoughtworks.xstream.XStreamException; +import com.thoughtworks.xstream.io.xml.Xpp3Driver; import com.thoughtworks.xstream.security.ForbiddenClassException; -import hudson.XmlFile; import hudson.model.Result; import hudson.model.Run; -import java.io.File; +import java.io.StringReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -42,10 +42,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import jenkins.model.Jenkins; -import org.apache.commons.io.FileUtils; import org.junit.Test; import org.jvnet.hudson.test.Issue; -import org.jvnet.hudson.test.Issue; /** * Tests for XML serialization of java objects. @@ -352,9 +350,9 @@ public class XStream2Test { WithDefaults newInstance = new WithDefaults(); - String xmlA = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(populatedXml, existingInstance)); - String xmlB = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(populatedXml, newInstance)); - String xmlC = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(populatedXml, null)); + String xmlA = Jenkins.XSTREAM2.toXML(fromXMLNullingOut(populatedXml, existingInstance)); + String xmlB = Jenkins.XSTREAM2.toXML(fromXMLNullingOut(populatedXml, newInstance)); + String xmlC = Jenkins.XSTREAM2.toXML(fromXMLNullingOut(populatedXml, null)); assertThat("Deserializing over an existing instance is the same as with no root", xmlA, is(xmlC)); assertThat("Deserializing over an new instance is the same as with no root", xmlB, is(xmlC)); @@ -390,9 +388,9 @@ public class XStream2Test { WithDefaults newInstance = new WithDefaults(); - String xmlA = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(defaultXml, existingInstance)); - String xmlB = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(defaultXml, newInstance)); - String xmlC = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(defaultXml, null)); + String xmlA = Jenkins.XSTREAM2.toXML(fromXMLNullingOut(defaultXml, existingInstance)); + String xmlB = Jenkins.XSTREAM2.toXML(fromXMLNullingOut(defaultXml, newInstance)); + String xmlC = Jenkins.XSTREAM2.toXML(fromXMLNullingOut(defaultXml, null)); assertThat("Deserializing over an existing instance is the same as with no root", xmlA, is(xmlC)); assertThat("Deserializing over an new instance is the same as with no root", xmlB, is(xmlC)); @@ -416,14 +414,21 @@ public class XStream2Test { WithDefaults newInstance = new WithDefaults(); - String xmlA = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(emptyXml, existingInstance)); - String xmlB = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(emptyXml, newInstance)); - String xmlC = Jenkins.XSTREAM2.toXML(Jenkins.XSTREAM2.fromXML(emptyXml, null)); + Object reloaded = fromXMLNullingOut(emptyXml, existingInstance); + assertSame(existingInstance, reloaded); + String xmlA = Jenkins.XSTREAM2.toXML(reloaded); + String xmlB = Jenkins.XSTREAM2.toXML(fromXMLNullingOut(emptyXml, newInstance)); + String xmlC = Jenkins.XSTREAM2.toXML(fromXMLNullingOut(emptyXml, null)); assertThat("Deserializing over an existing instance is the same as with no root", xmlA, is(xmlC)); assertThat("Deserializing over an new instance is the same as with no root", xmlB, is(xmlC)); } + private Object fromXMLNullingOut(String xml, Object root) { + // Currently not offering a public convenience API for this: + return Jenkins.XSTREAM2.unmarshal(new Xpp3Driver().createReader(new StringReader(xml)), root, null, true); + } + public static class WithDefaults { private String stringDefaultValue = "defaultValue"; private String stringDefaultNull; -- GitLab From b7f42b2e59b2081782d6e51da18b0c93808d98da Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Thu, 14 Dec 2017 20:44:54 +0100 Subject: [PATCH 0157/1380] [JENKINS-27027] Notify the SecurityListener on authentication (#3074) * [JENKINS-27026] Notify the SecurityListener in case of Token based authentication success - due the current version of the method, the UserDetails required for the event was not accessible. In order to stay with the same API in SecurityListener, two "protected" methods were created to split the job and let the UserDetails accessible * - add test to ensure the SecurityListener is called for REST Token but also for regular basic auth * - remove the comment about the split, will be put in GitHub comment instead * - add check for anonymous call instead of just putting a comment - remove the constructor in the dummy - add link to PR from Daniel to simplify a call * - separate the before/after to save one clear and be more explicit - put more meaning in the assertLastEventIs method by explicitly say we will remove the last event * - add comment about why we do not fire the "failedToAuthenticated" in the case of an invalid token (tips: it's because it could be a valid password) * - also add the authenticated trigger on legacy filter as pointed by Ivan * - add support of event on CLI remoting authentication - adjust tests by moving the helper class used to spy on events * - as mentioned Yvan, the code had some problems with null checking, so the approach is changed in order to encapsulate all that internal mechanism * - add javadoc - open the getUserDetailsForImpersonation from the User (will let the SSHD module to retrieve UserDetails from that) * - remove single quote in log messages * - basic corrections requested by Jesse * - just another typo * - adjust the javadoc for SecurityListener events * - add the link to Jenkins#Anonymous * - add link (not using see) * - update comment on the isAnonymous as we (me + Oleg) do not find a best place at the moment * - put the new method isAnonymous in ACL instead of Functions * - little typo - add requirement about the SecurityContext authentication --- core/src/main/java/hudson/Functions.java | 9 +- core/src/main/java/hudson/cli/CLICommand.java | 28 ++++- .../hudson/cli/ClientAuthenticationCache.java | 5 +- .../main/java/hudson/cli/LoginCommand.java | 3 + .../main/java/hudson/cli/LogoutCommand.java | 9 ++ core/src/main/java/hudson/model/User.java | 61 ++++++++-- core/src/main/java/hudson/security/ACL.java | 10 ++ .../security/BasicAuthenticationFilter.java | 10 +- .../BasicHeaderApiTokenAuthenticator.java | 12 +- .../jenkins/security/SecurityListener.java | 17 +-- .../security/CliAuthenticationTest.java | 112 ++++++++++++++++- .../security/BasicHeaderProcessorTest.java | 52 ++++++-- .../jenkins/security/SpySecurityListener.java | 115 ++++++++++++++++++ 13 files changed, 403 insertions(+), 40 deletions(-) create mode 100644 test/src/test/java/jenkins/security/SpySecurityListener.java diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index c9c5e8545b..bf8aca4e47 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -26,6 +26,7 @@ package hudson; import hudson.model.Slave; +import hudson.security.*; import jenkins.util.SystemProperties; import hudson.cli.CLICommand; import hudson.console.ConsoleAnnotationDescriptor; @@ -56,11 +57,6 @@ import hudson.model.View; import hudson.scm.SCM; import hudson.scm.SCMDescriptor; import hudson.search.SearchableModelObject; -import hudson.security.AccessControlled; -import hudson.security.AuthorizationStrategy; -import hudson.security.GlobalSecurityConfiguration; -import hudson.security.Permission; -import hudson.security.SecurityRealm; import hudson.security.captcha.CaptchaSupport; import hudson.security.csrf.CrumbIssuer; import hudson.slaves.Cloud; @@ -137,6 +133,7 @@ import jenkins.model.Jenkins; import jenkins.model.ModelObjectWithChildren; import jenkins.model.ModelObjectWithContextMenu; +import org.acegisecurity.Authentication; import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; import org.apache.commons.jelly.JellyContext; import org.apache.commons.jelly.JellyTagException; @@ -1582,7 +1579,7 @@ public class Functions { * Checks if the current user is anonymous. */ public static boolean isAnonymous() { - return Jenkins.getAuthentication() instanceof AnonymousAuthenticationToken; + return ACL.isAnonymous(Jenkins.getAuthentication()); } /** diff --git a/core/src/main/java/hudson/cli/CLICommand.java b/core/src/main/java/hudson/cli/CLICommand.java index d507662037..5efbaf3a33 100644 --- a/core/src/main/java/hudson/cli/CLICommand.java +++ b/core/src/main/java/hudson/cli/CLICommand.java @@ -30,6 +30,8 @@ import hudson.ExtensionPoint; import hudson.cli.declarative.CLIMethod; import hudson.ExtensionPoint.LegacyInstancesAreScopedToHudson; import hudson.Functions; +import hudson.security.ACL; +import jenkins.security.SecurityListener; import jenkins.util.SystemProperties; import hudson.cli.declarative.OptionHandlerExtension; import jenkins.model.Jenkins; @@ -44,6 +46,8 @@ import org.acegisecurity.Authentication; import org.acegisecurity.BadCredentialsException; import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.userdetails.User; +import org.acegisecurity.userdetails.UserDetails; import org.apache.commons.discovery.ResourceClassIterator; import org.apache.commons.discovery.ResourceNameIterator; import org.apache.commons.discovery.resource.ClassLoaders; @@ -344,8 +348,16 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { @Deprecated protected Authentication loadStoredAuthentication() throws InterruptedException { try { - if (channel!=null) - return new ClientAuthenticationCache(channel).get(); + if (channel!=null){ + Authentication authLoadedFromCache = new ClientAuthenticationCache(channel).get(); + + if(!ACL.isAnonymous(authLoadedFromCache)){ + UserDetails userDetails = new CLIUserDetails(authLoadedFromCache); + SecurityListener.fireAuthenticated(userDetails); + } + + return authLoadedFromCache; + } } catch (IOException e) { stderr.println("Failed to access the stored credential"); Functions.printStackTrace(e, stderr); // recover @@ -642,4 +654,16 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { } } } + + /** + * User details loaded from the CLI {@link ClientAuthenticationCache} + * The user is never anonymous since it must be authenticated to be stored in the cache + */ + @Deprecated + @Restricted(NoExternalUse.class) + private static class CLIUserDetails extends User { + private CLIUserDetails(Authentication auth) { + super(auth.getName(), "", true, true, true, true, auth.getAuthorities()); + } + } } diff --git a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java b/core/src/main/java/hudson/cli/ClientAuthenticationCache.java index cf5b5c113c..48b96f4686 100644 --- a/core/src/main/java/hudson/cli/ClientAuthenticationCache.java +++ b/core/src/main/java/hudson/cli/ClientAuthenticationCache.java @@ -22,6 +22,8 @@ import java.util.logging.Level; import java.util.logging.Logger; import jenkins.security.HMACConfidentialKey; +import javax.annotation.Nonnull; + /** * Represents the authentication credential store of the CLI client. * @@ -73,7 +75,7 @@ public class ClientAuthenticationCache implements Serializable { * * @return {@link jenkins.model.Jenkins#ANONYMOUS} if no such credential is found, or if the stored credential is invalid. */ - public Authentication get() { + public @Nonnull Authentication get() { Jenkins h = Jenkins.getActiveInstance(); String val = props.getProperty(getPropertyKey()); if (val == null) { @@ -100,6 +102,7 @@ public class ClientAuthenticationCache implements Serializable { LOGGER.log(Level.FINER, "Loaded stored CLI authentication for {0}", username); return new UsernamePasswordAuthenticationToken(u.getUsername(), "", u.getAuthorities()); } catch (AuthenticationException | DataAccessException e) { + //TODO there is no check to be consistent with User.ALLOW_NON_EXISTENT_USER_TO_LOGIN LOGGER.log(Level.FINE, "Stored CLI authentication did not correspond to a valid user: " + username, e); return Jenkins.ANONYMOUS; } diff --git a/core/src/main/java/hudson/cli/LoginCommand.java b/core/src/main/java/hudson/cli/LoginCommand.java index 8920ad4111..55efd9dbcb 100644 --- a/core/src/main/java/hudson/cli/LoginCommand.java +++ b/core/src/main/java/hudson/cli/LoginCommand.java @@ -3,6 +3,7 @@ package hudson.cli; import hudson.Extension; import java.io.PrintStream; import jenkins.model.Jenkins; +import jenkins.security.SecurityListener; import org.acegisecurity.Authentication; import org.kohsuke.args4j.CmdLineException; @@ -45,6 +46,8 @@ public class LoginCommand extends CLICommand { ClientAuthenticationCache store = new ClientAuthenticationCache(checkChannel()); store.set(a); + SecurityListener.fireLoggedIn(a.getName()); + return 0; } diff --git a/core/src/main/java/hudson/cli/LogoutCommand.java b/core/src/main/java/hudson/cli/LogoutCommand.java index 822c99d84e..8578a6eb04 100644 --- a/core/src/main/java/hudson/cli/LogoutCommand.java +++ b/core/src/main/java/hudson/cli/LogoutCommand.java @@ -1,6 +1,9 @@ package hudson.cli; import hudson.Extension; +import jenkins.security.SecurityListener; +import org.acegisecurity.Authentication; + import java.io.PrintStream; /** @@ -27,7 +30,13 @@ public class LogoutCommand extends CLICommand { @Override protected int run() throws Exception { ClientAuthenticationCache store = new ClientAuthenticationCache(checkChannel()); + + Authentication auth = store.get(); + store.remove(); + + SecurityListener.fireLoggedOut(auth.getName()); + return 0; } } diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index d75ec1b227..418e49aa0d 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -326,23 +326,70 @@ public class User extends AbstractModelObject implements AccessControlled, Descr * @since 1.419 */ public @Nonnull Authentication impersonate() throws UsernameNotFoundException { + return this.impersonate(this.getUserDetailsForImpersonation()); + } + + /** + * This method checks with {@link SecurityRealm} if the user is a valid user that can login to the security realm. + * If {@link SecurityRealm} is a kind that does not support querying information about other users, this will + * use {@link LastGrantedAuthoritiesProperty} to pick up the granted authorities as of the last time the user has + * logged in. + * + * @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}. + */ + public @Nonnull UserDetails getUserDetailsForImpersonation() throws UsernameNotFoundException { + ImpersonatingUserDetailsService userDetailsService = new ImpersonatingUserDetailsService( + Jenkins.getInstance().getSecurityRealm().getSecurityComponents().userDetails + ); + try { - UserDetails u = new ImpersonatingUserDetailsService( - Jenkins.getInstance().getSecurityRealm().getSecurityComponents().userDetails).loadUserByUsername(id); - return new UsernamePasswordAuthenticationToken(u.getUsername(), "", u.getAuthorities()); + UserDetails userDetails = userDetailsService.loadUserByUsername(id); + LOGGER.log(Level.FINE, "Impersonation of the user {0} was a success", new Object[]{ id }); + return userDetails; } catch (UserMayOrMayNotExistException e) { + LOGGER.log(Level.FINE, "The user {0} may or may not exist in the SecurityRealm, so we provide minimum access", new Object[]{ id }); // backend can't load information about other users. so use the stored information if available } catch (UsernameNotFoundException e) { // if the user no longer exists in the backend, we need to refuse impersonating this user - if (!ALLOW_NON_EXISTENT_USER_TO_LOGIN) + if(ALLOW_NON_EXISTENT_USER_TO_LOGIN){ + LOGGER.log(Level.FINE, "The user {0} was not found in the SecurityRealm but we are required to let it pass, due to ALLOW_NON_EXISTENT_USER_TO_LOGIN", new Object[]{ id }); + }else{ + LOGGER.log(Level.FINE, "The user {0} was not found in the SecurityRealm", new Object[]{ 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); + } + + /** + * 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 LegitimateButUnknownUserDetails(String username) throws IllegalArgumentException { + super( + username, "", + true, true, true, true, + new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY} + ); } + } - // seems like a legitimate user we have no idea about. proceed with minimum access - return new UsernamePasswordAuthenticationToken(id, "", - new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY}); + /** + * Creates an {@link Authentication} object that represents this user using the given userDetails + * + * @param userDetails Provided by {@link #getUserDetailsForImpersonation()}. + * @see #getUserDetailsForImpersonation() + */ + @Restricted(NoExternalUse.class) + public @Nonnull Authentication impersonate(@Nonnull UserDetails userDetails) { + return new UsernamePasswordAuthenticationToken(userDetails.getUsername(), "", userDetails.getAuthorities()); } /** diff --git a/core/src/main/java/hudson/security/ACL.java b/core/src/main/java/hudson/security/ACL.java index 0efe770050..41f8b5a9bc 100644 --- a/core/src/main/java/hudson/security/ACL.java +++ b/core/src/main/java/hudson/security/ACL.java @@ -44,6 +44,7 @@ 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; @@ -319,4 +320,13 @@ public abstract class ACL { return as(user == null ? Jenkins.ANONYMOUS : user.impersonate()); } + /** + * Checks if the given authentication is anonymous by checking its class. + * @see Jenkins#ANONYMOUS + * @see AnonymousAuthenticationToken + */ + public static boolean isAnonymous(@Nonnull Authentication authentication) { + //TODO use AuthenticationTrustResolver instead to be consistent through the application + return authentication instanceof AnonymousAuthenticationToken; + } } diff --git a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java index 11709bcf58..1ec0be214f 100644 --- a/core/src/main/java/hudson/security/BasicAuthenticationFilter.java +++ b/core/src/main/java/hudson/security/BasicAuthenticationFilter.java @@ -27,7 +27,10 @@ import hudson.model.User; import jenkins.model.Jenkins; import hudson.util.Scrambler; import jenkins.security.ApiTokenProperty; +import jenkins.security.SecurityListener; +import org.acegisecurity.Authentication; import org.acegisecurity.context.SecurityContextHolder; +import org.acegisecurity.userdetails.UserDetails; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -138,7 +141,12 @@ public class BasicAuthenticationFilter implements Filter { User u = User.getById(username, true); ApiTokenProperty t = u.getProperty(ApiTokenProperty.class); if (t!=null && t.matchesPassword(password)) { - SecurityContextHolder.getContext().setAuthentication(u.impersonate()); + UserDetails userDetails = u.getUserDetailsForImpersonation(); + Authentication auth = u.impersonate(userDetails); + + SecurityListener.fireAuthenticated(userDetails); + + SecurityContextHolder.getContext().setAuthentication(auth); try { chain.doFilter(request,response); } finally { diff --git a/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java b/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java index a192dddc5f..f0f6491e22 100644 --- a/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java +++ b/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java @@ -3,6 +3,7 @@ package jenkins.security; 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; @@ -21,6 +22,10 @@ import static java.util.logging.Level.*; */ @Extension public class BasicHeaderApiTokenAuthenticator extends BasicHeaderAuthenticator { + /** + * Note: if the token does not exist or does not match, we do not use {@link SecurityListener#fireFailedToAuthenticate(String)} + * 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 { // attempt to authenticate as API token @@ -28,7 +33,12 @@ public class BasicHeaderApiTokenAuthenticator extends BasicHeaderAuthenticator { ApiTokenProperty t = u.getProperty(ApiTokenProperty.class); if (t!=null && t.matchesPassword(password)) { try { - return u.impersonate(); + UserDetails userDetails = u.getUserDetailsForImpersonation(); + Authentication auth = u.impersonate(userDetails); + + SecurityListener.fireAuthenticated(userDetails); + + return auth; } 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. diff --git a/core/src/main/java/jenkins/security/SecurityListener.java b/core/src/main/java/jenkins/security/SecurityListener.java index f8c8ca6604..134e992e7f 100644 --- a/core/src/main/java/jenkins/security/SecurityListener.java +++ b/core/src/main/java/jenkins/security/SecurityListener.java @@ -45,29 +45,32 @@ public abstract class SecurityListener implements ExtensionPoint { private static final Logger LOGGER = Logger.getLogger(SecurityListener.class.getName()); /** - * Fired when a user was successfully authenticated by password. - * This might be via the web UI, or via REST (not with an API token) or CLI (not with an SSH key). - * Only {@link AbstractPasswordBasedSecurityRealm}s are considered. - * @param details details of the newly authenticated user, such as name and groups + * Fired when a user was successfully authenticated using credentials. It could be password or any other credentials. + * 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. */ protected void authenticated(@Nonnull UserDetails details){} /** - * Fired when a user tried to authenticate by password but failed. + * 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 */ protected void failedToAuthenticate(@Nonnull String username){} /** - * Fired when a user has logged in via the web UI. + * 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. * @param username the user */ protected void loggedIn(@Nonnull String username){} /** - * Fired when a user has failed to log in via the web UI. + * Fired when a user has failed to log in. * Would be called after {@link #failedToAuthenticate}. * @param username the user */ diff --git a/test/src/test/java/hudson/security/CliAuthenticationTest.java b/test/src/test/java/hudson/security/CliAuthenticationTest.java index bdddf14407..c02d085586 100644 --- a/test/src/test/java/hudson/security/CliAuthenticationTest.java +++ b/test/src/test/java/hudson/security/CliAuthenticationTest.java @@ -1,24 +1,34 @@ package hudson.security; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; - +import hudson.ExtensionList; import hudson.cli.CLI; import hudson.cli.CLICommand; import jenkins.model.Jenkins; +import jenkins.security.SecurityListener; +import jenkins.security.SpySecurityListener; import org.acegisecurity.Authentication; +import org.acegisecurity.AuthenticationException; +import org.acegisecurity.BadCredentialsException; +import org.acegisecurity.GrantedAuthority; +import org.acegisecurity.userdetails.User; +import org.acegisecurity.userdetails.UserDetails; +import org.acegisecurity.userdetails.UsernameNotFoundException; import org.apache.commons.io.input.NullInputStream; +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.For; +import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.TestExtension; +import org.springframework.dao.DataAccessException; import java.io.ByteArrayOutputStream; import java.util.Arrays; +import static org.junit.Assert.*; + /** * @author Kohsuke Kawaguchi */ @@ -27,6 +37,14 @@ public class CliAuthenticationTest { @Rule public JenkinsRule j = new JenkinsRule(); + + private SpySecurityListener spySecurityListener; + + @Before + public void prepareListeners(){ + //TODO simplify using #3021 into ExtensionList.lookupSingleton(SpySecurityListener.class) + this.spySecurityListener = ExtensionList.lookup(SecurityListener.class).get(SpySecurityListenerImpl.class); + } @Test public void test() throws Exception { @@ -40,6 +58,10 @@ public class CliAuthenticationTest { assertEquals(0, command(args)); } + private void unsuccessfulCommand(String... args) throws Exception { + assertNotEquals(0, command(args)); + } + private int command(String... args) throws Exception { try (CLI cli = new CLI(j.getURL())) { return cli.execute(args); @@ -91,9 +113,86 @@ public class CliAuthenticationTest { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); successfulCommand("login","--username","abc","--password","abc"); + spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(userDetails -> userDetails.getUsername().equals("abc")); + spySecurityListener.loggedInCalls.assertLastEventIsAndThenRemoveIt("abc"); + successfulCommand("test"); // now we can run without an explicit credential + spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(userDetails -> userDetails.getUsername().equals("abc")); + spySecurityListener.loggedInCalls.assertNoNewEvents(); + successfulCommand("logout"); + spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(userDetails -> userDetails.getUsername().equals("abc")); + spySecurityListener.loggedOutCalls.assertLastEventIsAndThenRemoveIt("abc"); + successfulCommand("anonymous"); // now we should run as anonymous + spySecurityListener.authenticatedCalls.assertNoNewEvents(); + spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents(); + spySecurityListener.loggedInCalls.assertNoNewEvents(); + spySecurityListener.loggedOutCalls.assertNoNewEvents(); + } + + @Test + @Issue("JENKINS-27026") + public void loginAsALegitimateUserButUnknown() throws Exception { + j.jenkins.setSecurityRealm(new MockSecurityRealm()); + + String username = "alice"; + + unsuccessfulCommand("login","--username",username,"--password","badCredentials"); + spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt(username); + spySecurityListener.failedToLogInCalls.assertNoNewEvents(); + spySecurityListener.authenticatedCalls.assertNoNewEvents(); + spySecurityListener.loggedInCalls.assertNoNewEvents(); +// spySecurityListener.failedToLogInCalls.assertLastEventIsAndThenRemoveIt(username); + + unsuccessfulCommand("login","--username",username,"--password","usernameNotFound"); + spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt(username); + spySecurityListener.failedToLogInCalls.assertNoNewEvents(); + spySecurityListener.authenticatedCalls.assertNoNewEvents(); + spySecurityListener.loggedInCalls.assertNoNewEvents(); +// spySecurityListener.failedToLogInCalls.assertLastEventIsAndThenRemoveIt(username); + + unsuccessfulCommand("login","--username",username,"--password","mayOrMayNotExist"); + spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt(username); + spySecurityListener.failedToLogInCalls.assertNoNewEvents(); + spySecurityListener.authenticatedCalls.assertNoNewEvents(); + spySecurityListener.loggedInCalls.assertNoNewEvents(); +// spySecurityListener.failedToLogInCalls.assertLastEventIsAndThenRemoveIt(username); + + // in case of authentication that throw a DataAccessException (see impersonatingUserDetailsService) + // the CLI login command does not work as expected + unsuccessfulCommand("login","--username",username,"--password","dataAccess"); + spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents(); + spySecurityListener.failedToLogInCalls.assertNoNewEvents(); + spySecurityListener.authenticatedCalls.assertNoNewEvents(); + spySecurityListener.loggedInCalls.assertNoNewEvents(); +// spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt(username); +// spySecurityListener.failedToLogInCalls.assertLastEventIsAndThenRemoveIt(username); + } + + private static class MockSecurityRealm extends AbstractPasswordBasedSecurityRealm { + @Override protected UserDetails authenticate(String username, String password) throws AuthenticationException { + switch(password){ + case "badCredentials": throw new BadCredentialsException("BadCredentials requested"); + case "usernameNotFound": throw new UsernameNotFoundException("UsernameNotFound requested"); + case "mayOrMayNotExist": throw new UserMayOrMayNotExistException("MayOrMayNotExist requested"); + case "dataAccess": throw new DataAccessException("DataAccess requested"){}; + default: + return new User( + username, password, + true, true, true, + new GrantedAuthority[]{SecurityRealm.AUTHENTICATED_AUTHORITY} + ); + } + } + + @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + return null; + } + + @Override public GroupDetails loadGroupByGroupname(String groupname) throws UsernameNotFoundException, DataAccessException { + return null; + } } /** @@ -112,4 +211,7 @@ public class CliAuthenticationTest { assertTrue(out1.contains("Bad Credentials. Search the server log for")); assertTrue(out2.contains("Bad Credentials. Search the server log for")); } + + @TestExtension + public static class SpySecurityListenerImpl extends SpySecurityListener {} } diff --git a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java index 449293ae10..1c7c32d43f 100644 --- a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java +++ b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java @@ -1,15 +1,15 @@ package jenkins.security; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.WebRequest; +import hudson.ExtensionList; import hudson.model.UnprotectedRootAction; import hudson.model.User; import hudson.util.HttpResponses; import hudson.util.Scrambler; +import org.junit.After; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -21,6 +21,8 @@ import org.xml.sax.SAXException; import java.io.IOException; import java.net.URL; +import static org.junit.Assert.*; + /** * @author Kohsuke Kawaguchi */ @@ -30,6 +32,14 @@ public class BasicHeaderProcessorTest { private WebClient wc; + private SpySecurityListener spySecurityListener; + + @Before + public void prepareListeners(){ + //TODO simplify using #3021 into ExtensionList.lookupSingleton(SpySecurityListener.class) + this.spySecurityListener = ExtensionList.lookup(SecurityListener.class).get(SpySecurityListenerImpl.class); + } + /** * Tests various ways to send the Basic auth. */ @@ -43,39 +53,54 @@ public class BasicHeaderProcessorTest { // call without authentication makeRequestWithAuthAndVerify(null, "anonymous"); + spySecurityListener.authenticatedCalls.assertNoNewEvents(); + spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents(); // call with API token ApiTokenProperty t = foo.getProperty(ApiTokenProperty.class); final String token = t.getApiToken(); makeRequestWithAuthAndVerify("foo:"+token, "foo"); + //TODO verify why there are two events "authenticated" that are triggered + // the whole authentication process seems to be done twice + spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo")); // call with invalid API token makeRequestAndFail("foo:abcd"+token); + spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo"); // call with password makeRequestWithAuthAndVerify("foo:foo", "foo"); + spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo")); // call with incorrect password makeRequestAndFail("foo:bar"); + spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo"); wc.login("bar"); + spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("bar")); + spySecurityListener.loggedInCalls.assertLastEventIsAndThenRemoveIt("bar"); // if the session cookie is valid, then basic header won't be needed makeRequestWithAuthAndVerify(null, "bar"); + spySecurityListener.authenticatedCalls.assertNoNewEvents(); + spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents(); // if the session cookie is valid, and basic header is set anyway login should not fail either makeRequestWithAuthAndVerify("bar:bar", "bar"); + spySecurityListener.authenticatedCalls.assertNoNewEvents(); + spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents(); // but if the password is incorrect, it should fail, instead of silently logging in as the user indicated by session makeRequestAndFail("foo:bar"); + spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo"); } private void makeRequestAndFail(String userAndPass) throws IOException, SAXException { - makeRequestWithAuthCodeAndFail(encrypt("Basic", userAndPass)); + makeRequestWithAuthCodeAndFail(encode("Basic", userAndPass)); } - private String encrypt(String prefix, String userAndPass) { + private String encode(String prefix, String userAndPass) { if (userAndPass==null) { return null; } @@ -92,7 +117,7 @@ public class BasicHeaderProcessorTest { } private void makeRequestWithAuthAndVerify(String userAndPass, String username) throws IOException, SAXException { - makeRequestWithAuthCodeAndVerify(encrypt("Basic", userAndPass), username); + makeRequestWithAuthCodeAndVerify(encode("Basic", userAndPass), username); } private void makeRequestWithAuthCodeAndVerify(String authCode, String expected) throws IOException, SAXException { @@ -116,20 +141,24 @@ public class BasicHeaderProcessorTest { // call with API token ApiTokenProperty t = foo.getProperty(ApiTokenProperty.class); final String token = t.getApiToken(); - String authCode1 = encrypt(prefix,"foo:"+token); + String authCode1 = encode(prefix,"foo:"+token); makeRequestWithAuthCodeAndVerify(authCode1, "foo"); + spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo")); // call with invalid API token - String authCode2 = encrypt(prefix,"foo:abcd"+token); + String authCode2 = encode(prefix,"foo:abcd"+token); makeRequestWithAuthCodeAndFail(authCode2); + spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo"); // call with password - String authCode3 = encrypt(prefix,"foo:foo"); + String authCode3 = encode(prefix,"foo:foo"); makeRequestWithAuthCodeAndVerify(authCode3, "foo"); + spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo")); // call with incorrect password - String authCode4 = encrypt(prefix,"foo:bar"); + String authCode4 = encode(prefix,"foo:bar"); makeRequestWithAuthCodeAndFail(authCode4); + spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo"); } } @@ -155,4 +184,7 @@ public class BasicHeaderProcessorTest { return HttpResponses.plainText(u!=null ? u.getId() : "anonymous"); } } + + @TestExtension + public static class SpySecurityListenerImpl extends SpySecurityListener {} } diff --git a/test/src/test/java/jenkins/security/SpySecurityListener.java b/test/src/test/java/jenkins/security/SpySecurityListener.java new file mode 100644 index 0000000000..0d9731cc90 --- /dev/null +++ b/test/src/test/java/jenkins/security/SpySecurityListener.java @@ -0,0 +1,115 @@ +/* + * The MIT License + * + * Copyright (c) 2017, 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 org.acegisecurity.userdetails.UserDetails; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +//TODO temporary solution, should be moved to Jenkins Test Harness project +/** + * Mean to be included in test classes to provide a way to spy on the SecurityListener events + * @see jenkins.security.BasicHeaderProcessorTest.SpySecurityListenerImpl + * @see hudson.security.CliAuthenticationTest.SpySecurityListenerImpl + */ +public abstract class SpySecurityListener extends SecurityListener { + public final EventQueue authenticatedCalls = new EventQueue<>(); + public final EventQueue failedToAuthenticateCalls = new EventQueue<>(); + public final EventQueue loggedInCalls = new EventQueue<>(); + public final EventQueue failedToLogInCalls = new EventQueue<>(); + public final EventQueue loggedOutCalls = new EventQueue<>(); + + public void clearPreviousCalls(){ + this.authenticatedCalls.clear(); + this.failedToAuthenticateCalls.clear(); + this.loggedInCalls.clear(); + this.failedToLogInCalls.clear(); + this.loggedOutCalls.clear(); + } + + @Override + protected void authenticated(@Nonnull UserDetails details) { + this.authenticatedCalls.add(details); + } + + @Override + protected void failedToAuthenticate(@Nonnull String username) { + this.failedToAuthenticateCalls.add(username); + } + + @Override + protected void loggedIn(@Nonnull String username) { + this.loggedInCalls.add(username); + } + + @Override + protected void failedToLogIn(@Nonnull String username) { + this.failedToLogInCalls.add(username); + } + + @Override + protected void loggedOut(@Nonnull String username) { + this.loggedOutCalls.add(username); + + } + + public static class EventQueue { + private final List eventList = new ArrayList<>(); + + private EventQueue add(T t){ + eventList.add(t); + return this; + } + + public void assertLastEventIsAndThenRemoveIt(T expected){ + assertLastEventIsAndThenRemoveIt(expected::equals); + } + + public void assertLastEventIsAndThenRemoveIt(Predicate predicate){ + if(eventList.isEmpty()){ + fail("event list is empty"); + } + + T t = eventList.remove(eventList.size() - 1); + assertTrue(predicate.test(t)); + eventList.clear(); + } + + public void assertNoNewEvents(){ + assertEquals("list of event should be empty", eventList.size(), 0); + } + + public void clear(){ + eventList.clear(); + } + } +} + -- GitLab From 14605fea838947546da600faff9483cf2ba3f4eb Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 14 Dec 2017 20:45:24 +0100 Subject: [PATCH 0158/1380] Introduce the new UserIdCause constructor, which accepts userId as an argument. (#3162) * Introduce UserIdCause constructor, which accepts userId as an argument. * [JENKINS-48467] - UserIdCuase: Add issue link in the TODO comment --- core/src/main/java/hudson/model/Cause.java | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java index ebfb0fc310..7b16918f04 100644 --- a/core/src/main/java/hudson/model/Cause.java +++ b/core/src/main/java/hudson/model/Cause.java @@ -402,17 +402,36 @@ public abstract class Cause { */ public static class UserIdCause extends Cause { + @CheckForNull private String userId; + /** + * Constructor, which uses the current {@link User}. + */ public UserIdCause() { User user = User.current(); this.userId = (user == null) ? null : user.getId(); } + /** + * Constructor. + * @param userId User ID. {@code null} if the user is unknown. + * @since TODO + */ + public UserIdCause(@CheckForNull String userId) { + this.userId = userId; + } + @Exported(visibility = 3) + @CheckForNull public String getUserId() { return userId; } + + @Nonnull + private String getUserIdOrUnknown() { + return userId != null ? userId : User.getUnknown().getId(); + } @Exported(visibility = 3) public String getUserName() { @@ -435,8 +454,8 @@ public abstract class Cause { @Override public void print(TaskListener listener) { listener.getLogger().println(Messages.Cause_UserIdCause_ShortDescription( - // TODO better to use ModelHyperlinkNote.encodeTo(User), or User.getUrl, since it handles URL escaping - ModelHyperlinkNote.encodeTo("/user/"+getUserId(), getUserName()))); + // TODO JENKINS-48467 - better to use ModelHyperlinkNote.encodeTo(User), or User.getUrl, since it handles URL escaping + ModelHyperlinkNote.encodeTo("/user/"+getUserIdOrUnknown(), getUserName()))); } @Override -- GitLab From 09bcc5d6538b3cfffbf71228ebd1679e3e20d8b2 Mon Sep 17 00:00:00 2001 From: Krishan Bhasin Date: Thu, 14 Dec 2017 19:52:56 +0000 Subject: [PATCH 0159/1380] [JENKINS-47324] - Reduce usage of File.mkdirs() in FilePath and IOUtils (#3173) * Move an instance of renameTo() to Files.move() * Replace an instance of File.toURI() with an instance of Path.toUri() * Replace mkdirs() with Files.createDirectories() Replace mkdir() with Files.createTempDirectory() * Undo addition of createTempDirectory() as per review comments * Return to use of FilePath#mkdirs(File) and instead modify it to use the new API. * Undo addition of toPath() in a URI conversion as it brings no benefits. * Replace new uses of toPath() with Util.fileToPath() to pre-handle runtime exceptions * Remove * import. move mkdirs() to using FilePath method instead of File method. * Make IOUtils.mkdirs(File) use Java7 API calls * Add back accidentally removed imports. * Fixed use of wildcard import * Use utility method fileToPath() to handle potential exception thrown --- core/src/main/java/hudson/FilePath.java | 18 +++++++++++------- core/src/main/java/hudson/util/IOUtils.java | 19 ++++++------------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 197a27f60b..8d1233ab04 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -79,6 +79,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.InvalidPathException; +import java.nio.file.LinkOption; import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; @@ -125,8 +126,10 @@ import org.kohsuke.stapler.Stapler; import static hudson.FilePath.TarCompression.GZIP; import static hudson.Util.deleteFile; +import static hudson.Util.fileToPath; import static hudson.Util.fixEmpty; import static hudson.Util.isSymlink; + import java.util.Collections; /** @@ -1165,7 +1168,7 @@ public final class FilePath implements Serializable { // following Ant task to avoid possible race condition. Thread.sleep(10); - return f.mkdirs() || f.exists(); + return mkdirs(f) || f.exists(); } })) throw new IOException("Failed to mkdirs: "+remote); @@ -1615,7 +1618,7 @@ public final class FilePath implements Serializable { if (Util.NATIVE_CHMOD_MODE) { PosixAPI.jnr().chmod(f.getAbsolutePath(), mask); } else { - Files.setPosixFilePermissions(Util.fileToPath(f), Util.modeToPermissions(mask)); + Files.setPosixFilePermissions(fileToPath(f), Util.modeToPermissions(mask)); } } @@ -1962,7 +1965,7 @@ public final class FilePath implements Serializable { act(new SecureFileCallable() { private static final long serialVersionUID = 1L; public Void invoke(File f, VirtualChannel channel) throws IOException { - reading(f).renameTo(creating(new File(target.remote))); + Files.move(fileToPath(reading(f)), fileToPath(creating(new File(target.remote))), LinkOption.NOFOLLOW_LINKS); return null; } }); @@ -2024,8 +2027,8 @@ public final class FilePath implements Serializable { File targetFile = new File(target.remote); File targetDir = targetFile.getParentFile(); filterNonNull().mkdirs(targetDir); - Files.createDirectories(Util.fileToPath(targetDir)); - Files.copy(Util.fileToPath(reading(f)), Util.fileToPath(writing(targetFile)), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); + Files.createDirectories(fileToPath(targetDir)); + Files.copy(fileToPath(reading(f)), fileToPath(writing(targetFile)), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING); return null; } }); @@ -2957,11 +2960,12 @@ public final class FilePath implements Serializable { return f; } - private boolean mkdirs(File dir) { + private boolean mkdirs(File dir) throws IOException { if (dir.exists()) return false; filterNonNull().mkdirs(dir); - return dir.mkdirs(); + Files.createDirectories(fileToPath(dir)); + return true; } private File mkdirsE(File dir) throws IOException { diff --git a/core/src/main/java/hudson/util/IOUtils.java b/core/src/main/java/hudson/util/IOUtils.java index 4168379405..5022dc021f 100644 --- a/core/src/main/java/hudson/util/IOUtils.java +++ b/core/src/main/java/hudson/util/IOUtils.java @@ -13,6 +13,8 @@ import java.util.Collection; import java.util.List; import java.util.regex.Pattern; +import static hudson.Util.fileToPath; + /** * Adds more to commons-io. * @@ -51,20 +53,11 @@ public class IOUtils { * This method returns the 'dir' parameter so that the method call flows better. */ public static File mkdirs(File dir) throws IOException { - if(dir.mkdirs() || dir.exists()) - return dir; - - // following Ant task to avoid possible race condition. try { - Thread.sleep(10); - } catch (InterruptedException e) { - // ignore + return Files.createDirectories(fileToPath(dir)).toFile(); + } catch (UnsupportedOperationException e) { + throw new IOException(e); } - - if (dir.mkdirs() || dir.exists()) - return dir; - - throw new IOException("Failed to create a directory at "+dir); } /** @@ -134,7 +127,7 @@ public class IOUtils { if (Util.NATIVE_CHMOD_MODE) { return PosixAPI.jnr().stat(f.getPath()).mode(); } else { - return Util.permissionsToMode(Files.getPosixFilePermissions(Util.fileToPath(f))); + return Util.permissionsToMode(Files.getPosixFilePermissions(fileToPath(f))); } } catch (IOException cause) { PosixException e = new PosixException("Unable to get file permissions", null); -- GitLab From eb7582a53deed2cac2b0e986417f0dae311ee52b Mon Sep 17 00:00:00 2001 From: surenpi Date: Fri, 15 Dec 2017 21:07:16 +0800 Subject: [PATCH 0160/1380] Add Chinese translation for slave agent configure --- .../model/Computer/configure_zh_CN.properties | 25 +++++++++++ .../index_zh_CN.properties | 14 +++--- .../configure-entries_zh_CN.properties | 27 ++++++++++- .../master-slave/availability_zh_CN.html | 45 +++++++++++++++++++ .../master-slave/demand/idleDelay_zh_CN.html | 3 ++ .../demand/inDemandDelay_zh_CN.html | 3 ++ .../master-slave/description_zh_CN.html | 5 +++ 7 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 core/src/main/resources/hudson/model/Computer/configure_zh_CN.properties create mode 100644 war/src/main/webapp/help/system-config/master-slave/availability_zh_CN.html create mode 100644 war/src/main/webapp/help/system-config/master-slave/demand/idleDelay_zh_CN.html create mode 100644 war/src/main/webapp/help/system-config/master-slave/demand/inDemandDelay_zh_CN.html create mode 100644 war/src/main/webapp/help/system-config/master-slave/description_zh_CN.html diff --git a/core/src/main/resources/hudson/model/Computer/configure_zh_CN.properties b/core/src/main/resources/hudson/model/Computer/configure_zh_CN.properties new file mode 100644 index 0000000000..b76bbc325a --- /dev/null +++ b/core/src/main/resources/hudson/model/Computer/configure_zh_CN.properties @@ -0,0 +1,25 @@ +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. + +title={0} \u8BBE\u7F6E +Name=\u540D\u79F0 +Save=\u4FDD\u5B58 diff --git a/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index_zh_CN.properties b/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index_zh_CN.properties index a4a5ce68dd..d8ee8ee3c0 100644 --- a/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index_zh_CN.properties +++ b/core/src/main/resources/hudson/security/GlobalSecurityConfiguration/index_zh_CN.properties @@ -21,9 +21,13 @@ # THE SOFTWARE. LOADING=\u52A0\u8F7D\u4E2D -Enable\ security=\u542f\u7528\u5b89\u5168 +Enable\ security=\u542F\u7528\u5B89\u5168 Markup\ Formatter= -Access\ Control=\u8bbf\u95ee\u63a7\u5236 -Security\ Realm=\u5b89\u5168\u57df -Authorization=\u6388\u6743\u7b56\u7565 -Save=\u4fdd\u5b58 + +Agents=\u4EE3\u7406 +Agent\ protocols=\u4EE3\u7406\u534F\u8BAE + +Access\ Control=\u8BBF\u95EE\u63A7\u5236 +Security\ Realm=\u5B89\u5168\u57DF +Authorization=\u6388\u6743\u7B56\u7565 +Save=\u4FDD\u5B58 diff --git a/core/src/main/resources/hudson/slaves/DumbSlave/configure-entries_zh_CN.properties b/core/src/main/resources/hudson/slaves/DumbSlave/configure-entries_zh_CN.properties index 2c6ac8d99d..ec8eb777e3 100644 --- a/core/src/main/resources/hudson/slaves/DumbSlave/configure-entries_zh_CN.properties +++ b/core/src/main/resources/hudson/slaves/DumbSlave/configure-entries_zh_CN.properties @@ -1,6 +1,29 @@ -# This file is under the MIT License by authors +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. Description=\u63CF\u8FF0 +\#\ of\ executors=\u5E76\u53D1\u6784\u5EFA\u6570 Labels=\u6807\u7B7E -Launch\ method=\u542F\u52A8\u65B9\u6CD5 Remote\ root\ directory=\u8FDC\u7A0B\u5DE5\u4F5C\u76EE\u5F55 +Launch\ method=\u542F\u52A8\u65B9\u5F0F +Availability=\u53EF\u7528\u6027 +Node\ Properties=\u8282\u70B9\u5C5E\u6027 diff --git a/war/src/main/webapp/help/system-config/master-slave/availability_zh_CN.html b/war/src/main/webapp/help/system-config/master-slave/availability_zh_CN.html new file mode 100644 index 0000000000..9124339933 --- /dev/null +++ b/war/src/main/webapp/help/system-config/master-slave/availability_zh_CN.html @@ -0,0 +1,45 @@ +

+ 决定Jenkins的启动和停止。 + +
+
+ 尽可能保持代理在线 +
+
+ 该模式下,Jenkins会尽可能让代理保持在线。 +

+ 如果该代理由于临时性网络故障,Jenkins会定期尝试重启它。 +

+ +
+ 让代理在特定的时间段内在线或者离线 +
+
+ 该模式下,Jenkins会根据一个计划表来启动代理,并保持指定的时长。 +

+ 如果在计划周期内代理掉线,Jenkins会定期尝试重启它。 +

+ 当代理在线时间达到字段计划启动的时间,它将会被下线。 +
+ 如果勾选了当有构建时保持在线,并且根据计划表应该下线,Jenkins会等所有的 + 构建任务完成后再下线。 +

+ +
+ 当代理被需要时保持在线,空闲时离线 +
+
+ 该模式下,当代理被需要时Jenkins将会让代理上线,例如有排队的构建任务满足下列条件: +
    +
  • 在队列中排队时间达到需求延迟时间限制
  • +
  • 被构建任务指定(例如有一个匹配的标签表达式)
  • +
+ + 如果发生下述情况,代理将会被下线: +
    +
  • 代理没有需要构建的任务
  • +
  • 该代理已经空闲了指定的时间
  • +
+
+
+
diff --git a/war/src/main/webapp/help/system-config/master-slave/demand/idleDelay_zh_CN.html b/war/src/main/webapp/help/system-config/master-slave/demand/idleDelay_zh_CN.html new file mode 100644 index 0000000000..ab5063cc60 --- /dev/null +++ b/war/src/main/webapp/help/system-config/master-slave/demand/idleDelay_zh_CN.html @@ -0,0 +1,3 @@ +
+ 在Jenkins会把代理下线之前必须保持的空闲分钟数。 +
diff --git a/war/src/main/webapp/help/system-config/master-slave/demand/inDemandDelay_zh_CN.html b/war/src/main/webapp/help/system-config/master-slave/demand/inDemandDelay_zh_CN.html new file mode 100644 index 0000000000..ac944c565e --- /dev/null +++ b/war/src/main/webapp/help/system-config/master-slave/demand/inDemandDelay_zh_CN.html @@ -0,0 +1,3 @@ +
+ Jenkins把代理上线之前必须在队列中等待的分钟数。 +
diff --git a/war/src/main/webapp/help/system-config/master-slave/description_zh_CN.html b/war/src/main/webapp/help/system-config/master-slave/description_zh_CN.html new file mode 100644 index 0000000000..9b60348206 --- /dev/null +++ b/war/src/main/webapp/help/system-config/master-slave/description_zh_CN.html @@ -0,0 +1,5 @@ +
+ 使得该代理更加容易被识别的可选项。 +

+ 可以包含的信息有:CPU的核数、内存大小以及它的物理位置等。 +

\ No newline at end of file -- GitLab From dce450e1b4565b682f321329caea080293ef0b9b Mon Sep 17 00:00:00 2001 From: Johno Crawford Date: Fri, 15 Dec 2017 15:04:55 +0100 Subject: [PATCH 0161/1380] Invoke optimistic before computeIfAbsent to avoid contention. --- core/src/main/java/jenkins/model/Jenkins.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 62e611b441..b69d04b516 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -2595,7 +2595,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve */ @SuppressWarnings({"unchecked"}) public ExtensionList getExtensionList(Class extensionType) { - return extensionLists.computeIfAbsent(extensionType, key -> ExtensionList.create(this, key)); + ExtensionList extensionList = extensionLists.get(extensionType); + return extensionList != null ? extensionList : extensionLists.computeIfAbsent(extensionType, key -> ExtensionList.create(this, key)); } /** -- GitLab From 3c695a3ed6836abce19c0c71eeca418f0fe9fd66 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Fri, 15 Dec 2017 23:11:30 +0100 Subject: [PATCH 0162/1380] [JENKINS-34254] Use released Stapler 1.254 --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index 2a06499268..ecbdfc4919 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -39,7 +39,7 @@ THE SOFTWARE. true - 1.254-SNAPSHOT + 1.254 2.5.6.SEC03 2.4.11 -- GitLab From f0efdbab087ea26342a034da198d055bd7141b8a Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Fri, 15 Dec 2017 23:43:01 +0100 Subject: [PATCH 0163/1380] [JENKINS-34254] Add test --- .../security/csrf/CrumbFilter/retry.jelly | 2 +- .../security/csrf/DefaultCrumbIssuerTest.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly index 163e015556..70e615b78e 100644 --- a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly +++ b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.jelly @@ -34,7 +34,7 @@ THE SOFTWARE.

${requestURL}

${%warning}

- +
diff --git a/test/src/test/java/hudson/security/csrf/DefaultCrumbIssuerTest.java b/test/src/test/java/hudson/security/csrf/DefaultCrumbIssuerTest.java index f3fddef1af..52dc455faf 100644 --- a/test/src/test/java/hudson/security/csrf/DefaultCrumbIssuerTest.java +++ b/test/src/test/java/hudson/security/csrf/DefaultCrumbIssuerTest.java @@ -8,6 +8,8 @@ package hudson.security.csrf; import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; import com.gargoylesoftware.htmlunit.html.HtmlPage; +import jenkins.model.Jenkins; +import junit.framework.Assert; import net.sf.json.JSONObject; import org.junit.Before; import org.junit.Rule; @@ -135,4 +137,19 @@ public class DefaultCrumbIssuerTest { wc.assertFails("crumbIssuer/api/json?jsonp=hack", HttpURLConnection.HTTP_FORBIDDEN); } + @Issue("JENKINS-34254") + @Test public void testRequirePostErrorPageCrumb() throws Exception { + Jenkins.getInstance().setCrumbIssuer(new DefaultCrumbIssuer(false)); + WebClient wc = r.createWebClient(); + try { + wc.goTo("quietDown"); + fail("expected failure"); + } catch (FailingHttpStatusCodeException ex) { + Assert.assertEquals("expect HTTP 405 method not allowed", 405, ex.getStatusCode()); + } + HtmlPage retry = (HtmlPage) wc.getCurrentWindow().getEnclosedPage(); + HtmlPage success = r.submit(retry.getFormByName("retry")); + Assert.assertTrue("quieting down", r.jenkins.isQuietingDown()); + } + } -- GitLab From 1270ba3b40ade4b822ee7538e31f0692ffbf64d2 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Sat, 16 Dec 2017 03:23:41 -0500 Subject: [PATCH 0164/1380] [JENKINS-48405] Use NIO in tryOnceDeleteFile and makeWritable (#3169) * Use NIO in tryOnceDeleteFile and makeWritable * Don't try to set PosixFileAttributes on Windows * Do not create arbitrary exceptions in makeWritable to fix test failures on Windows * Remove unhelpful layer of exception wrapping * Add test exercising Util#makeWritable in Util#tryOnceDeleteFile * Add test for deleting a non-existant file * Return early if changing permissions with NIO succeeds --- core/src/main/java/hudson/Util.java | 87 +++++++++++-------------- core/src/test/java/hudson/UtilTest.java | 32 +++++++-- 2 files changed, 62 insertions(+), 57 deletions(-) diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index 8ee5c7390a..1e13ea9957 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -28,7 +28,6 @@ import jenkins.util.SystemProperties; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.TaskListener; -import hudson.os.PosixAPI; import hudson.util.QuotedStringTokenizer; import hudson.util.VariableResolver; @@ -36,16 +35,12 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang.time.FastDateFormat; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; -import org.apache.tools.ant.taskdefs.Chmod; import org.apache.tools.ant.taskdefs.Copy; import org.apache.tools.ant.types.FileSet; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; -import jnr.posix.FileStat; -import jnr.posix.POSIX; - import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -70,6 +65,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFileAttributes; import java.nio.file.attribute.DosFileAttributes; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -257,16 +253,16 @@ public class Util { * * @param f * What to delete. If a directory, it'll need to be empty. - * @throws IOException if it exists but could not be successfully deleted + * @throws IOException if it exists but could not be successfully deleted, + * or if it represents an invalid {@link Path}. */ private static void tryOnceDeleteFile(File f) throws IOException { - if (!f.delete()) { - if(!f.exists()) - // we are trying to delete a file that no longer exists, so this is not an error - return; - + Path path = fileToPath(f); + try { + Files.deleteIfExists(path); + } catch (IOException e) { // perhaps this file is read-only? - makeWritable(f); + makeWritable(path); /* on Unix both the file and the directory that contains it has to be writable for a file deletion to be successful. (Confirmed on Solaris 9) @@ -280,56 +276,47 @@ public class Util { $ rm x rm: x not removed: Permission denied */ - - makeWritable(f.getParentFile()); - - if(!f.delete() && f.exists()) { - // trouble-shooting. - try { - Files.deleteIfExists(f.toPath()); - } catch (InvalidPathException e) { - throw new IOException(e); - } - + Path parent = path.getParent(); + if (parent != null) { + makeWritable(parent); + } + try { + Files.deleteIfExists(path); + } catch (IOException e2) { // see https://java.net/projects/hudson/lists/users/archive/2008-05/message/357 // I suspect other processes putting files in this directory File[] files = f.listFiles(); if(files!=null && files.length>0) - throw new IOException("Unable to delete " + f.getPath()+" - files in dir: "+Arrays.asList(files)); - throw new IOException("Unable to delete " + f.getPath()); + throw new IOException("Unable to delete " + f.getPath()+" - files in dir: "+Arrays.asList(files), e2); + throw e2; } } } /** - * Makes the given file writable by any means possible. + * Makes the file at the given path writable by any means possible. */ - private static void makeWritable(@Nonnull File f) { - if (f.setWritable(true)) { - return; - } - // TODO do we still need to try anything else? - - // try chmod. this becomes no-op if this is not Unix. - try { - Chmod chmod = new Chmod(); - chmod.setProject(new Project()); - chmod.setFile(f); - chmod.setPerm("u+w"); - chmod.execute(); - } catch (BuildException e) { - LOGGER.log(Level.INFO,"Failed to chmod "+f,e); - } - - try {// try libc chmod - POSIX posix = PosixAPI.jnr(); - String path = f.getAbsolutePath(); - FileStat stat = posix.stat(path); - posix.chmod(path, stat.mode()|0200); // u+w - } catch (Throwable t) { - LOGGER.log(Level.FINE,"Failed to chmod(2) "+f,t); + private static void makeWritable(@Nonnull Path path) throws IOException { + if (!Functions.isWindows()) { + try { + PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class); + Set newPermissions = ((PosixFileAttributes)attrs).permissions(); + newPermissions.add(PosixFilePermission.OWNER_WRITE); + Files.setPosixFilePermissions(path, newPermissions); + return; + } catch (NoSuchFileException e) { + return; + } catch (UnsupportedOperationException e) { + // PosixFileAttributes not supported, fall back to old IO. + } } + /** + * We intentionally do not check the return code of setWritable, because if it + * is false we prefer to rethrow the exception thrown by Files.deleteIfExists, + * which will have a more useful message than something we make up here. + */ + path.toFile().setWritable(true); } /** diff --git a/core/src/test/java/hudson/UtilTest.java b/core/src/test/java/hudson/UtilTest.java index 21a8993de4..f1cf614055 100644 --- a/core/src/test/java/hudson/UtilTest.java +++ b/core/src/test/java/hudson/UtilTest.java @@ -36,6 +36,9 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.FileSystemException; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.attribute.PosixFilePermissions; import static org.hamcrest.CoreMatchers.allOf; @@ -287,12 +290,6 @@ public class UtilTest { @Test public void testDeleteFile_onWindows() throws Exception { Assume.assumeTrue(Functions.isWindows()); - Class c; - try { - c = Class.forName("java.nio.file.FileSystemException"); - } catch (ClassNotFoundException x) { - throw new AssumptionViolatedException("prior to JDK 7", x); - } final int defaultDeletionMax = Util.DELETION_MAX; try { File f = tmp.newFile(); @@ -304,7 +301,7 @@ public class UtilTest { Util.deleteFile(f); fail("should not have been deletable"); } catch (IOException x) { - assertThat(calcExceptionHierarchy(x), hasItem(c)); + assertThat(calcExceptionHierarchy(x), hasItem(FileSystemException.class)); assertThat(x.getMessage(), containsString(f.getPath())); } } finally { @@ -313,6 +310,27 @@ public class UtilTest { } } + @Test + public void testDeleteFileReadOnly() throws Exception { + // Removing the calls to Util#makeWritable in Util#tryOnceDeleteFile should cause this test to fail. + Path file = tmp.newFolder().toPath().resolve("file.tmp"); + Files.createDirectories(file.getParent()); + Files.createFile(file); + // Using old IO so the test can run on Windows. + file.getParent().toFile().setWritable(false); + file.toFile().setWritable(false); + Util.deleteFile(file.toFile()); + assertFalse(Files.exists(file)); + } + + @Test + public void testDeleteFileDoesNotExist() throws Exception { + Path file = tmp.newFolder().toPath().resolve("file.tmp"); + assertFalse(Files.exists(file)); + // Should not throw an exception. + Util.deleteFile(file.toFile()); + } + @Test public void testDeleteContentsRecursive() throws Exception { final File dir = tmp.newFolder(); -- GitLab From 814d202716a6c61c7d371c6a62755d296fe199a5 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Sat, 16 Dec 2017 09:25:30 +0100 Subject: [PATCH 0165/1380] [JENKINS-22474] API Token does not require CSRF token (#3129) * [JENKINS-22474] API Token does not require CSRF token - in order to ease the use of the api, we are not requiring the request to have a crumb - in terms of security it's not a problem normally since the CSRF attacks use the cookies and in case of API Token, it's session-less / cookie-less * - adjust the license header * - add test for basic authentication - add test for login process * - add test for form submission using ui (htmlunit), not just login form * - modification requested by Jesse * - pom.xml update to use the last version of jenkins-test-harness (with the token helper methods) - beginning of the simplification of tests * - using the try-with-resource approach to ease readability * - using closure method now * - add missing login transformation * - add unit test * - use withToken - remove useless crumb for GET method - add fail (otherwise the assert in catch is not as useful as it could be) * another bunch of test cases * - for HudsonTestCase, some additional modifications are required: changing the view / different type of management for the variable inside the views * - small other tests * - last batch for the login method * - crumb is not more required since we are using API Token * - converting auth to ApiToken to avoid crumb method * - converting auth to ApiToken to avoid crumb method (second) * - remove usage of closure aware methods * - update the pom using the snapshot as adviced by Jesse - modifications on other class to adapt to the last modifications in JTH * - modifications requested during code review * - also put back my changes to the conflicted file * - correction of the merge :) --- .../jenkins/security/ApiCrumbExclusion.java | 53 +++++++ .../BasicHeaderApiTokenAuthenticator.java | 8 +- test/pom.xml | 2 +- .../bugs/JnlpAccessWithSecuredHudsonTest.java | 3 +- .../HudsonHomeDiskUsageMonitorTest.java | 19 ++- .../hudson/model/AbstractProjectTest.java | 8 +- .../test/java/hudson/model/ExecutorTest.java | 10 +- .../src/test/java/hudson/model/ItemsTest.java | 20 +-- test/src/test/java/hudson/model/JobTest.java | 11 +- .../PasswordParameterDefinitionTest.java | 15 +- .../test/java/hudson/model/ProjectTest.java | 36 ++++- .../src/test/java/hudson/model/QueueTest.java | 18 ++- test/src/test/java/hudson/model/UserTest.java | 4 +- test/src/test/java/hudson/model/ViewTest.java | 2 +- .../security/ExtendedReadPermissionTest.java | 12 +- .../HudsonPrivateSecurityRealmTest.java | 1 + .../test/java/hudson/security/LoginTest.java | 3 +- .../util/RobustReflectionConverterTest.java | 28 ++-- .../test/java/jenkins/model/JenkinsTest.java | 25 +-- .../security/ApiCrumbExclusionTest.java | 146 ++++++++++++++++++ .../security/ApiTokenPropertyTest.java | 1 - .../security/BasicHeaderProcessorTest.java | 104 +++++++------ .../jenkins/security/Security380Test.java | 1 + test/src/test/java/lib/form/PasswordTest.java | 81 ++++++---- .../index.jelly} | 2 +- 25 files changed, 448 insertions(+), 165 deletions(-) create mode 100644 core/src/main/java/jenkins/security/ApiCrumbExclusion.java create mode 100644 test/src/test/java/jenkins/security/ApiCrumbExclusionTest.java rename test/src/test/resources/lib/form/PasswordTest/{test1.jelly => SecretNotPlainText/index.jelly} (91%) diff --git a/core/src/main/java/jenkins/security/ApiCrumbExclusion.java b/core/src/main/java/jenkins/security/ApiCrumbExclusion.java new file mode 100644 index 0000000000..8eea5cee13 --- /dev/null +++ b/core/src/main/java/jenkins/security/ApiCrumbExclusion.java @@ -0,0 +1,53 @@ +/* + * The MIT License + * + * Copyright (c) 2017 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 hudson.Extension; +import hudson.security.csrf.CrumbExclusion; +import org.jenkinsci.Symbol; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * JENKINS-22474: Makes API Token calls bypass CSRF protection to ease usage + */ +@Symbol("apiToken") +@Extension +@Restricted(DoNotUse.class) +public class ApiCrumbExclusion extends CrumbExclusion { + @Override + public boolean process(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { + if (Boolean.TRUE.equals(request.getAttribute(BasicHeaderApiTokenAuthenticator.class.getName()))) { + chain.doFilter(request, response); + return true; + } + return false; + } +} diff --git a/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java b/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java index f0f6491e22..345281fdce 100644 --- a/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java +++ b/core/src/main/java/jenkins/security/BasicHeaderApiTokenAuthenticator.java @@ -32,13 +32,12 @@ public class BasicHeaderApiTokenAuthenticator extends BasicHeaderAuthenticator { User u = User.getById(username, true); ApiTokenProperty t = u.getProperty(ApiTokenProperty.class); if (t!=null && t.matchesPassword(password)) { + Authentication auth; try { UserDetails userDetails = u.getUserDetailsForImpersonation(); - Authentication auth = u.impersonate(userDetails); + auth = u.impersonate(userDetails); SecurityListener.fireAuthenticated(userDetails); - - return auth; } 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. @@ -47,6 +46,9 @@ public class BasicHeaderApiTokenAuthenticator extends BasicHeaderAuthenticator { } catch (DataAccessException x) { throw new ServletException(x); } + + req.setAttribute(BasicHeaderApiTokenAuthenticator.class.getName(), true); + return auth; } return null; } diff --git a/test/pom.xml b/test/pom.xml index 578d451007..8745d54534 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -53,7 +53,7 @@ THE SOFTWARE. ${project.groupId} jenkins-test-harness - 2.31 + 2.32 test diff --git a/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java b/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java index 8c970e8cc9..906adcf6e7 100644 --- a/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java +++ b/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java @@ -31,6 +31,7 @@ import hudson.Proc; import hudson.cli.util.ScriptLoader; import hudson.model.Node.Mode; import hudson.model.Slave; +import hudson.model.User; import hudson.remoting.Channel; import hudson.slaves.JNLPLauncher; import hudson.slaves.RetentionStrategy; @@ -86,7 +87,7 @@ public class JnlpAccessWithSecuredHudsonTest { public void anonymousCanAlwaysLoadJARs() throws Exception { r.jenkins.setNodes(Collections.singletonList(createNewJnlpSlave("test"))); JenkinsRule.WebClient wc = r.createWebClient(); - HtmlPage p = wc.login("alice").goTo("computer/test/"); + HtmlPage p = wc.withBasicApiToken(User.getById("alice", true)).goTo("computer/test/"); // this fresh WebClient doesn't have a login cookie and represent JNLP launcher JenkinsRule.WebClient jnlpAgent = r.createWebClient(); diff --git a/test/src/test/java/hudson/diagnosis/HudsonHomeDiskUsageMonitorTest.java b/test/src/test/java/hudson/diagnosis/HudsonHomeDiskUsageMonitorTest.java index d416b3e451..3b3da3b177 100644 --- a/test/src/test/java/hudson/diagnosis/HudsonHomeDiskUsageMonitorTest.java +++ b/test/src/test/java/hudson/diagnosis/HudsonHomeDiskUsageMonitorTest.java @@ -11,10 +11,7 @@ import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.util.NameValuePair; import hudson.model.User; import hudson.security.GlobalMatrixAuthorizationStrategy; -import hudson.security.HudsonPrivateSecurityRealm; -import hudson.security.Permission; import jenkins.model.Jenkins; -import org.acegisecurity.context.SecurityContextHolder; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -25,6 +22,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.ElementNotFoundException; import java.io.IOException; +import java.net.URL; import java.util.Collections; /** @@ -73,28 +71,33 @@ public class HudsonHomeDiskUsageMonitorTest { auth.add(Jenkins.READ, "users"); j.jenkins.setAuthorizationStrategy(auth); - WebRequest request = new WebRequest(wc.createCrumbedUrl("administrativeMonitor/hudsonHomeIsFull/act"), HttpMethod.POST); + User bob = User.getById("bob", true); + User administrator = User.getById("administrator", true); + + WebRequest request = new WebRequest(new URL(wc.getContextPath() + "administrativeMonitor/hudsonHomeIsFull/act"), HttpMethod.POST); NameValuePair param = new NameValuePair("no", "true"); request.setRequestParameters(Collections.singletonList(param)); HudsonHomeDiskUsageMonitor mon = HudsonHomeDiskUsageMonitor.get(); + wc.withBasicApiToken(bob); try { - wc.login("bob"); wc.getPage(request); + fail(); } catch (FailingHttpStatusCodeException e) { assertEquals(403, e.getStatusCode()); } assertTrue(mon.isEnabled()); + WebRequest requestReadOnly = new WebRequest(new URL(wc.getContextPath() + "administrativeMonitor/hudsonHomeIsFull"), HttpMethod.GET); try { - WebRequest getIndex = new WebRequest(wc.createCrumbedUrl("administrativeMonitor/hudsonHomeIsFull"), HttpMethod.GET); - wc.getPage(getIndex); + wc.getPage(requestReadOnly); + fail(); } catch (FailingHttpStatusCodeException e) { assertEquals(403, e.getStatusCode()); } - wc.login("administrator"); + wc.withBasicApiToken(administrator); wc.getPage(request); assertFalse(mon.isEnabled()); diff --git a/test/src/test/java/hudson/model/AbstractProjectTest.java b/test/src/test/java/hudson/model/AbstractProjectTest.java index 308fc12993..8ba1982b11 100644 --- a/test/src/test/java/hudson/model/AbstractProjectTest.java +++ b/test/src/test/java/hudson/model/AbstractProjectTest.java @@ -629,16 +629,16 @@ public class AbstractProjectTest { grant(Item.READ).everywhere().to("alice"). grant(Item.READ).onItems(us).to("bob"). grant(Item.READ).onItems(ds).to("charlie")); - String api = j.createWebClient().login("alice").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); + String api = j.createWebClient().withBasicCredentials("alice").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); System.out.println(api); assertThat(api, containsString("downstream-project")); - api = j.createWebClient().login("alice").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); + api = j.createWebClient().withBasicCredentials("alice").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); System.out.println(api); assertThat(api, containsString("upstream-project")); - api = j.createWebClient().login("bob").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); + api = j.createWebClient().withBasicCredentials("bob").goTo(us.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); System.out.println(api); assertThat(api, not(containsString("downstream-project"))); - api = j.createWebClient().login("charlie").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); + api = j.createWebClient().withBasicCredentials("charlie").goTo(ds.getUrl() + "api/json?pretty", null).getWebResponse().getContentAsString(); System.out.println(api); assertThat(api, not(containsString("upstream-project"))); } diff --git a/test/src/test/java/hudson/model/ExecutorTest.java b/test/src/test/java/hudson/model/ExecutorTest.java index e13ff1c345..70254456fd 100644 --- a/test/src/test/java/hudson/model/ExecutorTest.java +++ b/test/src/test/java/hudson/model/ExecutorTest.java @@ -136,10 +136,16 @@ public class ExecutorTest { grant(Jenkins.READ).everywhere().toEveryone(). grant(Item.READ).onItems(publicProject).toEveryone(). grant(Item.READ).onItems(secretProject).to("has-security-clearance")); - String api = j.createWebClient().login("has-security-clearance").goTo(slave.toComputer().getUrl() + "api/json?pretty&depth=1", null).getWebResponse().getContentAsString(); + + JenkinsRule.WebClient wc = j.createWebClient(); + wc.withBasicCredentials("has-security-clearance"); + String api = wc.goTo(slave.toComputer().getUrl() + "api/json?pretty&depth=1", null).getWebResponse().getContentAsString(); System.out.println(api); assertThat(api, allOf(containsString("public-project"), containsString("secret-project"))); - api = j.createWebClient().login("regular-joe").goTo(slave.toComputer().getUrl() + "api/json?pretty&depth=1", null).getWebResponse().getContentAsString(); + + wc = j.createWebClient(); + wc.withBasicCredentials("regular-joe"); + api = wc.goTo(slave.toComputer().getUrl() + "api/json?pretty&depth=1", null).getWebResponse().getContentAsString(); System.out.println(api); assertThat(api, allOf(containsString("public-project"), not(containsString("secret-project")))); } diff --git a/test/src/test/java/hudson/model/ItemsTest.java b/test/src/test/java/hudson/model/ItemsTest.java index 32ec8e6e47..40dfd2fa8d 100644 --- a/test/src/test/java/hudson/model/ItemsTest.java +++ b/test/src/test/java/hudson/model/ItemsTest.java @@ -45,6 +45,7 @@ import org.acegisecurity.context.SecurityContext; import org.acegisecurity.context.SecurityContextHolder; import org.apache.commons.httpclient.HttpStatus; +import org.junit.Before; import org.junit.Test; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -123,6 +124,8 @@ public class ItemsTest { // TODO would be more efficient to run these all as a single test case, but after a few Jetty seems to stop serving new content and new requests just hang. private void overwriteTargetSetUp() throws Exception { + User.getById("attacker", true); + // A fully visible item: r.createFreeStyleProject("visible").setDescription("visible"); // An item known to exist but not visible: @@ -185,7 +188,7 @@ public class ItemsTest { JenkinsRule.WebClient wc = wc(r); wc.getOptions().setRedirectEnabled(false); wc.getOptions().setThrowExceptionOnFailingStatusCode(false); // redirect perversely counts as a failure - WebResponse webResponse = wc.getPage(new WebRequest(createCrumbedUrl(r, wc, "createItem?name=" + target + "&mode=hudson.model.FreeStyleProject"), HttpMethod.POST)).getWebResponse(); + WebResponse webResponse = wc.getPage(new WebRequest(new URL(wc.getContextPath() + "createItem?name=" + target + "&mode=hudson.model.FreeStyleProject"), HttpMethod.POST)).getWebResponse(); if (webResponse.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) { throw new FailingHttpStatusCodeException(webResponse); } @@ -198,7 +201,7 @@ public class ItemsTest { JenkinsRule.WebClient wc = wc(r); wc.getOptions().setRedirectEnabled(false); wc.getOptions().setThrowExceptionOnFailingStatusCode(false); - WebResponse webResponse = wc.getPage(new WebRequest(createCrumbedUrl(r, wc, "createItem?name=" + target + "&mode=copy&from=dupe"), HttpMethod.POST)).getWebResponse(); + WebResponse webResponse = wc.getPage(new WebRequest(new URL(wc.getContextPath() + "createItem?name=" + target + "&mode=copy&from=dupe"), HttpMethod.POST)).getWebResponse(); r.jenkins.getItem("dupe").delete(); if (webResponse.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) { throw new FailingHttpStatusCodeException(webResponse); @@ -209,7 +212,7 @@ public class ItemsTest { REST_CREATE { @Override void run(JenkinsRule r, String target) throws Exception { JenkinsRule.WebClient wc = wc(r); - WebRequest req = new WebRequest(createCrumbedUrl(r, wc, "createItem?name=" + target), HttpMethod.POST); + WebRequest req = new WebRequest(new URL(wc.getContextPath() + "createItem?name=" + target), HttpMethod.POST); req.setAdditionalHeader("Content-Type", "application/xml"); req.setRequestBody(""); wc.getPage(req); @@ -222,7 +225,7 @@ public class ItemsTest { JenkinsRule.WebClient wc = wc(r); wc.getOptions().setRedirectEnabled(false); wc.getOptions().setThrowExceptionOnFailingStatusCode(false); - WebResponse webResponse = wc.getPage(new WebRequest(createCrumbedUrl(r, wc, "job/dupe/doRename?newName=" + target), HttpMethod.POST)).getWebResponse(); + WebResponse webResponse = wc.getPage(new WebRequest(new URL(wc.getContextPath() + "job/dupe/doRename?newName=" + target), HttpMethod.POST)).getWebResponse(); if (webResponse.getStatusCode() != HttpStatus.SC_MOVED_TEMPORARILY) { r.jenkins.getItem("dupe").delete(); throw new FailingHttpStatusCodeException(webResponse); @@ -275,14 +278,7 @@ public class ItemsTest { }; abstract void run(JenkinsRule r, String target) throws Exception; private static final JenkinsRule.WebClient wc(JenkinsRule r) throws Exception { - return r.createWebClient().login("attacker"); - } - // TODO replace with standard version once it is fixed to detect an existing query string - private static URL createCrumbedUrl(JenkinsRule r, JenkinsRule.WebClient wc, String relativePath) throws IOException { - CrumbIssuer issuer = r.jenkins.getCrumbIssuer(); - String crumbName = issuer.getDescriptor().getCrumbRequestField(); - String crumb = issuer.getCrumb(null); - return new URL(wc.getContextPath() + relativePath + (relativePath.contains("?") ? "&" : "?") + crumbName + "=" + crumb); + return r.createWebClient().withBasicApiToken("attacker"); } } diff --git a/test/src/test/java/hudson/model/JobTest.java b/test/src/test/java/hudson/model/JobTest.java index d20b9c28a6..58e14cdd33 100644 --- a/test/src/test/java/hudson/model/JobTest.java +++ b/test/src/test/java/hudson/model/JobTest.java @@ -202,7 +202,7 @@ public class JobTest { JenkinsRule.WebClient wc = j.createWebClient(); wc.assertFails("job/testJob/", HttpURLConnection.HTTP_NOT_FOUND); wc.assertFails("jobCaseInsensitive/testJob/", HttpURLConnection.HTTP_NOT_FOUND); - wc.login("joe"); // Has Item.READ permission + wc.withBasicCredentials("joe"); // Has Item.READ permission // Verify we can access both URLs: wc.goTo("job/testJob/"); wc.goTo("jobCaseInsensitive/TESTJOB/"); @@ -216,11 +216,14 @@ public class JobTest { Item.EXTENDED_READ.setEnabled(true); try { wc.assertFails("job/testJob/config.xml", HttpURLConnection.HTTP_FORBIDDEN); - wc.login("alice"); // Has CONFIGURE and EXTENDED_READ permission + + wc.withBasicApiToken(User.getById("alice", true)); // Has CONFIGURE and EXTENDED_READ permission tryConfigDotXml(wc, 500, "Both perms; should get 500"); - wc.login("bob"); // Has only CONFIGURE permission (this should imply EXTENDED_READ) + + wc.withBasicApiToken(User.getById("bob", true)); // Has only CONFIGURE permission (this should imply EXTENDED_READ) tryConfigDotXml(wc, 500, "Config perm should imply EXTENDED_READ"); - wc.login("charlie"); // Has only EXTENDED_READ permission + + wc.withBasicApiToken(User.getById("charlie", true)); // Has only EXTENDED_READ permission tryConfigDotXml(wc, 403, "No permission, should get 403"); } finally { Item.EXTENDED_READ.setEnabled(saveEnabled); diff --git a/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java b/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java index 2129c9fce9..4fab18d601 100644 --- a/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java +++ b/test/src/test/java/hudson/model/PasswordParameterDefinitionTest.java @@ -62,16 +62,21 @@ public class PasswordParameterDefinitionTest { return true; } }); + + User admin = User.getById("admin", true); + User dev = User.getById("dev", true); + JenkinsRule.WebClient wc = j.createWebClient(); wc.getOptions().setThrowExceptionOnFailingStatusCode(false); // ParametersDefinitionProperty/index.jelly sends a 405 but really it is OK // Control case: admin can use default value. - j.submit(wc.login("admin").getPage(p, "build?delay=0sec").getFormByName("parameters")); + j.submit(wc.withBasicApiToken(admin).getPage(p, "build?delay=0sec").getFormByName("parameters")); j.waitUntilNoActivity(); FreeStyleBuild b1 = p.getLastBuild(); assertEquals(1, b1.getNumber()); j.assertLogContains("I heard about a s3cr3t!", j.assertBuildStatusSuccess(b1)); + // Another control case: anyone can enter a different value. - HtmlForm form = wc.login("dev").getPage(p, "build?delay=0sec").getFormByName("parameters"); + HtmlForm form = wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters"); HtmlPasswordInput input = form.getInputByName("value"); input.setText("rumor"); j.submit(form); @@ -79,14 +84,16 @@ public class PasswordParameterDefinitionTest { FreeStyleBuild b2 = p.getLastBuild(); assertEquals(2, b2.getNumber()); j.assertLogContains("I heard about a rumor!", j.assertBuildStatusSuccess(b2)); + // Test case: anyone can use default value. - j.submit(wc.login("dev").getPage(p, "build?delay=0sec").getFormByName("parameters")); + j.submit(wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters")); j.waitUntilNoActivity(); FreeStyleBuild b3 = p.getLastBuild(); assertEquals(3, b3.getNumber()); j.assertLogContains("I heard about a s3cr3t!", j.assertBuildStatusSuccess(b3)); + // Another control case: blank values. - form = wc.login("dev").getPage(p, "build?delay=0sec").getFormByName("parameters"); + form = wc.withBasicApiToken(dev).getPage(p, "build?delay=0sec").getFormByName("parameters"); input = form.getInputByName("value"); input.setText(""); j.submit(form); diff --git a/test/src/test/java/hudson/model/ProjectTest.java b/test/src/test/java/hudson/model/ProjectTest.java index a2d5bf3f67..f966f4e56b 100644 --- a/test/src/test/java/hudson/model/ProjectTest.java +++ b/test/src/test/java/hudson/model/ProjectTest.java @@ -32,9 +32,12 @@ import hudson.tasks.*; import hudson.security.HudsonPrivateSecurityRealm; import hudson.security.GlobalMatrixAuthorizationStrategy; +import java.io.Closeable; +import java.net.HttpURLConnection; import java.net.URL; import java.util.Collections; +import hudson.util.Scrambler; import org.jvnet.hudson.reactor.ReactorException; import org.jvnet.hudson.test.FakeChangeLogSCM; import hudson.scm.SCMRevisionState; @@ -553,9 +556,8 @@ public class ProjectTest { GlobalMatrixAuthorizationStrategy auth = new GlobalMatrixAuthorizationStrategy(); j.jenkins.setAuthorizationStrategy(auth); j.jenkins.setCrumbIssuer(null); - HudsonPrivateSecurityRealm realm = new HudsonPrivateSecurityRealm(false); - j.jenkins.setSecurityRealm(realm); - User user = realm.createAccount("John Smith", "password"); + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + User user = User.getById("john", true); try (ACLContext as = ACL.as(user)) { project.doDoDelete(null, null); fail("User should not have permission to build project"); @@ -568,7 +570,13 @@ public class ProjectTest { auth.add(Jenkins.READ, user.getId()); auth.add(Job.READ, user.getId()); auth.add(Job.DELETE, user.getId()); - List forms = j.createWebClient().login(user.getId(), "password").goTo(project.getUrl() + "delete").getForms(); + + // use Basic to speedup the test, normally it's pure UI testing + JenkinsRule.WebClient wc = j.createWebClient(); + wc.withBasicCredentials(user.getId()); + HtmlPage p = wc.goTo(project.getUrl() + "delete"); + + List forms = p.getForms(); for(HtmlForm form:forms){ if("doDelete".equals(form.getAttribute("action"))){ j.submit(form); @@ -605,9 +613,13 @@ public class ProjectTest { String cmd = "echo hello > change.log"; project.getBuildersList().add(Functions.isWindows()? new BatchFile(cmd) : new Shell(cmd)); j.buildAndAssertSuccess(project); - JenkinsRule.WebClient wc = j.createWebClient().login(user.getId(), "password"); + + JenkinsRule.WebClient wc = j.createWebClient(); + wc.withBasicCredentials(user.getId(), "password"); WebRequest request = new WebRequest(new URL(wc.getContextPath() + project.getUrl() + "doWipeOutWorkspace"), HttpMethod.POST); HtmlPage p = wc.getPage(request); + assertEquals(p.getWebResponse().getStatusCode(), 200); + Thread.sleep(500); assertFalse("Workspace should not exist.", project.getSomeWorkspace().exists()); } @@ -633,7 +645,12 @@ public class ProjectTest { auth.add(Job.READ, user.getId()); auth.add(Job.CONFIGURE, user.getId()); auth.add(Jenkins.READ, user.getId()); - List forms = j.createWebClient().login(user.getId(), "password").goTo(project.getUrl()).getForms(); + + JenkinsRule.WebClient wc = j.createWebClient(); + wc.withBasicCredentials(user.getId(), "password"); + HtmlPage p = wc.goTo(project.getUrl()); + + List forms = p.getForms(); for(HtmlForm form:forms){ if("disable".equals(form.getAttribute("action"))){ j.submit(form); @@ -666,7 +683,12 @@ public class ProjectTest { auth.add(Job.READ, user.getId()); auth.add(Job.CONFIGURE, user.getId()); auth.add(Jenkins.READ, user.getId()); - List forms = j.createWebClient().login(user.getId(), "password").goTo(project.getUrl()).getForms(); + + JenkinsRule.WebClient wc = j.createWebClient(); + wc.withBasicCredentials(user.getId(), "password"); + HtmlPage p = wc.goTo(project.getUrl()); + + List forms = p.getForms(); for(HtmlForm form:forms){ if("enable".equals(form.getAttribute("action"))){ j.submit(form); diff --git a/test/src/test/java/hudson/model/QueueTest.java b/test/src/test/java/hudson/model/QueueTest.java index 5e626aea3c..689b63e5ae 100644 --- a/test/src/test/java/hudson/model/QueueTest.java +++ b/test/src/test/java/hudson/model/QueueTest.java @@ -943,8 +943,12 @@ public class QueueTest { project.getBuildersList().add(new SleepBuilder(10)); project.scheduleBuild2(0); + User alice = User.getById("alice", true); + User bob = User.getById("bob", true); + User james = User.getById("james", true); + JenkinsRule.WebClient webClient = r.createWebClient(); - webClient.login("bob", "bob"); + webClient.withBasicApiToken(bob); XmlPage p = webClient.goToXml("queue/api/xml"); //bob has permission on the project and will be able to see it in the queue together with information such as the URL and the name. @@ -959,13 +963,15 @@ public class QueueTest { } } } + webClient = r.createWebClient(); - webClient.login("alice"); + webClient.withBasicApiToken(alice); XmlPage p2 = webClient.goToXml("queue/api/xml"); //alice does not have permission on the project and will not see it in the queue. assertTrue(p2.getByXPath("/queue/node()").isEmpty()); + webClient = r.createWebClient(); - webClient.login("james"); + webClient.withBasicApiToken(james); XmlPage p3 = webClient.goToXml("queue/api/xml"); //james has DISCOVER permission on the project and will only be able to see the task name. @@ -975,9 +981,9 @@ public class QueueTest { // Also check individual item exports. String url = project.getQueueItem().getUrl() + "api/xml"; - r.createWebClient().login("bob").goToXml(url); // OK, 200 - r.createWebClient().login("james").assertFails(url, HttpURLConnection.HTTP_FORBIDDEN); // only DISCOVER → AccessDeniedException - r.createWebClient().login("alice").assertFails(url, HttpURLConnection.HTTP_NOT_FOUND); // not even DISCOVER + r.createWebClient().withBasicApiToken(bob).goToXml(url); // OK, 200 + r.createWebClient().withBasicApiToken(james).assertFails(url, HttpURLConnection.HTTP_FORBIDDEN); // only DISCOVER → AccessDeniedException + r.createWebClient().withBasicApiToken(alice).assertFails(url, HttpURLConnection.HTTP_NOT_FOUND); // not even DISCOVER } //we force the project not to be executed so that it stays in the queue diff --git a/test/src/test/java/hudson/model/UserTest.java b/test/src/test/java/hudson/model/UserTest.java index b81105fe55..34bb26a1c9 100644 --- a/test/src/test/java/hudson/model/UserTest.java +++ b/test/src/test/java/hudson/model/UserTest.java @@ -415,7 +415,7 @@ public class UserTest { auth.add(Jenkins.ADMINISTER, user.getId()); auth.add(Jenkins.READ, user2.getId()); SecurityContextHolder.getContext().setAuthentication(user.impersonate()); - HtmlForm form = j.createWebClient().login(user.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config"); + HtmlForm form = j.createWebClient().withBasicCredentials(user.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config"); form.getInputByName("_.fullName").setValueAttribute("Alice Smith"); j.submit(form); assertEquals("User should have full name Alice Smith.", "Alice Smith", user2.getFullName()); @@ -429,7 +429,7 @@ public class UserTest { fail("AccessDeniedException should be thrown."); } } - form = j.createWebClient().login(user2.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config"); + form = j.createWebClient().withBasicCredentials(user2.getId(), "password").goTo(user2.getUrl() + "/configure").getFormByName("config"); form.getInputByName("_.fullName").setValueAttribute("John"); j.submit(form); diff --git a/test/src/test/java/hudson/model/ViewTest.java b/test/src/test/java/hudson/model/ViewTest.java index 7a70d87df2..21aa56fc38 100644 --- a/test/src/test/java/hudson/model/ViewTest.java +++ b/test/src/test/java/hudson/model/ViewTest.java @@ -529,7 +529,7 @@ public class ViewTest { return null; } }); - JenkinsRule.WebClient wc = j.createWebClient().login("admin"); + JenkinsRule.WebClient wc = j.createWebClient().withBasicCredentials("admin"); assertEquals("original ${rootURL}/checkJobName still supported", "
", wc.goTo("checkJobName?value=stuff").getWebResponse().getContentAsString()); assertEquals("but now possible on a view in a folder", "
", wc.goTo("job/d1/view/All/checkJobName?value=stuff").getWebResponse().getContentAsString()); } diff --git a/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java b/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java index f989e1439a..04d3820e21 100644 --- a/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java +++ b/test/src/test/java/hudson/security/ExtendedReadPermissionTest.java @@ -52,7 +52,9 @@ public class ExtendedReadPermissionTest { GlobalMatrixAuthorizationStrategy gas = (GlobalMatrixAuthorizationStrategy)as; assertTrue("Charlie should have extended read for this test", gas.hasExplicitPermission("charlie",Item.EXTENDED_READ)); - JenkinsRule.WebClient wc = r.createWebClient().login("charlie","charlie"); + JenkinsRule.WebClient wc = r.createWebClient(); + wc.withBasicCredentials("charlie"); + HtmlPage page = wc.goTo("job/a/configure"); HtmlForm form = page.getFormByName("config"); HtmlButton saveButton = r.getButtonByCaption(form,"Save"); @@ -68,7 +70,9 @@ public class ExtendedReadPermissionTest { GlobalMatrixAuthorizationStrategy gas = (GlobalMatrixAuthorizationStrategy)as; assertFalse("Charlie should not have extended read for this test", gas.hasExplicitPermission("charlie",Item.EXTENDED_READ)); - JenkinsRule.WebClient wc = r.createWebClient().login("charlie","charlie"); + JenkinsRule.WebClient wc = r.createWebClient(); + wc.withBasicCredentials("charlie"); + wc.assertFails("job/a/configure", HttpURLConnection.HTTP_FORBIDDEN); } @@ -81,7 +85,9 @@ public class ExtendedReadPermissionTest { GlobalMatrixAuthorizationStrategy gas = (GlobalMatrixAuthorizationStrategy)as; assertFalse("Bob should not have extended read for this test", gas.hasExplicitPermission("bob",Item.EXTENDED_READ)); - JenkinsRule.WebClient wc = r.createWebClient().login("bob","bob"); + JenkinsRule.WebClient wc = r.createWebClient(); + wc.withBasicCredentials("bob"); + wc.assertFails("job/a/configure", HttpURLConnection.HTTP_FORBIDDEN); } diff --git a/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java index 5e51c5ff04..e58fb48d0b 100644 --- a/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java +++ b/test/src/test/java/hudson/security/HudsonPrivateSecurityRealmTest.java @@ -91,6 +91,7 @@ public class HudsonPrivateSecurityRealmTest { // verify the sanity that the password is really used // this should fail j.createWebClient().login("bob", "bob"); + fail(); } catch (FailingHttpStatusCodeException e) { assertEquals(401,e.getStatusCode()); } diff --git a/test/src/test/java/hudson/security/LoginTest.java b/test/src/test/java/hudson/security/LoginTest.java index 86c1a0662c..91ef0e6e34 100644 --- a/test/src/test/java/hudson/security/LoginTest.java +++ b/test/src/test/java/hudson/security/LoginTest.java @@ -11,6 +11,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlCheckBoxInput; +import hudson.model.User; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -56,7 +57,7 @@ public class LoginTest { WebClient wc = j.createWebClient(); wc.assertFails("loginError", SC_UNAUTHORIZED); // but not once the user logs in. - verifyNotError(wc.login("alice")); + verifyNotError(wc.withBasicApiToken(User.getById("alice", true))); } private HtmlForm prepareLoginFormWithRememberMeChecked(WebClient wc) throws IOException, org.xml.sax.SAXException { diff --git a/test/src/test/java/hudson/util/RobustReflectionConverterTest.java b/test/src/test/java/hudson/util/RobustReflectionConverterTest.java index 0bff3490cf..c57acd55cd 100644 --- a/test/src/test/java/hudson/util/RobustReflectionConverterTest.java +++ b/test/src/test/java/hudson/util/RobustReflectionConverterTest.java @@ -27,16 +27,18 @@ package hudson.util; import hudson.cli.CLICommandInvoker; import hudson.diagnosis.OldDataMonitor; import hudson.model.AbstractDescribableImpl; -import hudson.model.Items; -import hudson.model.JobProperty; -import hudson.model.JobPropertyDescriptor; import hudson.model.Descriptor; import hudson.model.FreeStyleProject; +import hudson.model.Items; import hudson.model.Job; +import hudson.model.JobProperty; +import hudson.model.JobPropertyDescriptor; import hudson.model.Saveable; +import hudson.model.User; import hudson.security.ACL; import java.io.ByteArrayInputStream; +import java.net.URL; import java.util.Collections; import java.util.Map; @@ -81,7 +83,7 @@ public class RobustReflectionConverterTest { // GUI related implementations (@DataBoundConstructor and newInstance) aren't used actually // (no jelly files are provides and they don't work actually), // but written to clarify a use case. - public static class AcceptOnlySpecificKeyword extends AbstractDescribableImpl{ + public static class AcceptOnlySpecificKeyword extends AbstractDescribableImpl { public static final String ACCEPT_KEYWORD = "accept"; private final String keyword; @@ -185,7 +187,9 @@ public class RobustReflectionConverterTest { @Test public void testRestInterfaceFailure() throws Exception { Items.XSTREAM2.addCriticalField(KeywordProperty.class, "criticalField"); - + + User test = User.getById("test", true); + // without addCriticalField. This is accepted. { FreeStyleProject p = r.createFreeStyleProject(); @@ -198,11 +202,8 @@ public class RobustReflectionConverterTest { // Configure a bad keyword via REST. r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); WebClient wc = r.createWebClient(); - wc.login("test", "test"); - WebRequest req = new WebRequest( - wc.createCrumbedUrl(String.format("%s/config.xml", p.getUrl())), - HttpMethod.POST - ); + wc.withBasicApiToken(test); + WebRequest req = new WebRequest(new URL(wc.getContextPath() + String.format("%s/config.xml", p.getUrl())), HttpMethod.POST); req.setEncodingType(null); req.setRequestBody(String.format(CONFIGURATION_TEMPLATE, "badvalue", AcceptOnlySpecificKeyword.ACCEPT_KEYWORD)); wc.getPage(req); @@ -231,11 +232,8 @@ public class RobustReflectionConverterTest { // Configure a bad keyword via REST. r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); WebClient wc = r.createWebClient(); - wc.login("test", "test"); - WebRequest req = new WebRequest( - wc.createCrumbedUrl(String.format("%s/config.xml", p.getUrl())), - HttpMethod.POST - ); + wc.withBasicApiToken(test); + WebRequest req = new WebRequest(new URL(wc.getContextPath() + String.format("%s/config.xml", p.getUrl())), HttpMethod.POST); req.setEncodingType(null); req.setRequestBody(String.format(CONFIGURATION_TEMPLATE, AcceptOnlySpecificKeyword.ACCEPT_KEYWORD, "badvalue")); diff --git a/test/src/test/java/jenkins/model/JenkinsTest.java b/test/src/test/java/jenkins/model/JenkinsTest.java index 2e488bd80b..a68ee0a4d6 100644 --- a/test/src/test/java/jenkins/model/JenkinsTest.java +++ b/test/src/test/java/jenkins/model/JenkinsTest.java @@ -314,20 +314,23 @@ public class JenkinsTest { grant(Jenkins.READ).everywhere().to("bob"). grantWithoutImplication(Jenkins.ADMINISTER, Jenkins.READ).everywhere().to("charlie")); WebClient wc = j.createWebClient(); - wc.login("alice"); + + wc.withBasicApiToken(User.getById("alice", true)); wc.goTo("script"); wc.assertFails("script?script=System.setProperty('hack','me')", HttpURLConnection.HTTP_BAD_METHOD); assertNull(System.getProperty("hack")); WebRequest req = new WebRequest(new URL(wc.getContextPath() + "script?script=System.setProperty('hack','me')"), HttpMethod.POST); - wc.getPage(wc.addCrumb(req)); + wc.getPage(req); assertEquals("me", System.getProperty("hack")); wc.assertFails("scriptText?script=System.setProperty('hack','me')", HttpURLConnection.HTTP_BAD_METHOD); req = new WebRequest(new URL(wc.getContextPath() + "scriptText?script=System.setProperty('huck','you')"), HttpMethod.POST); - wc.getPage(wc.addCrumb(req)); + wc.getPage(req); assertEquals("you", System.getProperty("huck")); - wc.login("bob"); + + wc.withBasicApiToken(User.getById("bob", true)); wc.assertFails("script", HttpURLConnection.HTTP_FORBIDDEN); - wc.login("charlie"); + + wc.withBasicApiToken(User.getById("charlie", true)); wc.assertFails("script", HttpURLConnection.HTTP_FORBIDDEN); } @@ -338,18 +341,22 @@ public class JenkinsTest { grant(Jenkins.ADMINISTER).everywhere().to("alice"). grant(Jenkins.READ).everywhere().to("bob"). grantWithoutImplication(Jenkins.ADMINISTER, Jenkins.READ).everywhere().to("charlie")); + WebClient wc = j.createWebClient(); - wc.login("alice"); + + wc.withBasicApiToken(User.getById("alice", true)); wc.assertFails("eval", HttpURLConnection.HTTP_BAD_METHOD); assertEquals("3", eval(wc)); - wc.login("bob"); + + wc.withBasicApiToken(User.getById("bob", true)); try { eval(wc); fail("bob has only READ"); } catch (FailingHttpStatusCodeException e) { assertEquals(HttpURLConnection.HTTP_FORBIDDEN, e.getStatusCode()); } - wc.login("charlie"); + + wc.withBasicApiToken(User.getById("charlie", true)); try { eval(wc); fail("charlie has ADMINISTER but not RUN_SCRIPTS"); @@ -358,7 +365,7 @@ public class JenkinsTest { } } private String eval(WebClient wc) throws Exception { - WebRequest req = new WebRequest(wc.createCrumbedUrl("eval"), HttpMethod.POST); + WebRequest req = new WebRequest(new URL(wc.getContextPath() + "eval"), HttpMethod.POST); req.setEncodingType(null); req.setRequestBody("${1+2}"); return wc.getPage(req).getWebResponse().getContentAsString(); diff --git a/test/src/test/java/jenkins/security/ApiCrumbExclusionTest.java b/test/src/test/java/jenkins/security/ApiCrumbExclusionTest.java new file mode 100644 index 0000000000..eef3b21dfd --- /dev/null +++ b/test/src/test/java/jenkins/security/ApiCrumbExclusionTest.java @@ -0,0 +1,146 @@ +/* + * The MIT License + * + * Copyright (c) 2017 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 com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlFormUtil; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.model.UnprotectedRootAction; +import hudson.model.User; +import hudson.security.csrf.DefaultCrumbIssuer; +import hudson.util.HttpResponses; +import hudson.util.Scrambler; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.JenkinsRule.WebClient; +import org.jvnet.hudson.test.TestExtension; +import org.kohsuke.stapler.HttpResponse; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.net.URL; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class ApiCrumbExclusionTest { + @Rule + public JenkinsRule j = new JenkinsRule(); + + private WebClient wc; + + @Test + @Issue("JENKINS-22474") + public void callUsingApiTokenDoesNotRequireCSRFToken() throws Exception { + j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); + j.jenkins.setCrumbIssuer(null); + User foo = User.get("foo"); + + wc = j.createWebClient(); + + // API Token + wc.withBasicApiToken(foo); + makeRequestAndVerify("foo"); + + // Basic auth using password + wc = j.createWebClient(); + wc.withBasicCredentials("foo"); + makeRequestAndVerify("foo"); + + wc = j.createWebClient(); + wc.login("foo"); + checkWeCanChangeMyDescription(200); + + wc = j.createWebClient(); + j.jenkins.setCrumbIssuer(new DefaultCrumbIssuer(false)); + + // even with crumbIssuer enabled, we are not required to send a CSRF token when using API token + wc.withBasicApiToken(foo); + makeRequestAndVerify("foo"); + + // Basic auth using password requires crumb + wc = j.createWebClient(); + wc.withBasicCredentials("foo"); + makeRequestAndFail(403); + + wc = j.createWebClient(); + wc.login("foo"); + checkWeCanChangeMyDescription(200); + } + + private void makeRequestAndVerify(String expected) throws IOException, SAXException { + WebRequest req = new WebRequest(new URL(j.getURL(), "test-post")); + req.setHttpMethod(HttpMethod.POST); + req.setEncodingType(null); + Page p = wc.getPage(req); + assertEquals(expected, p.getWebResponse().getContentAsString()); + } + + private void makeRequestAndFail(int expectedCode) throws IOException, SAXException { + try { + makeRequestAndVerify("-"); + fail(); + } catch (FailingHttpStatusCodeException e) { + assertEquals(expectedCode, e.getStatusCode()); + } + } + + private void checkWeCanChangeMyDescription(int expectedCode) throws IOException, SAXException { + HtmlPage page = wc.goTo("me/configure"); + HtmlForm form = page.getFormByName("config"); + form.getTextAreaByName("_.description").setText("random description: " + Math.random()); + + Page result = HtmlFormUtil.submit(form); + assertEquals(expectedCode, result.getWebResponse().getStatusCode()); + } + + @TestExtension + public static class WhoAmI implements UnprotectedRootAction { + @Override + public String getIconFileName() { + return null; + } + + @Override + public String getDisplayName() { + return null; + } + + @Override + public String getUrlName() { + return "test-post"; + } + + public HttpResponse doIndex() { + User u = User.current(); + return HttpResponses.text(u != null ? u.getId() : "anonymous"); + } + } +} diff --git a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java index f5e746d161..8737d26974 100644 --- a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java +++ b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java @@ -121,7 +121,6 @@ public class ApiTokenPropertyTest { wc.getOptions().setThrowExceptionOnFailingStatusCode(true); WebRequest request = new WebRequest(new URL(j.getURL().toString() + foo.getUrl() + "/" + descriptor.getDescriptorUrl()+ "/changeToken"), HttpMethod.POST); - wc.addCrumb(request); HtmlPage res = wc.getPage(request); // TODO This nicer alternative requires https://github.com/jenkinsci/jenkins/pull/2268 or similar to work diff --git a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java index 1c7c32d43f..0b471dd2b3 100644 --- a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java +++ b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java @@ -7,8 +7,6 @@ import hudson.ExtensionList; import hudson.model.UnprotectedRootAction; import hudson.model.User; import hudson.util.HttpResponses; -import hudson.util.Scrambler; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -20,6 +18,8 @@ import org.xml.sax.SAXException; import java.io.IOException; import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import static org.junit.Assert.*; @@ -46,87 +46,68 @@ public class BasicHeaderProcessorTest { @Test public void testVariousWaysToCall() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); - User foo = User.get("foo"); - User bar = User.get("bar"); + User foo = User.getById("foo", true); + User.getById("bar", true); wc = j.createWebClient(); // call without authentication - makeRequestWithAuthAndVerify(null, "anonymous"); + makeRequestAndVerify("anonymous"); spySecurityListener.authenticatedCalls.assertNoNewEvents(); spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents(); // call with API token - ApiTokenProperty t = foo.getProperty(ApiTokenProperty.class); - final String token = t.getApiToken(); - makeRequestWithAuthAndVerify("foo:"+token, "foo"); - //TODO verify why there are two events "authenticated" that are triggered - // the whole authentication process seems to be done twice + wc = j.createWebClient(); + wc.withBasicApiToken("foo"); + makeRequestAndVerify("foo"); spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo")); // call with invalid API token - makeRequestAndFail("foo:abcd"+token); + wc = j.createWebClient(); + wc.withBasicCredentials("foo", "abcd" + foo.getProperty(ApiTokenProperty.class).getApiToken()); + makeRequestAndFail(); spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo"); // call with password - makeRequestWithAuthAndVerify("foo:foo", "foo"); + wc = j.createWebClient(); + wc.withBasicCredentials("foo"); + makeRequestAndVerify("foo"); spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo")); // call with incorrect password - makeRequestAndFail("foo:bar"); + wc = j.createWebClient(); + wc.withBasicCredentials("foo", "bar"); + makeRequestAndFail(); spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo"); - + wc = j.createWebClient(); wc.login("bar"); spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("bar")); spySecurityListener.loggedInCalls.assertLastEventIsAndThenRemoveIt("bar"); // if the session cookie is valid, then basic header won't be needed - makeRequestWithAuthAndVerify(null, "bar"); + makeRequestAndVerify("bar"); spySecurityListener.authenticatedCalls.assertNoNewEvents(); spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents(); // if the session cookie is valid, and basic header is set anyway login should not fail either - makeRequestWithAuthAndVerify("bar:bar", "bar"); + wc.withBasicCredentials("bar"); + makeRequestAndVerify("bar"); spySecurityListener.authenticatedCalls.assertNoNewEvents(); spySecurityListener.failedToAuthenticateCalls.assertNoNewEvents(); // but if the password is incorrect, it should fail, instead of silently logging in as the user indicated by session - makeRequestAndFail("foo:bar"); + wc.withBasicCredentials("foo", "bar"); + makeRequestAndFail(); spySecurityListener.failedToAuthenticateCalls.assertLastEventIsAndThenRemoveIt("foo"); } - private void makeRequestAndFail(String userAndPass) throws IOException, SAXException { - makeRequestWithAuthCodeAndFail(encode("Basic", userAndPass)); - } - - private String encode(String prefix, String userAndPass) { - if (userAndPass==null) { - return null; - } - return prefix+" "+Scrambler.scramble(userAndPass); - } - - private void makeRequestWithAuthCodeAndFail(String authCode) throws IOException, SAXException { - try { - makeRequestWithAuthCodeAndVerify(authCode, "-"); - fail(); - } catch (FailingHttpStatusCodeException e) { - assertEquals(401, e.getStatusCode()); - } - } - - private void makeRequestWithAuthAndVerify(String userAndPass, String username) throws IOException, SAXException { - makeRequestWithAuthCodeAndVerify(encode("Basic", userAndPass), username); + private void makeRequestAndFail() throws IOException, SAXException { + makeRequestWithAuthCodeAndFail(null); } - private void makeRequestWithAuthCodeAndVerify(String authCode, String expected) throws IOException, SAXException { - WebRequest req = new WebRequest(new URL(j.getURL(),"test")); - req.setEncodingType(null); - if (authCode!=null) - req.setAdditionalHeader("Authorization", authCode); - Page p = wc.getPage(req); - assertEquals(expected, p.getWebResponse().getContentAsString().trim()); + private void makeRequestAndVerify(String expectedLogin) throws IOException, SAXException { + makeRequestWithAuthCodeAndVerify(null, expectedLogin); } @Test @@ -136,7 +117,7 @@ public class BasicHeaderProcessorTest { wc = j.createWebClient(); String[] basicCandidates = {"Basic", "BASIC", "basic", "bASIC"}; - + for (String prefix : basicCandidates) { // call with API token ApiTokenProperty t = foo.getProperty(ApiTokenProperty.class); @@ -144,7 +125,7 @@ public class BasicHeaderProcessorTest { String authCode1 = encode(prefix,"foo:"+token); makeRequestWithAuthCodeAndVerify(authCode1, "foo"); spySecurityListener.authenticatedCalls.assertLastEventIsAndThenRemoveIt(u -> u.getUsername().equals("foo")); - + // call with invalid API token String authCode2 = encode(prefix,"foo:abcd"+token); makeRequestWithAuthCodeAndFail(authCode2); @@ -162,6 +143,31 @@ public class BasicHeaderProcessorTest { } } + private String encode(String prefix, String userAndPass) { + if (userAndPass==null) { + return null; + } + return prefix + " " + Base64.getEncoder().encodeToString(userAndPass.getBytes(StandardCharsets.UTF_8)); + } + + private void makeRequestWithAuthCodeAndVerify(String authCode, String expectedLogin) throws IOException, SAXException { + WebRequest req = new WebRequest(new URL(j.getURL(),"test")); + req.setEncodingType(null); + if (authCode!=null) + req.setAdditionalHeader("Authorization", authCode); + Page p = wc.getPage(req); + assertEquals(expectedLogin, p.getWebResponse().getContentAsString()); + } + + private void makeRequestWithAuthCodeAndFail(String authCode) throws IOException, SAXException { + try { + makeRequestWithAuthCodeAndVerify(authCode, "-"); + fail(); + } catch (FailingHttpStatusCodeException e) { + assertEquals(401, e.getStatusCode()); + } + } + @TestExtension public static class WhoAmI implements UnprotectedRootAction { @Override @@ -181,7 +187,7 @@ public class BasicHeaderProcessorTest { public HttpResponse doIndex() { User u = User.current(); - return HttpResponses.plainText(u!=null ? u.getId() : "anonymous"); + return HttpResponses.text(u!=null ? u.getId() : "anonymous"); } } diff --git a/test/src/test/java/jenkins/security/Security380Test.java b/test/src/test/java/jenkins/security/Security380Test.java index 5d3475df92..c7b3e90a6f 100644 --- a/test/src/test/java/jenkins/security/Security380Test.java +++ b/test/src/test/java/jenkins/security/Security380Test.java @@ -67,6 +67,7 @@ public class Security380Test { JenkinsRule.WebClient wc = j.createWebClient(); Page page = wc.goTo("listJobs", "text/plain"); + // return "0\r\n" Assert.assertEquals("expect 0 items", "0", page.getWebResponse().getContentAsString().trim()); } diff --git a/test/src/test/java/lib/form/PasswordTest.java b/test/src/test/java/lib/form/PasswordTest.java index 0e3ed7c783..8eef7d40ff 100644 --- a/test/src/test/java/lib/form/PasswordTest.java +++ b/test/src/test/java/lib/form/PasswordTest.java @@ -27,16 +27,9 @@ import com.gargoylesoftware.htmlunit.Page; import com.gargoylesoftware.htmlunit.html.HtmlInput; import com.gargoylesoftware.htmlunit.html.HtmlPage; import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput; -import hudson.Extension; import hudson.cli.CopyJobCommand; import hudson.cli.GetJobCommand; -import hudson.model.Describable; -import hudson.model.Descriptor; -import hudson.model.FreeStyleProject; -import hudson.model.Item; -import hudson.model.JobProperty; -import hudson.model.JobPropertyDescriptor; -import hudson.model.User; +import hudson.model.*; import hudson.util.FormValidation; import hudson.util.Secret; import java.io.ByteArrayOutputStream; @@ -51,8 +44,10 @@ import org.acegisecurity.Authentication; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; -import org.jvnet.hudson.test.HudsonTestCase; +import static org.junit.Assert.*; + +import org.junit.Rule; +import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.MockAuthorizationStrategy; @@ -62,25 +57,43 @@ import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; -public class PasswordTest extends HudsonTestCase implements Describable { - public Secret secret; +public class PasswordTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); - public void test1() throws Exception { - secret = Secret.fromString("secret"); - HtmlPage p = createWebClient().goTo("self/test1"); + @Test + public void secretNotPlainText() throws Exception { + SecretNotPlainText.secret = Secret.fromString("secret"); + HtmlPage p = j.createWebClient().goTo("secretNotPlainText"); String value = ((HtmlInput)p.getElementById("password")).getValueAttribute(); assertFalse("password shouldn't be plain text",value.equals("secret")); assertEquals("secret",Secret.fromString(value).getPlainText()); } - public DescriptorImpl getDescriptor() { - return jenkins.getDescriptorByType(DescriptorImpl.class); - } + @TestExtension("secretNotPlainText") + public static class SecretNotPlainText implements RootAction { + + public static Secret secret; - @Extension - public static final class DescriptorImpl extends Descriptor {} + @Override + public String getIconFileName() { + return null; + } + + @Override + public String getDisplayName() { + return null; + } + + @Override + public String getUrlName() { + return "secretNotPlainText"; + } + } @Issue({"SECURITY-266", "SECURITY-304"}) + @Test public void testExposedCiphertext() throws Exception { boolean saveEnabled = Item.EXTENDED_READ.getEnabled(); Item.EXTENDED_READ.setEnabled(true); @@ -93,24 +106,28 @@ public class PasswordTest extends HudsonTestCase implements Describable - + -- GitLab From 24442d8d24e8c35441df9bb43ed736d2a61166b1 Mon Sep 17 00:00:00 2001 From: surenpi Date: Sat, 16 Dec 2017 17:54:20 +0800 Subject: [PATCH 0166/1380] Add Chinese translation for parameter build --- .../help/parameter/boolean-default_zh_CN.html | 3 +++ .../webapp/help/parameter/boolean_zh_CN.html | 3 +++ .../help/parameter/choice-choices_zh_CN.html | 3 +++ .../webapp/help/parameter/choice_zh_CN.html | 3 +++ .../help/parameter/description_zh_CN.html | 3 +++ .../help/parameter/file-name_zh_CN.html | 3 +++ .../webapp/help/parameter/file_zh_CN.html | 22 +++++++++++++++++++ .../webapp/help/parameter/name_zh_CN.html | 6 +++++ .../main/webapp/help/parameter/run_zh_CN.html | 4 ++++ .../help/parameter/string-default_zh_CN.html | 3 +++ .../webapp/help/parameter/string_zh_CN.html | 3 +++ 11 files changed, 56 insertions(+) create mode 100644 war/src/main/webapp/help/parameter/boolean-default_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/boolean_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/choice-choices_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/choice_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/description_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/file-name_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/file_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/name_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/run_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/string-default_zh_CN.html create mode 100644 war/src/main/webapp/help/parameter/string_zh_CN.html diff --git a/war/src/main/webapp/help/parameter/boolean-default_zh_CN.html b/war/src/main/webapp/help/parameter/boolean-default_zh_CN.html new file mode 100644 index 0000000000..e013875b37 --- /dev/null +++ b/war/src/main/webapp/help/parameter/boolean-default_zh_CN.html @@ -0,0 +1,3 @@ +
+ 定义当前参数的默认值。 +
\ No newline at end of file diff --git a/war/src/main/webapp/help/parameter/boolean_zh_CN.html b/war/src/main/webapp/help/parameter/boolean_zh_CN.html new file mode 100644 index 0000000000..f197c1c94c --- /dev/null +++ b/war/src/main/webapp/help/parameter/boolean_zh_CN.html @@ -0,0 +1,3 @@ +
+ 定义一个简单的布尔值参数,你可以在构建时使用,或者作为环境变量,或者在配置中做变量替换。对应的字符值为'true'或者'false'。 +
\ No newline at end of file diff --git a/war/src/main/webapp/help/parameter/choice-choices_zh_CN.html b/war/src/main/webapp/help/parameter/choice-choices_zh_CN.html new file mode 100644 index 0000000000..d6e7f29bb4 --- /dev/null +++ b/war/src/main/webapp/help/parameter/choice-choices_zh_CN.html @@ -0,0 +1,3 @@ +
+ 备用选项,每行一个。第一行的将作为默认选项。 +
\ No newline at end of file diff --git a/war/src/main/webapp/help/parameter/choice_zh_CN.html b/war/src/main/webapp/help/parameter/choice_zh_CN.html new file mode 100644 index 0000000000..87a6803c31 --- /dev/null +++ b/war/src/main/webapp/help/parameter/choice_zh_CN.html @@ -0,0 +1,3 @@ +
+ 定义一个简单的字符串列表,提供给用户选择。,你可以在构建时使用,或者作为环境变量,或者在配置中做变量替换。 +
\ No newline at end of file diff --git a/war/src/main/webapp/help/parameter/description_zh_CN.html b/war/src/main/webapp/help/parameter/description_zh_CN.html new file mode 100644 index 0000000000..50c8c588c5 --- /dev/null +++ b/war/src/main/webapp/help/parameter/description_zh_CN.html @@ -0,0 +1,3 @@ +
+ 后续展示给用户看的描述信息。 +
diff --git a/war/src/main/webapp/help/parameter/file-name_zh_CN.html b/war/src/main/webapp/help/parameter/file-name_zh_CN.html new file mode 100644 index 0000000000..659619285c --- /dev/null +++ b/war/src/main/webapp/help/parameter/file-name_zh_CN.html @@ -0,0 +1,3 @@ +
+ 指定上传文件的路径,相对于工作空间。(例如:jaxb-ri/data.zip) +
\ No newline at end of file diff --git a/war/src/main/webapp/help/parameter/file_zh_CN.html b/war/src/main/webapp/help/parameter/file_zh_CN.html new file mode 100644 index 0000000000..6752d38dc6 --- /dev/null +++ b/war/src/main/webapp/help/parameter/file_zh_CN.html @@ -0,0 +1,22 @@ +
+ 从浏览器表单提交中接受一个文件,作为构建参数。上传后的文件将会放在工作空间中指定的位置,你 + 可以在构建任务中访问并使用它。 +

这对于很多场景下是有帮助的,例如:

    +
  1. 让用户基于他们构建出来的成品运行测试。
  2. +
  3. 允许用户在自动化上传、发布、部署过程中替换文件。
  4. +
  5. 通过上传一个数据集来处理数据。
  6. +
+

+ 表单提交中的文件名称就是文件的路径,并且是在环境变量中可见的。例如:你把文件路径设置为 + abc.zip,然后${abc.zip}会从浏览器传递给你原始的文件名称。 + (例如my.zip。)这里的名称不会包含目录部分。 +

+

+ 文件的上传是可选的。如果用户不上传任何文件,Jenkins只是简单地跳过这个参数并不会替换 + 任何文件(但是也不会删除任何已经存在的文件)。 +

+

+ 在命令行模式下参数-p对于构建命令build会选择一个本地文件 + (-remoting),或者从标准输出中读取。(在字符模式下,只允许定义一个文件参数) +

+
\ No newline at end of file diff --git a/war/src/main/webapp/help/parameter/name_zh_CN.html b/war/src/main/webapp/help/parameter/name_zh_CN.html new file mode 100644 index 0000000000..bfabfc9278 --- /dev/null +++ b/war/src/main/webapp/help/parameter/name_zh_CN.html @@ -0,0 +1,6 @@ +
+ 参数名称 + +

+ 这些参数会当作环境变量暴露给构建任务。 +

\ No newline at end of file diff --git a/war/src/main/webapp/help/parameter/run_zh_CN.html b/war/src/main/webapp/help/parameter/run_zh_CN.html new file mode 100644 index 0000000000..7fb3156e43 --- /dev/null +++ b/war/src/main/webapp/help/parameter/run_zh_CN.html @@ -0,0 +1,4 @@ +
+ 定义一个运行参数,用户可以选择一个确定的工程。网站的url地址将会被当作环境变量暴露出来,你可以在构建时使用,或者作为环境变 + 量,或者在配置中做变量替换。通过Jenkins可以查询到更多的信息。 +
\ No newline at end of file diff --git a/war/src/main/webapp/help/parameter/string-default_zh_CN.html b/war/src/main/webapp/help/parameter/string-default_zh_CN.html new file mode 100644 index 0000000000..83526bf997 --- /dev/null +++ b/war/src/main/webapp/help/parameter/string-default_zh_CN.html @@ -0,0 +1,3 @@ +
+ 定义该字段的默认值,允许用户在保存时修改为真实的值。 +
\ No newline at end of file diff --git a/war/src/main/webapp/help/parameter/string_zh_CN.html b/war/src/main/webapp/help/parameter/string_zh_CN.html new file mode 100644 index 0000000000..a700358140 --- /dev/null +++ b/war/src/main/webapp/help/parameter/string_zh_CN.html @@ -0,0 +1,3 @@ +
+ 自定义个简单的文本变量,用户可以输入字符串,在构建时使用,或者作为环境变量,或者在配置中做变量替换。 +
\ No newline at end of file -- GitLab From 2476d1417ecfbfb9864b86d544056421e67b0679 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Sat, 16 Dec 2017 13:31:28 +0100 Subject: [PATCH 0167/1380] [JENKINS-48365] Restrict InstallUtil#NEW_INSTALL_VERSION --- core/src/main/java/jenkins/install/InstallUtil.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/jenkins/install/InstallUtil.java b/core/src/main/java/jenkins/install/InstallUtil.java index d89cac2df5..5b3f95555b 100644 --- a/core/src/main/java/jenkins/install/InstallUtil.java +++ b/core/src/main/java/jenkins/install/InstallUtil.java @@ -70,6 +70,7 @@ public class InstallUtil { private static final Logger LOGGER = Logger.getLogger(InstallUtil.class.getName()); // tests need this to be 1.0 + @Restricted(NoExternalUse.class) public static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0"); private static final VersionNumber FORCE_NEW_INSTALL_VERSION = new VersionNumber("0.0"); -- GitLab From 26262f873275c99005b1ddc59c19c21edbe32bf1 Mon Sep 17 00:00:00 2001 From: Hubert Woszczyk Date: Sun, 17 Dec 2017 14:28:18 +0100 Subject: [PATCH 0168/1380] =?UTF-8?q?French=20localization:=20Changed=20?= =?UTF-8?q?=C3=A9=20to=20\u00E9=20(#3159)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/src/main/resources/lib/form/advanced_fr.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/lib/form/advanced_fr.properties b/core/src/main/resources/lib/form/advanced_fr.properties index 7f82aecfd4..c998a30a84 100644 --- a/core/src/main/resources/lib/form/advanced_fr.properties +++ b/core/src/main/resources/lib/form/advanced_fr.properties @@ -20,5 +20,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -Advanced=Avanc +Advanced=Avanc\u00E9 customizedFields=Un ou plusieurs champs de ce bloc ont \u00E9t\u00E9 \u00E9dit\u00E9s. -- GitLab From eac29da4f1fbf98d85747f88f3f8ee48f369034c Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 17 Dec 2017 16:39:29 -0800 Subject: [PATCH 0169/1380] [maven-release-plugin] prepare release jenkins-2.96 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 1cb344f3bf..8f76554442 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.96-SNAPSHOT + 2.96 cli diff --git a/core/pom.xml b/core/pom.xml index 1c3e84253f..49f2207f71 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.96-SNAPSHOT + 2.96 jenkins-core diff --git a/pom.xml b/pom.xml index 61644dbd00..dc96249e56 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.96-SNAPSHOT + 2.96 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.96 diff --git a/test/pom.xml b/test/pom.xml index 8745d54534..6b1ddbc3ca 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.96-SNAPSHOT + 2.96 test diff --git a/war/pom.xml b/war/pom.xml index 13086ab727..afa71b7fe4 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.96-SNAPSHOT + 2.96 jenkins-war -- GitLab From c32b6d807a5647df8b5347c8acc1b42024b597bf Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 17 Dec 2017 16:39:29 -0800 Subject: [PATCH 0170/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 8f76554442..6042680574 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.96 + 2.97-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 49f2207f71..4517bab996 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.96 + 2.97-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index dc96249e56..4ce5365476 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.96 + 2.97-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.96 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 6b1ddbc3ca..da6245605c 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.96 + 2.97-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index afa71b7fe4..9147f0ecc3 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.96 + 2.97-SNAPSHOT jenkins-war -- GitLab From 5098524513883a48d07fd32d5a6f058d68adb8b8 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Mon, 18 Dec 2017 10:59:47 -0500 Subject: [PATCH 0171/1380] Add failing test that reproduces JENKINS-48604 --- .../install/LoadDetachedPluginsTest.java | 22 ++++++++++++++++++ .../upgradeFromJenkins2WithDependency.zip | Bin 0 -> 150149 bytes 2 files changed, 22 insertions(+) create mode 100644 test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithDependency.zip diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java index eecc1e2b49..fffb220d27 100644 --- a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java +++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java @@ -26,10 +26,12 @@ package jenkins.install; import hudson.ClassicPluginStrategy; import hudson.ClassicPluginStrategy.DetachedPlugin; +import hudson.Plugin; import hudson.PluginManager; import hudson.PluginManagerUtil; import hudson.PluginWrapper; import hudson.util.VersionNumber; +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -44,6 +46,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -81,6 +84,25 @@ public class LoadDetachedPluginsTest { }); } + @Issue("JENKINS-48604") + @Test + @LocalData + public void upgradeFromJenkins2WithDependency() { + VersionNumber since = new VersionNumber("2.0"); + rr.then((JenkinsRule r) -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + assertThat("Plugins have been detached since the pre-upgrade version", + detachedPlugins.size(), greaterThan(1)); + assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", + getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); + Plugin scriptSecurity = r.jenkins.getPlugin("script-security"); + assertThat("Script-security should be installed", scriptSecurity, notNullValue()); + assertThat("Dependencies of detached plugins should not be downgraded", + scriptSecurity.getWrapper().getVersionNumber(), greaterThan(new VersionNumber("1.18.1"))); + assertNoFailedPlugins(r); + }); + } + @Test public void newInstallation() { rr.then(r -> { diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithDependency.zip b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithDependency.zip new file mode 100644 index 0000000000000000000000000000000000000000..557841273ad6240326625333b6bda5b61586225b GIT binary patch literal 150149 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'_RD7)2@On9eRhfG56 zbO!c~M}B_G40jD#R(h@&uy4%p6qoppISgtL*ridFX#WmTMxoCYYm^v$hq>GX9OfX> z;1pABxtqy7VmVsK+%q8T32ilmXcqVW1?BV^8~EgF<-$F_YbDknLk7n^yOm9R;~3cr z+bgvRm?RxaDI-li(a&g>0&=D^7?dJym^@v-WDMuIzC_Smr4`R+Df@L5{B?)=Jpq2* zJc~lUm`*?5$G`d`3+v|ko;T~V0M+y~5EI!?0Jh$RZNPu^q5>Kl<5A0X#1qluKjxXc zJWx=!kiQcBRw9UT^L}VI&OBK`9J!b=TTUnA-aOaK^X(zT5ei|HuuW@&4clTbz^_c3 z#Z6f@jOKAAlMLzZSTyvCIX2{-T%_$KL(#}Ys)0BRd0WwkbR4HS(X%TqJ@YTUL1~4( z@@@AYn2P$S8u%_o+GJK&drzrT7oJ9^?@8sW`lVFg$&rt2npfgYW_Z(J6ix%d@5U15 zA|C>ITZ)u8z#NBRak9HRXNxX=6zYyiS{kG)$`923ReMNMGoG{SAE`fFW zwk*i7lSVG!IAR?bWWYu>nnhG)`O(Mw_`3fs7oYLbw(a%jpZ&b0U-U<4{5{1q^yW54 zCs#K{_a=8G7Y_gXr`mWFgVI1iWq)WGS!>yqMiyj%<5nzGgOP*~2tt2#{Gvnc}_vX*%dKbVm)(8J_Rh!eokJ{_OSG#0&E|#}hYU$04-o4sdBm zyt(?%>O9yOe_?RI>~JG^at{ZzB{?170y+>X1MuD;jAgZQD1uSncO;vMX|-|r(9Ea( z0F<Fq>%%z z+JoEuOTaz zae^iy>~wqmomI$bl6(i;;PXSi()HCbG{wER&krxGLX1kwK`M(PjnQaRC|v=vpPV_X z=^@|#CS;6-%#oJ{dIW2uedaBfvh>hZF^k0K4@>jATmJ8*&0w8+?e4yO>F>eE=r=!Y ztG`@XAgWb-5xZa129os4cQLF`3850Wt;F-Ui8w>MiEOjbfXyk=kJtDP{{b>asRLUi z1GMN@UnD=A^1r(f(KpGu$4PA!I$Z%CQ z@wi60Sfgl>pt-ph-2Mi#{5;z$7mkD8w=p>R%GCRyODcZ*!XT=yxYdmw)ZKY~yFwTn zb}Q6=|H_QhO&dg5nnCJ}Lz$tDu%(p?Jl}(8lWveoRUg>vz+5EN9OiOEipn+=72}nw zYY#xzEio35sPd~Ks)HW}+JM8@q6n+j`%YtF*bzFFdt60J7vmwi^_XGS;)HFn+5ruU z-=;O!sOSjDlg7wBvTz6Eb5hi#%PIc8T6BxZIDsthjKf&v^(NwS(2$X{?w4J&!})G> zD(RbdsNNilU-5wB@#H$}n5Ku*VELyy`rfKN`7S3->S3!uTOx~&<+B&;Do@U~(H2>G zB&LEY8OpflgiIyLl;875rF9*PE^}^cP+pvAMzz>eqgW9m3Yr{cu-cnl<+=_-7fkUY zUX%pR;Hx!L#Ad4k&t6igG?kVmqAzmNIP2SrC)@0Doq^2{#~+|YQ;1#^eO?pDg-gFL zksOg^$upFu)?U=abVq#?i;rHG0?%`o+z8YBsAe7)08OGr17IBeM4$c$I z_Yx|h^Yk<*AG1>o-R<-jQd25-j`ZXKx`c7!2&0sDlXoT%XHTft<*jGKFZ=TZePBV{ z*Ei%E{7AV)J(Jn@4hGN7H>uqP3zf(GMv=Wqk{*4*kt0zmvesN znf=%C^ZGag>j^6tOf151&O!xQ3>38%zUUe0FqTgq((E@HtgMvrc%9a5={Dz^<(#JU zz_MKjV@uHnzI4QKz?oj9DpZ75)?b$`#2c^ba^U0i%W3mXrN-;EBvrm{#AdY86Lv#M z)cIt0wp;t+f@^X-Hr!X2&ePzAS{JRMHk#Y73yp52XTj$TC1)qokev%Q8J-Ohx?idI z$!{A(HE#s7Bl)JRAL0*_7%XK7HD31*ayv2F<8}6~LSXl{)jU=Whz&1I#QJUlsB*i# zCLW(EYH?X;we*JwVHIOeR719-r;Ddf{y{Pl2mlvTNMDqYicJRvyjPS&WM zewTpE?Q)bYeCN#D-$ne4j~=TE^mO}w3=H{-zV$EHJMJ@eI!Mx$kzrorJ?c{5mk35_ zmr7D2NsF%pI@b0?Rt(J`yOTL7JJLm-=puqc0c8ZjLloGdTnZ+H+Y2_BtkEBseBWd= zNBBcY5-1C=w2<}u9T%K+6UIHCulD3?<5sn11nDYCINhBLSJYLoDCl0FYF=f)O;7a8WBG zfOe5dxTJE!k|1Fm*3PD{`su~~>wEcCDM<9Kw4b(=zv09{HvVxC5Yp`nLmDlDs3(ZK z$%7zag;Hx!b7-fMLWJ*in~C}}S!pHq6}-xR0}~>x0ynG=@qGF$$bbWdO!M@+%k>?z zvehj5o#2>$?biYSkV0xY zV>vD7wbB)9CNd(OZ8bO0l2dgKjaL=b3j1#BV`pO`|L$})n;Ys-g*6tr90tqg*d?gY zX*8hs&qEiY1x2X(l@f@49HK0wTbuZ&e&Q1Acc%*+sg*U&=hhYx98*gdJ)4SFqGnt2 zt%zzrsxO1S1MehXz>!pt8Dl-ZdPHm5tIvdXhF~p7*6Ge#3Ik2_ePt@gWOPF~Z=hU{ zp?16uT(4
    d`!c%x?@8w`mGt83}fw!pogJ)XL>lnX*xRwt5b*o0T=Pl|i8e&R5I zGD`iF_OJ3$)8*9BZO1u!C|SAEUGw!?y!g>zHq}V8J=ZV_C67q#Q9E!r#h=kkcAcGh z4W4DGOj<|<$Z4-8y`L2r<7#f_053EbqsSh*{jFin?!`IZ?o1vc&Q{j z_m&(aO4Uv3(Wb)p64z9)<@6*7Y8c=yT#`CClQw3tdyvFqhHs%5{8PuS90kufq3MJA z-xEn>J_Gs*?}Xpa_OJ>JkUZU)fZtk=Iztda2x8}UvBWq(PK(}z=H8!si9fE0F+E52 zMh-H0!F@UsdId+B%VXX_J0cOGfn~=o;S)~T72?+Od>ZZZxCdMB<9L?#q=_%i(N1P> z2nU%`l49S?$PeOQ(Rj|8og-?1a48UGN+vS7FW#|!@7qrIO@GM&ws{CTsQYQj{urta zKll4d6|$5CR$*lU(?5keVckj=>X_R3HR2ED8WGFwH!qZq*5?r7E^I1%+?zSsK{{;4&{X7o$TXKw@YN z`58Fr>r|x2q-SWVIHI|Q?ly%(Of_1Ex?WY<%bKX`a&3@D+o-9vTJ@PUFHxRb?LAQ1 z)hd+gXh$pT7)jmb*bKSRYYd{!-5R$VTW&MEIT8fHnv70462A~Cc8%UvbF4UXRxacz zPQ2Q!`wCXC!xAabrar|wQR9EZhDy0Qhw36zll1ns&SNysYBUKi)Q7-=r`Z*oPqxJ- zQ~WR>@0z7?vQsFp+Fl+irV+~0kU1>>)6=GKPkzkJqL3GfRhCim$v^|1|A$>+nsjR| zTY!R;SMpA1G~n!Ep}+GLwsyUqLQ{=by()Hep9-+()dLbs+QY7VnS`98 zS4I)N7u7MD^Bo-crL%Km7Y*$*pk}J9A)^emgCfl2;He2jKshD~6D_jtdfDj-!DvI^ z9=8KAm1Bk|+t8`?j>tK>q@Rt^pwf;`%vYk|fw=oBNm$T0;Izp{3|s;ET4|-QpqRm5 zmZb;K1ByDg`h+4^MXm0(zva{mM+^Xh*`v+}mi480dqy_w~G& z&L#i=aSbn?7x65b!!Xw!hPd|*2V}%4Qhrvo$lyISh2YHyPRS1_bA}==owr2$>X=Ra zgYb49!e4)=Xweyt+h-fK<2R}9DM~q&$KLP1_$=fB&Xr&H@rv9Y(5v>DGmr5Jo8et{ zgc|B?`_R}G7|b)9!gx^kNZC-+KSIbC70{-8vgBfhCJSCGmjsobpjApO9JSe}W@8QrupzuAwx7-nZl?{7=ms%DNI}ycC)^R}kzi?&MH~{G zsgA_w74HE-+Cu)D4wzgPW;($Frp7~R|FGe>BN(m)aGPBYfPhw0Dm!^Ip41s|R3_@9 z$fl5Id1@xEa7)G1Hl>hCNPN&|&(Mi+>rgx_mlM?QS48wR1C&POzeE!oz(73(x4%Qw zq$Y`8Vx6Jm88>H_=_g!-HLr17?#~vU*a^){2sFFdas#IS*VYT$#yBeZV<)!rHlqs2 zwdS-Ov`jQeal%y$HHO5T4$$-I*SYMAfBUPLw8zO>HZwE2_du~6`Iry}RkI`3%tueP zZ4kjZtG&8;TI%^!HLRKZX#5E_69oaS3@cYW#U*XA_!`u_bc%>CFOq&((Seo?P;Jll z#)i>_L7ZGpX^ZzQR9rB$$=Or%aOO+^H$ce0-;~`msb?crLbtH@=qm;@E-Qso9ngxrGFFXEPVefN^dB&hz?f%`EYS zyPex^GcpTkKQ#7z@*QNEc2JHj*ej8>#!Jm6>DIEhpf>5Gi6d7Pu}RV$@uEa7;7Yk; zxn@&bee(R1V9lNUX94ou9I@36#X4;=at;_U%aV2y$dpWJ3uYQ34%k~BlO&zTp)4py zZ@rk_h@xEzir4;%lTkC!Vpt|oB`f?!4i2YvaT+2TT0{Ax99xF7zNO*D6k0VbowQp( zsrT)dw5kB~{X|=5k1MIKKqN+d2H=)ucHr#c+x14W_T!ue*$6c5w_~C>5lV!f!)bTJ|Z5kC}=~ukosQG^%`*rf`D^EhcurkrZtb z7fqnmV`U#xAv#3`yA%!~)rZoE^m^!;N`8V+w7?{+Zc9?RPtuCpE57XzJ+4Kr;?rC4 zYxH-wh4G5(cN6_G90eXYKl2Tvl3G5U+IIg`ofgftsp+3e+LrR6p3}J)vMlh6pLf?g zRhQ&_RFWro=t}jXD#!1fw@-MTk$$Hu&*ec~BcM<_D?TM{T9md;Qprx`qAo8>_04WxNxMm*?Q6l2McOF6+<|+z+h!5) zd)j6O_x+@gFxYF7*QfmI18nFDl}kO9OC30_vF)*$_;erm%-G~Ehjf0dZV!|j4Ec(9 zSgrib)K`OVyuFCtfrgI|a&H1hlKc>0Hx1?Qiy?g8|glSCWW zJ0l}zsi~o;$x2xXse(x@E3dhOp=QFk<`O>l+w#@?z$`D-JTRRbXOU zEEjD!r*m$(cc(Y-`}@AZZ^M5bn2D`?58L@GwNiTDn08=}fh^gTASx!TChSAhZPeA~ zGr^QaZ<$E3(znuKjK!S1{Vp$NP*e*q7k-&#qq{EtcQ36^ohL&dYf; z6Ui_mt*TYpJ2!r(oN_G6xe!;Uk1EO%E0}G+X&uK`ob-cZE&k_Z!!)!k6`SqdVA<<@ zt{WW~EaTLg2t`=h7?G790=sMr3-h_VC~GH}LLFJ`Wl2&`2w;=;xvkydF~mm!?k%K^ zYL$xg*z%JRcN%4j%Y`_cCY^~lf+`*|A7uz3;Y7mPj1Ts+@ke2dTL#qLY71rUj~G=T z@OjGK#l3R#S1^dIRnd8E&!L zrl=AsmGqrtC{2_b{oW2T&8tev#EyMHS!9i@4D3ztm3z(8KOSoQ8Vi4plB*;a+Ww!f zaQ^1>gZ|&s4|SPo9Slz%R^>XS36RYX6Xcvz-p-#0LST}6z0u!ktSf!-_%K`tbU!B1 zvfa?8Lw*7$5$=WAs{J_y_gvvJgdwaHTe>-G9sh>+{ra$8CIe(ykih44nLm;+SIA)l z0loq>G7{`t%uXVCxtwUZC?&d4M70|8VRbzWiMYDUx-w?_<~;6FbDQpA(x0;ri2NbU z014pe7qwAn^h4KNKA`F#{bgSO4OO92INx}U24t=Ak_IYP)1j^WG_lA`O_P38DaRMn zE}KPKCYAbPG$WDfY*NzAw~LV7u{=;uAIaUis?6?; zJc@;U->(7Bsw}6Rl1HvE7-K?uobkGRP8R*rXYa|W<^^)+51hYPTBI>~XyG!!=f#7M-h*TRGC5-`Lb$h*Xkn!cA~Ijb8zAfT{r z7^ee#0kjyRxr_@puaAv*_{EG6=rB5X(hzRV1uh}A6qMZ(JFqjDXFKl)llQB{O>m4J zQyJI-zoEKyis+#2W{2;g7=ozy6^mY{q`u*mMcZLCld^pA@-|=~Pk?^49;7M4)DG zj;-5z!S+_oEPtpsK&y5zhN?Nh((0fq#??6Nd)_vE0dfCEmhoYFzt|{HQ|=2^*?ujS zMlsA6i&K;IlbNEEvX9417Ny3v*`2*J{N4mK{|FD{Ezv*HR4m=0G*I|eitf7XSNsWC zO}$~P{!3rG*6#5cVY?>Q=8oUYvMQcCWLFlkcDtfbHbYu{2Oc^@+vcb&1L7h$!#mS%)RQH6;%RO|lxg9& zvlPo-F7KZX&Mn}m)zx$HIG3#KV{nhz)Y}Zri1nmM#|F~4ObulvSJbzfc?5{!SF=!d z!H0~CD7&H#af;r9z{MgX_ny%>EHCg*^%h#|C&b8j)EOCqLP}K@*0;1{1_8I!IW>wg zx=g>z+_=-IP<^?~qn*aPgunUfCkM0Bk{`D1&)|f`W6ZH>pF5ZzsfC3x;T~Nh+EmlS zut2MW5B3x_Gl)Je}SY1;oc;FHja7F_ZlK45=OkuuMZa15)61bD1K7-xe~B z#Zvpg@G-FjhY|^zOcKk}zM9qOob7Ogi)F*^xI#j|2;KrQe57MJd-X?f{2Bs(4vwUU z(AF9d0Ko3=RIj1`cl8=68B{(*k5=vKh~m)KWS?y}I*_ZvT@3`XC^2F{;1OZ_R~N?2 zyC+)r_X$qgr$UL6Z^0xbAB6f2KtOdH%~NxVn^s#~m1bAke|SDYty2T}XXx;C2n3~OKiZwP$?Ser^M3+N@@9- zA)qf}Zpu+1+AyZ;YGB?kBbg7FA)cDR*WTtOXPZ{Q z7v0@gMOl+=q_p)yNnl`1q<+9jN87WjiZu4`DSIcQ|sDcfq z=CwO%-1Xe$T)W|2a~!)}&W}8TsQAP))SYq0OHtO5@y(k32jQr8?p+F#SHccH1?yAX zj+YYC#(SP02(P}`yv`7}Qza-6G}y2*P#Va~^0WO)tU@5vHor|QgB8CZ*LGRsPVt%F zKO_+ojZ!fyN>4F9dNEjSDN(KIT`W*%n_yaEJ(TG8djr*lw(q$cBb|+eiK2>CdmNSedNmUoWC; z61yrMU9NeXN;#ZL8m^M51f5nsm9RHV!6Q?Qrj|VcaZyJ8wOS!C?SaA}rYtKk6}nWZ zU|4#ELJu{{;zPb{B$qGr)exgWf0`roZY32`^a($wkRex|tVSj{K|41hOi#C*lNFKZ z#Byd7p2h3$di31FWN?0)t~2E#o5NSmP7T$a=mvypLS)zvQ;6`ro0EPSE{NigfhvOC zNZeR!qGUKY#gW1uTbtZy5zhd>nX&14iMfTwv}0Wga|$PoNGmOJsl`{}$X-xg zBoZf^x2QoG(iO@h7Nql7&OjZM*ZZTvtMJ^5JBP0?g0jhvZXlLCCO@_0Lt31l0BE(% zO1_|+L=d}P^5uGDjzw5&m-f5Y33+7BFIv~AL|zoJv{$YqD29<2^5TSubceaIb-Fdi zY!_pvNY|WB3DgeTvryxwK&eY=oMH!Zp~z1HU7@P`bD7~7{dp_X^(82CDxIxTkGs&n zxLrjhBA1TVRZ4)l(v7mQel%vsyP>amOXjIC&UA_LDj?MJh>)|hxKVF^98uz}g4EW@fBq2FDv8bC_8f$MB4 z_{Ydz^w*TM++DJB2I!g8NKgluFiX83MjH>3b;E`r?**Klkaysw(?&-#f`k`p%oY+| z!G0{dZcP@I&$`UeiWY4ce;!86a2ezA;>X$%mcJAqY43~+_h`n6RY%uA4sLOy8#u z!$5N)%_wR58q_uy2SgY8DV=>*7Vu{=y|3*J_7bf%hBloYv}vT@w?o1iw*N1UV~ zbc1{8AWcK?_ks-q^DleyizC;ilZKaQV?tP((kjOG#G1WV3(J&q==Y;1#}k#~6>;ceninUi1k4zX55Q<&Y+9=^jhiDjlRH~Cu< zk0gEOG_%Ln?A+dn;ugHBTlP9RhPQ4cetj@qP|U1}dF zyq3eso{7+lojniy{LX%DYH#ThH~a*7wwN?FmZ>S_gdjCasfpl1SJf+_O+@u}LB2xx zCWL*Nbd=?Ln=tQZeACN!8>-B~#!{-urF&Q3ko@5m*1AdkkLZ2P8pM>wt7TAUPQUE+ zUjlR|zwUfY9mq@CjgpRx)(8~IwF$QpB;l9}z)nW+O^5K3b$p}<$0Kx{!L=_I=G-SG zhb2LZ^qcL*y4w1Td77Vr&@|8 z&5&ue(fd7oS8+%ajdnO`HUdHQCHgrcLQ~tATqSUxEL;N__oK!$jzb;3$?@t(xXkTt zS%tK+8{_n{Yu$FYGXKzFn_S2|ti+1V>BA46b+%`j*U^c^ut-+Z$o-gxw;6@_?UgBE zry%-`DT`k=x0&w-TwhCo=m?R@o@V%J_6<}&&c5)*YogSe+!7usx`(6y?DOzAFPRP? z^D2E!YtWiC||Xhs+t{>h-lu*mBL za2Ed=75jTVoR1;c2Px{wD{xm*CrVcqC%}*VhI2!XxAruJJBH=Hj0M{rB^O$WHh$ao ziX*rlo3*bXmC};Wx#7GuMCsZ;Zt-`i5nL5g8??mb?g!rCE9_!_f!`@I58F!U=(f}m zoU^UO_6<&vkm>Hc+|&8caj}aLc4yATBf-csfR}HZ*~uQsjy;=%69Grkhn?WHWrx+& zg9HyT(#d!f=c>_*1dmh*6J)}5T0)Od1MOw91IvuuH*@ulHpxa{5SuvV2xfrZM7-B{ z*-$&~eMu!Np-TQN+DUM_uTU$wfElx&ue^5hZlw>#F!IdSMwH2Yb-$W(jTG8<4o*gj zhyyBgfp=r&gb~$QCRE;GRz4{%p&ybA5rkLfF|pZ3fF+tf`*F}Wi6m4r-G`rxs3gMM z?3KVO>rUQ}w0s*w=#lKEUp1#^G3^dtwxy2QqqCtdwr=k4kkhB1T1YVTtkR2Nt&DBj zx~tb-33rh*1kZ)@c6Q?6nUNynfnCb^dDkN%@7Ia%^=7n!4|Vn6Zc4h5=M}bB3cGGP zKdlu$xiC2D96HfioY9fPLYdr^KEgU~ICqd#84Czu`L^pl6%rjlgs)BlI@42YHGVLy zAB|r9-NYvR8;Y*woARNzhKO8fTfWR)3#0?nuSqKH=?at2j@w5li!mGo-gW0ozy2zx zY}CPMZm%DfuvxMl>&rIWB&WBsN4JfqKVTvFjh;%qPa;5gC9`3bm^rd)6NK-}e#V2E3CVN` zJa3D!b@;`)SfYJP7k6|81CkiY7E;24y5lTcc;C_5~sknF*(9yK$tXhEFupyaZ( z2r>6`N>BKt>~!6yXLq;8Tt7iI{(3aCnVw|$s&{0>1E)VXI8JbyQId5VJg@l;GmZA- zj;6v|lX>pL)Cj@D*P~VXYwuKa@=a@!1YQuYx2qo?9L!wp-^#A0HbGZ-(tU|G@byJY(j#C(SZ0EZO!HllE^Tdo14f1SZSw%wFO zVL909Joe1Oy^*gL|8X(TpF)jH%qdDd&S)-o_(;PO z`|%rMU5fQC-l%C@rL@rOW8&h-C{u#Pt2{=cv6Vz!-DIqM3nI4ZSDoxb@#^vX4=PJk zw6FJf{q(K@5f_V)vL{lTdqfe@gUxi)hQ*FMm-=%osPOBm<b;tl$k`eEBO)*ml(Okwc}bX`V}g zL!OGqB3H`p1a{{5MVdtl$Nep(6H^?TWMItdawj!Qeh$>StkM--h6xCyC*3$I>0~iI z?&+z!L--w_p6r9U^zE@}jbN&KrS=TX4@A=Y{e{x;{pjo(>~6BVweUKXPFl$ME-fti zbCJ_GnPbzDjE-B9Fk@&?2bvILpemWw)~8*Bhh3ZGy$T^GosvcyahrE#$oU+fh_#Hw zkl}X1C__z_HV_OZZObhX#0|W~PXR@(oYug%Uln7GCG|{@hq=0b91$%)h6!|PEkU?h zM^N=BjBSuhY4u+aCF4?ym7KA`l#KCXEBqXxP7j5!jkS2zRN)Yl7Z=2)SDHA`1XddZ z@XAHWBo}cgUrn5($&WCcbTx{lCis1cb6whaG|3w=O&lnOv~K(IA+9GuGTLctSb2aX zsE{o+Vy1{xly-YM*JMV8@gP@8tORL9A~knER|~Ov#O6)NHTrAmhe`0R1i`>zxnuhv z2fRfxPfNy!2X3mb*EHAX&C%mL9KNmgNN#(WN7>;=vD8PA=oKQ4m8{oQ_`>rjv|V6a zH+<=pY3)bvc|Co&mwEh&euQbp`K~B_5-C6hV?v9RXTO%EzMdjH$|rUX18&-wcB15L zu{R-&Prqq-zjRK>-8XJ@#(>*F`3767cBI1$xOLu?`^HLjXG=-~X0_^a!(v#-x!;y} zwRSo1hDv7BW4QTt;DX-Mp@PbvZEzFAEv4#-=&<8XJ{Q^NQ!Cc2})enlIqpQyF=}#aF*&k6mFreS1fv^ zyZeJ6wO}0%!U|82#*5qU8){4L`Y~9dL!zBwu#Zz%#pB|-dF=9Dd%WeE(iz^9NXa5E z{XHFas=hqRT|9v&BHO3$0;*AO>^5{q_{LsK3tM_-IMiYFzQ^Sy{zewB)YE)Fp))kj zEI)k;>ziyE1Di)f9N*+=+2RqE^+PD+DzK2Fdz8dzn^a^%XmzOQfau}f!qcFRSC|;f z0FM_PsPOum%toBJ z$#o6VR=^yde9~KjOi>EUj~aEQXs%1TFsgLvxN!Tb`lBQ7OoU~!{qxL)lVt4!+BB7y z@AL(NKu@e{f_(ZnB+uSqnXUHxb2EV>h+FXX4I_Bd`A5qgv{W2fNe=pS@&WcB{yxtn zpC_)@p*~N^jwI(*Pu!|et;ziQ-vF*HLwMk=$|6p@uI#~V{C$l5NY~DGxJctP8i|7T zqcU5KCK0Z3wy;*wj0wKXj2i~$KW=^bzic_Ymis~W#S`- zM>5lHhh!U7x;uO+g~NrY^pT8g+J|fb4nSYkkL=))@i2VFxCD;v66}{b!|edGd(MZ| z^>44TAKxX&`&5O4O>JwaffVchP)~HF5y4LjZh87g0`u2|)SpvTdCNEs!QcRZ6a4?x z_wt_!Ol4hj2SZ^KbNhd1s%n)r6_$iixhb(>Bluw(WSeWCMn%G58>B=R*WKmX4(6hm z^5R&;6c!4EJ4M5&P%h?1E)y z%m{pi^nlnY5O89aXV}Dw!CzRZ6mVA3YDt$mlme{OSfaoh3(+c>7Nu1n<(93s#Kd6OU+0u=P}EebRpZ1Mx@n_SJ`)B}y@@3a%seHHUSSpw zXw+weMVG%5`5uv8xTnl+_w#N-*0@mVs{qR#QG)r}b!|=1(JX>k6jPe*2Wg~RANiL$ z*x}qhhkVIUtZ8<$m3hNG25fyNeW3S(fw&nlbGrBn%mgl-2!P~qXMjO3d#JNRh!mzw zYf)pWp4&@lNsVC{&do!)T#@iLnjogy@kpvnbo2voV?_DVboOmWe&N%aU#ifBL|>g5 z=Pp<35g%q-lFwnHGCTH+LDWOg0;<#YRZrB0de++9%9R0~;I5O4zlr+nYx@lZ_MK$n z6NK(@s}t|DZ*Ww5yq|=cP1T3{wo;WG9Et^-O>q-Oq=lkC33az_%`XLP@vy%Eu^1OW z(>|{INUGA#3V13m8 zU`zT6a*uPwOHXGRGeqDrE;D*^K6kb=T`o%}QLjGnsJj_;whxpjDks_ozjf0@t@LbG z%@twj^uFko(3PydzshL!&AInm?iB**D!4IB1fv>()JYaLd{;Ij&K|MaPWIi6)C4va z(Oy%<%-ap5GdXX>9#-K%j*P! zUjw=}c^mK~_vCv{Z2K7h=-w5d4qeW|y`0^8O5q90iUC@<& z54$Z7ky?L9!Sl)*d?Izt@mmEzv3ejQ%k%S#i$?jCL`6j*BZDCCA)gU(5gD8uRmk|T z5P%DUfsa6esAUU6V*o(|azE+vVP8x#cpf{~n9YFp2Z`+-vLSUOd^_(Y(w4mF&Do`2 zMuxk;+Y8_{&)@$%n+aJ#gRh%!HJFy3+h`5*w4C{v4y!|hleu;W_DEwtVWkZ?Ttxbw z#+#%4-PQTwrd1!!>cR}gjZH~}>eHil`EG+jq{Q{lU!kD|wMO4nT~g7?cMV{Ss!x9a zcCyN?o_Z$@q{Vk~`bVlCJt%L**LTjL!sU{OCthuCovc3nV}I|jx3fRr=vFK85mcc8 z0O5B4faL!rC$Dd1ZK!Cgt8ZpvX(aH+Jx;*Nz);B9+Sbs{&cw>{za{HO6ZhQ|`qBE< zLV;$(=+%pF-yVgkgnhsib*k?F`8owG7 z6r)TNF%*TEH=^kEQa2t__m}!lzSk~`Q%=I6#Oh5dC*7~SCNw{eCE4vbjV&`~BmB5O zp#~J6v7!UlC28JlmB2W zG#+*BoQ6AKcXUiP6ZIn3bY_x0v`i-rwT;eGg%;pLvp;d zlCNA`!yA0@-pM1Ondpsk(7WY|bY_KhadPV>hl%py3ZvF@letij_4)V&GXTQV@djmt zPjNrKK@si!462gByBX@%N<`?0;56$7VnWq?G_w&p<_u4G~4!p%X8v2~A&7>AVb-I)oiHVo}>tH^Zl}ZuYvtbRUCBsRdmd zM19E&EZ~+*RdsbS-E@9Gk{oDIp1-MXQ$0GOUoOL!>%UN-QM9k2&!!KIl+x_2M;i%o zwIgFZm2#MpSnu&|mE>`y7SLowovL51?v!jGp)U4KcKia9#AMe|0orYwPuE@?7(q5` zs-m4_;~{D>MlK<@^}d*Z0X%;IlA?Ry)lh#E#sur(DqvX5?>N9jYp60NN5nbDI{Hlt zC8-HnBI4xeT)d+3{xQ@Zxf)Ad3UPaIEUd?St4P>-Yq0Lp@Q~B0`Fb%lkA`{LL0Cc> zqWsngwXjK3_^W_c*EH?07CqSesO>CEQ!@U_fLztQ4L8+Jt1}E1W&XQ+suQe$GcY*P z^PE1ZZBsa2L3;E}Yfa0I-7Fb0lUpPn9h*$)@GG_{KqsEhG zNL)p_yzs`x{we~m+%@#Dnh+-t`6D60kkELi&+Hz2;)j`xZ8BoN2rsBs33St*Q=+xc zonb!!=D8Gp@D3>*L6jlt@vFY$q5@kAJ(=YbcYjCfxEC!*h^3dIovPW#(Q=s# z#Wp!TH@Cd>uK$aRY+{l-vV?m>4z+qig2Hfi?^i3Aovt`@8hvMP2F`# z*oZQ}I~?5xZ}&WT4>lG!PXV0BZwl(pGuNa(%86Ns2qf`}!qsA@kplv?Tt!0(`!+66 z)*@#4Cuk%)>hTQlqS`56ZCD+`dc0M_m8M|(x4EzHRKth?s6SMvoYT7SDRI=rZxuV6 z_w3@9KiQ@spvVptE3l05VV0{Y0=89YUKMqOBwj#@%dB(Pf3=V#!}U8}3<7yG3<90a zUkTEqK&&j@Ocg$v8JAtr+1pl4Jj9uJhIbS>MIe$r)uWrn>{$P(9N(?HO=bOtO^ZKs zNo!r>LpHEsmal*`ZKz|#D=u#Jz4QWNT>?VpXLH0xFz13kt&=(gLMwn-7WHtisFdaX zF&2UATE*T>^0KW{8LE;{DCh3AgLES>x53*}YDW>F=j8aZ>J`%E`6IynVufT^gV0D; zvK3hzXnSIqYD7L~yNVrj3|Lxn;T8B~D8D+^jAAAWfp*6haF0pQd0_4{`@vKtRt_%X zro#cXylr4;$4;?%M*sf36Tpbmyo(!rb};QVObWX(|Fh9<8$ zSFQfl-+XUI!AB6k~>GK zf8J{i9mDIBb5A`j-2>v2*1Z++d8$6AwF|vIef7i{mBX4-5CDol$0kL$e z73^@qE$;Sa(3^4K-O`|BfjE!Kx3NN6|{i!A`{wOgVp=dtWF##9UeL-74i**Nwa=eI)+MqMp5Gg=E4}!k8~DcAqQreC+K#Bcfx5;ctw8}!EH(&Ezc*tIoU;Ti)#N{17yg2y*6IAoF+^TQ z?u5xrrt*dCWGE3yJ8CMVvOUK3AT%4 z8xL#J`G5*vWt~F~&1d(C59Jdd6kU%m&}D8E=jVHgmLHcpL3X39xwz0`AC;8g7Sc-AJRPtP@>+u*meir5+6~VR=eo{yM|Q z(EK~-6CJ+r?4IKw^3})tEAUUbsd#4$ovom7V2R!;jo{kdge_y!tJuqPTKbMHNo;YJ zc@J{RlCOw|>VA1-YeEHEx72OZCRXeU@ zof%kWo95S8?=DVLQdA3c5JLl=z-T3HD&lukSuv;*bMORp45j0YN~;_^SKg;2*6uZF~#lH4No`3N9m$Fu7hZxeE5 z5LdqqqKxLdL&@O>3*PQE2xMWDXO8cvmFp0o5mN9PMQ&kredBMMPC!CE87y14@J@;u z*;KAK$H@`Y%2=Umbj^-Yvr4v9r6(NP_JX*1fN~v6RS}Vk}EYiue*1b{R{SC>AWm32SQXTmqPu^jIiV55Eg$5)a}8Msm~l zk?-#YxNNhOx`(Lbv!cnD&lC;9b_XL*GrAEl_T$2ag-+E3Dq>3gs|JzyPG-`Uwm#eA z;lfjB*~#|nQser_%gvU6V{+s^9;nqSo{0ciV-Cvd(zmj@s&tMYy)sG*NBB7Fp4L{y zh0A+Ojs!Vy**>B`FBXv1pSK`Yqa;T-gw_pM3osIm~k}H4tM}9{KjAy0^YNnZ=gsf=-b_9 zv=W~JPGhm}gWD#jEilk;rBk1@-LVI-0wa2g2gUf%h%oLMM=Q%toS}1^s#ntEu-iSC z$Am%HL3_d8*2oNK$z9{uSMu8%vPIHlx8B$xC@V;FGpFv0v9|R1p(jl0;loslpC?r* z^$=J+$ykqnEjiALllpFQ&d-j%I0nbOf1wB7+V9GBG?AoK+Cm3Udm@5`oWMT4bML1; zSyJ%>?fg6^tmS^C?OI*sNw87O=`@)|%#3Wb+pE>;eIckyo5D!g%E1wHmE;2rd)X;E zZ?i^UPY|b&)VEHpH%*Vjbmrg9tIVJ1a*5R!cAB$sn(G#6l0x>&-1GyEbp&9{;<^^A zETqCs46yEEh?bZ z`PXoL4&>H9swenSM63P`U^T!$y$OCVkgaWh&Q@*hO$_be1}mXIy+s5}tdt`r#}eV- zW))^evRzHRZef!;IJNVzQpL>Fi#mxNhU3Xx&eR1~GngX7HKxXuDlY~NTrR6u^vG$( z4cTP^%638898aW;l% zQ`gsm{I^T-KQ+ewQ{&D5-uTz+_&-WZ~b}Tf)M{4KRlz|@&9w**z4+>DeKyr=<1mpDj3@THQt&VGWw5`R|@{J z>k|oox9b`HT4Gigg_iPDhT~5XZ-{?$E%v{hUjAAKaNIt`^s|?AkpKCR56}+ zrM>I3P=@tyo4vk-zx2j@hPT`OmQ&_S@JoxwXRw^Z|0l!8my$1i2A?H)oqpSO@Fo1E z3)?f?)AjFrvb|J!>8katg6i?x9$PQrFCB}X;b@SuUd*l+KsehGiM`Scmy5c%6%QD4GeZr*!_&&K?{t$Z&PUTy?? zRyd6NTie233cOst^DF?&|2vC$Uh-c)HGk%NmHhvGhW@MaUu8Fcx%Jimja=u;oW;wW z%QGIU`R`|7WF;UV|De$Q{Ki580Op(j`0xJ!P)h>@3IG5A2moMuB}=Jbhssci0RY3; z0RSHW0037-LM=&7MlWn>VlQ)Ja%pgMEpugKb#iHRc`j;Ua;&@sR2+%cu8q696WraM z;O@b#ad&qDG!Wd~2@U~*69~cGEw}{D{`>Z-1)r}lpL z`*x!w3l0GX^7;UHQOJV)%LDdhkr!8c%OI^F!KC#2YA_&%udBI(bn1eFfLMTmfMERV zYVvOtq$R{v)fnX^T$T0g@mNrN8n>RSkK>MW#jITIw?q*O-^N*-YxA@tHh!B=Zi8O> z8iS0K1aWCunKm&zdMC~2)z$qI(wOv^76+S*rf7n_uIC2Xuc65vJrjn;j?Ai8d)Wn3 zE{GGcGhrn^VWWs*2wL~+&#JjAsmkrkmd0o0$fsy3W16Gu$SQXY__l9w@!=Sn6zsO- zM}?YD>=zOe&P;dmHlMAmDpx5|3v?1>bN3N>WC%F!Qv(EWj=81M9@{_$v3-@^w^-&+l`+VKSVHm?H@(rX-?iSzpz#M+yX|d z(R1%;ZCV1;p!dVv?)y#uGv7C+V|DOGeurv;rU$aNKn(x<%3J_aDeVwpeLCE)Pm6z-he3m z{NHZE8DM8(Wbg48n_~Y@ zH)ZZ*Z}0Y3A5QYW+@k91>!y;wo~Zs>^DlOy{`c)f%=EXn*gHwvI@tWhh5K(0OwH8U z<+nYmm>SzVnV35L#es4Er#o@CuyirCv2=EE{#$_g?de~f1%dx`Q*Rv{ob25IHlp@+ zE~XxTby4~M(@l%o0Gyp204^5db}mkT#iZ>2t5gYyfuVt^jk>zdGgr z@p<`;>_1NYzqGU0Ezd!Gf$)2Imj1H9zbuw^CZ-;YR;D&Ko`g|+NS(|mkoTDzuBBku z{gQP{oi*5+ng`QuewlM!SkA;&JI|?d`%yj43Q^xk=>fgWa*bR75yb zT3t3T{cphk`7vM5YTuDbPU1_~ZGrr)Wd4tJ-on+y+1`#(+{4Ax&iUn)k7}|CUCb!1 zx3ILF0o|~|MnT>IGH=vqb7j6`Kp2@%DsRlzLc6Tmrj znMWz5Jt$TOT|4#>k!OZ9YAJkS+#w39bJ}6JNaN?N3Rd7pE{W2&v*B;U42`B4`-2<< zvVjT~S6}^@MisqRGxQZX)WXexd8|^==Ik6#hYHDlt7ot{+Qg8Kr1o=Yx0%CI7qN@Z z5OZ{Bb-FiNxE{u)CxFtEJC*sY(`Om$0$pPaK*L-I{{Zs1?+2duYgiGQSQRu0%NiVo z$Ew^KYO#hMnldL%7;o9_Fu9^fkrK9vyt8#hpTyGmxT?i%Qmn}t)@luc7j?@ohZfAg z?d33aK9ABebUDEnt!reO;c!u;+k9L4v?NpcAg_#Yhy3Tusz0%`&iOmj^#4DX)zZ$= zg;DzDk0rpy(#zCoh)HH{NM>YcbeK+IbegW8MjpEM;zL3NE4<7H)dblHR#{qruE0)a z_r}Ki?s$Z`FY);@j39sBKYpOjdiKjR7jO^|*8gq)FE<;bwY`P2tFhN8U_!5n5#$3;-+AbmdIM<(-a73$k^KH^53FD z+J<5$2^US+Bv3c)W--HbU@Kshpue^DO79DULly725RK)sKUhDknFe$bWbO$-L!(=`cJBP=YqRu-;y z$LJ=nm9S)?{E}kaiaR=^iW+FPcj$>Bp1(O1zD4-sOPAb44kbH zt~Zkz{jD9R=DJ3lY<9FU%0yMX&;@a(@BiyDpATVA1PgnpUJHpt=I+=lDF4}0`pYYDD94Xh9 z@^B?6fmjfYRuN0)sdDcj7GV#Q!lE0U5&+CIpD2buAh2BkP(ZK0GXYe-q+;oQjDQ0n%4AKp-1 zx1F#9Rq%jx7p6*kU2l);`JLj`;yGLWy&?0%1;7l`PO z`y|uGBT^nXo?a&){6?CVjNvjmJ6ebSy_R0Z1E36xZH^vY{X?k+wHG!&6s7~OOs z5`3p>nVk;RpElS>A-ohmMQ3ZW+vB2nx*$@d%8JDYI zZsr0WTJ=iP0!YiuScn)))8i5yT4#dSaTqsL(A1;@qKky7@{V~(@zPxy(MAPewPkp& z)k_kKq~dq{Fy}}gt%6}{pu)=`gMf^a zfPg6dm-qBR!mo8RwhEqD)t8P}&zS1jc-nu?ze>nX@=aHU)0 z8{@)3wXirlp#sT?YQc91S8%!ANJuQu<=Y_w!r7mq#nc41@_p2Zd#^ zwSbq#Ul3U+^kff=XSbAf2&*|;<@;Qaug>oFX|wC+gbEz*_3At$w-(BqRceUeD*$A8$z;v+8DUIrM9F+ZsQ?y&pF zcg;PPbev~fJ~>lGdEY=2Z$+?QN1+k)!BgDeKUDhosCGrDJh)Fjt=^v^dq@lFC)e5! z^fhuk6lC603AG1lJe+Lg{Mek1TjYoyj$ZEHt~kwWq{eTqY!}x%}Wg zk|)N_mcG$MLk!xy>++_e$&WpbB{WKq&31CO{=2cg9C>_NNvF19srDBpG}qv`Y!gxz zyqI%lcyrz5Ou0+bEUPR7ay)pW&F~_XiAPp6NJ?2VP8-w{#Ps{fKt{5+nnnZbaVDKo zCgeacOh_=*e0o)1>dy)0Oc0po?=2~+{Z^!~-0}zI+$ks1EJ)3CGgw1MBAY`)TXIQ` z)gTdAS>F>S?Z~-Opo`Z19Hn0MDYDgeY9D8T$G=2t%#tpWw-;>}i7wn^6 zF=kfKD%8RKvHrn$qC0>GWduiSGE+jidsk#I47Zz{gKzX|_CgI!y2~FP!aTtRi)(CX z$E;6*tRX}9paw=8t-y$q18<1Dd8WwcVQ0^@FEY*byYErj|E+e(ouiBbRmE+_ScyckkHZZwaL9E^T}SC0mn%%#3n%Rxot4 zE`<@wWv^23oc7vKoxCrf>^@v~+F{E=lv zdfUS_IEQ(phzg^8`B|)GQiOH7zKm*0(N-p=?;V*8?h>*XZRZYHxmk;X8glg-Nodc| zFE&5$IWF=rCd_>CNR?F5;Oui`tm(6(!7j;&u{7G%Bmq*Kg{0g_xQ4fWRJP)7_z3iI zWWUPJSVZe}KG&^O)6mLDV`e)9tAfHsaW^X-ICpWSP!)~SlQ809>QBYR4Ua)`#E|Y& z*_NWT?UqnAGxH}O@wSq5GBBY|k~{iN+${3wBDQ&|&t`mqcSc`EdcR=g=#B4h@5GMD z#+gyOsjcqOW96wy$hCIu`arxgc}n-Cay>ZOV3Uv#d~NWo8FxV9n_vib?pQ`twMtCd#)piNcZLw}DIoK8I%BHLsxul2}i$=~|ba4a#w zCp?nHq|jv@RiDrwC^Q#b1fN5_omBxZw2V&izBPFyJT@BqrkW8eOhsR~g&O@wOG&Ju zZnQk_QP<8Xp1}vDR(%=&@wkQ86_63kC#z8(ADo+1nEK$|63Z z82a7ZpNERQf3>thAO~ zJz9NjT4YhAwq7RH^rlaI1!uyR2V_jRWRHye^qvu>C>$(J5) zYbdaH$nWq~oqLD8u*}U2g17AQpgNl=Qn1oT^ie3xc#D%vH&~D#*=!lVYQkZ<+@_Hz zhewOuCSj3Ockx>x88=LF-BNJG*SAr4iqi73l6EPl__J_sOZOj=kWI0)K7O*IE^KNN zo~RMzltJrV!BvF2*vpE#9YqbPi=*)|b;Ih&w|9e_&l$z3^(g-oujv%r2QxCJVqRSL zj=+5ON_?|Gox5tV)^zre<|`J#2K`_@YlPC7w~IOZvhhIy(^2r9*1XdAYR^m6B*QYd zLvR`X>7c>Rp54}0xpro~<0Hb9*~W}tX5_q`T{`@IW`j0_BZSMPslrhi**$$f;yiqv z!;UaY=I6VuR1K5jik9k28%B&b@ykQE0Jn}qT~B((%iK)RD&i`FZhd5u4M3< z^TFBIl9m)5#+PFKfBHxsOxD{)`vv-0RkbrE4C{*ois-}vF6HL>l~{Q&#?0)xrKeoF zOQCAD=7%yV+D6#zX>PI63Rkw(oHX-ua=L1LYh(|Sw=5fu$@QQk>jrYUv2*CR3M{9M zz-2(g&M3~}w@NVlJi9tVMlc#r=H&NmXN;+lLa0buI#u&Zq1PP=UF%;^M9LG=ojL=P z5#?Y&r^f4=sVX1<{>ZRKSRu0*8Ko~c;Rf@`3g)BFA^M0?SUBm8W11)cEk9uK6d+5G z=GX6=*xKp{nIllL@j1la+N1Y6AIzGj8E41=c2ts1L^G7qrm!%mK;EJldbtUWD<7U~ z6AxjNH_{MZWqLkTa5TVr3FZw@L04RHkE*JObGjsh500Y+h!x?$(AUDyoZb+ye_Kq@ z-jQ|Uy0^z1Ss&3&Y%V{wCyLq8bIX#@?O8?VmI5zuS$3-uW5O0G4; zCS-u}IQj{&9DK!33Uj;u;n6d#j7}RsV|>3XTTE+Qhw$P8s%>-K5qfQC7vV%O z+lr5{4RUBAhF5GNj1a;FWS>P!GZdyz0gP8{kMXG$-?F||m!1Q^&5kmdCx$?v^dth0JrpI*~bt5q1sNKM-#jFHxL;uZy&&7bEF6{|u zz9;n5p#uA+DEVn<__*Zm_dZOcdVX2>WRz8sAI8|ID^=rr@1+sMkZ!khzsd_D>plq= z9Ii3;G(n1g#?ctQ>9)=BA2kT~HMc;M%?4ZS%-$}pRx?DPtEkf!yn@t^E4h96AaD z_%%TqvYaY!XmR+synfk_%1<&DWoA^ySh)i8Lzk>1LW-`$rpTd`)n9Zj*rl0^W*16- z@B~Na@~zog&b(iM7=<4)KO`R|?xF>&<+lh=s%uUwFym-@56Mt+S;GJD0aDlFgQbt< ze2-J36N2@5L+Q}6+0QpJNh3j%qfNEv+DI3N)CrQ5kvXOup251jk=JW5BwmT#9>LA- ziJoWT+Y_4*SFlSVOl-{P2>s|cvINeZ%0sp3YC>b(8op5+TpUkz&>Mx;_@$B0}tu>Os9wiI3pi>Jyyehk<=U9O=b z-F9cjktN%9x{W(XuK~E_MLy;4TQzhP8kO@7WcSQAJrfV<3ZZ3U>XDSjE4Aeg!$Bei zMupK=sn3oY8qd%7gBXBZvmay8br7qvw99t&Rf4#m^}tmJ8M8crla(N za(x%KHdbx0xEx>YZQjjF3DWNu8I#UEX2b5$71kF))HiFs!;+n8gzX%1IpX_bt?#HE zBAaTR(@m_>3gk&vb5BI@Z&12CTWj1_vzDdvmPA!4=a)|~g)j~UBdb)pC4-f?tgF}^ zg%H*qRX7pq&6$S~5_8pB(@2nuT9#KmWc zeEyEU*IG0DnAPr6ZM^^3KtxZ&y^3{xSLlp$X2W)E4O7TIhD>6Rthv!cvAOdQ({oXG zk?_}i93@W#fSZ`S9^h_{KYV6A4TJGr+zm*aYsIO4YwdTvN5JKb7oM|Q9I?ykA-9;4eK)Ow5HsuHLvp4Ebp;2hrzgoX69 zg#NXLa^l^=XShKL%A7LA_Ppvj{zWdxdDE7Q&lI& zwU(S2A*I$}%df>#P25Ee&NB;9Gz6zrCusI%x-OCt%VyYv*$!HaQ#m)wUz)y^zqEXt zb1=4*NeO+HWi(pO{tVWrGnO^0nEME7aYW|Oy6va*MV!ko1ntWR{yqfHIlg*O!a;~) z%~X1?`q?ekb`i~oYMmx#+NF8AFE`XWBdx*6*I`DJTNp>?3@Ie7X|oMZm*5B=dik!6 zIQbWGkK_;TD*aMp*GX2sC>4kdj)A9w7Z?%*j~64FR@ngXEh;T*X~! z-Mds3hbn31s)stX<)E=mc-qEj-Uuc&Hp9!71GLb~ zCxW3PVZY~+(8N|lWSh4p996gihwIAszp|dj-*h0KNa89kB6_TcSR`Z*&9Rxc`|bEV zpB^G~u(9h+I}UZRtMf1QmIZM@?;{;Cdyr*w+S+Vc_Euw1NbFB=%Qee%+J)P(41bB0 zF=N6_)=9y%Az+*4Yhl8{A}j6odupH>Mm_!*3=tRY=`|che+H{L1~tig(<%cyCEZ;A zelklcgPZ{FQi2wL=!AU){X^%6EKEl`6HX)sjmK<1VujQ%jCze=8H(X{BU>MA%GZ9l zy4o3?$|URhmHYB)t&oq}{?)sll?LaAa|{tepyx4x-3w@CnI^`W#f-@(x<7AeGk12vIK>76w464!KU z@PeYKu&hSk?eYR+G7=Tl5)}_h=#X_lFWI1i)$MQs+gYPnBa4NeMz@;BklH5bJDW7^ zx@Is~E8EkOFvO&1yQrwsDP1tlZW%4pgA*XD){|U|io^GuquVReshf=)pOekrw>xq; zgvi#JU99~Gv0=#ybMeErFCB@FE}6@|BC*+-@i<+=^V^9{b2O)M5zWa$I0oWyh=QR; z1>|BQjrE}8l5iI4nxWtFJbQy{K~1S*YczWDkja5ifc_o%F`oDkx`(US>7Hp9%^m|) zDgM|z)e>eGRrHfN+83O(G-u95oI9@-TA-IE&CjAwpB5AxDd=w*% zkCSY?mFO<8%_>7VI26v>PLmO-Qd1POi$w;jm{0UuI!8D6#QNqiK9h@+!na(#qlON- za{sWQxVLhpyOlblz{8uON{t~vINLIpKr7zO}lHrXZ=O|vbat-x?a2*%vb4Z|G@~!-5fg#un_D5e{jfN%% zGt*^O3a4+AUO#x?AQ=hihqA2N*gp}E@bY|(ZONECi37&qR=n49iVT3HsOD&9cAU>{ z9s8!A;BEvRtMkJgL;YiOZj(Y%jvfbXTA?jD8zL)Kc><;>=101Wl8pPppN6=wJ}1xh zReJpf^JkG(GiP`vcz!Y{2@S^3?v{OF-5AKTHk8J99_eIL)~;x-5PJdd@oSBazL)h7 za}%aO!7T~8IV%-EaJuy-MMTzsg&+7gcn)|2ez0?eo0NhJ7~u6yypIY<@BhyBNi(w@>~$vb4{K zG4fuaTERl(y@pcN)~FxXF4DE+NUOGBjnU4iu$lIQsRrjumFcm=H3-?C3kB322ib2^ zEp+w4G@=g%p%WV&viOz;Zb;ca^fc7>+o4DE^PVMLFL^0s9q{np3{w&E0j##z-9;PC zlekkVDN%Vx#B~L8Jd7jjY$qOV?LjF%nLrMT@cM3zMK@XJGdE8Jy^`&YNKV&g z8Al_Z2&Gw%@~@es&iM0|?@Qz$2%yV7e``r_nx7EOC zEo(Kh^ccf#eo~tnI9-@x9)+7e@MlF$*%W@D`+4xAv7no!9|f6~P-CVn;wvZQr-Xbt zJm?f2BqtlAW?*BSx@HweHG+gDYgB+#8NFoq5vf@4kv@D2RMWkOq&Vj_ee5a~J1nd0 zBa3uxj9?ln)6xTe`;|Bu9okQa2q>S5;Wjn0-qwCPXKym+34}k(x?Kw$!NF zAFIPUdj-RSqYSQo;peQ!O!QGl8yk#_hk0MCF}`Zecm057MQX$e-DANG)kBc$?n}JS zk#i}NE{^Qt{!A+FNz+0$(_ap-0{v7a=+k*wEO#&_b(>n3Gc^<=ecKGiUB=2a1(F&L zqW|&Qze6h$qrTszgj+Y+QHrDrpwP4KK)tZ7B_}w>oq{yZGkD@Q`4}WWHwSm7$N>3$ zphZ(@C*tR|fUYu$k%nr6+w;E##YtkTPqJVjAc#=^eRTPIE0nf#F?BLFef|2zKNORi zj{Oufj(}aO867UeCM!-ODCB}2{pJ~zh{zNt5Ka^$YKNVpa?VhrL8pPs4-H-J4bctg z19kUK;S42|l&Cz$ed~PRQ?%m4=1$`=$dR!p9D37kx-8_DV{-sq(@(%k7YFhju1pX! zBfcHO+*tEr309Y`FCRhhTY^Ei+}p)eNlqzeWZbg`GC9w*bxXZHx9Sm74Jq{STC;aEO?p)_1X;@V?lyU4sw7LCphMVJY9}sDp^}|Jtxtqs&>t@dM`U18W z>m-fpRRoBR%1ub^1;R<~hJQwbW6GeJ<>lSg^`<*;3=`>j3@D3{R}50COZ2OW1}#1mfNLal3|l0O10huHBVKq{dOo@ z>7~1(QHGd1NI17O6Ccetn`IijHi}X}al92;d-`AnsL*0piqR zQk`EKC*8M`(Z__;0I}dJ!&V}}3Ln*S3h?u_Zu_1&h#tLQaEKm|V-O<1#0csgtcE7l2K)cQTkd35;p zFmq`1SV+hp)Z={;Y~*^nw;t1*7<|a3ZU$s{B0KMbFV%( zOb~bCq40SOON>pdTqWz4tz_yMT-OkA^3X75h|yD$bhqb`Q#vrbZda?TpdIV#4Rf9i z$Bf_jtPCR3`z*Ce*&)|%Z*|K)U38WC9fZokl3a5MrDfCp%5p4oYR=#-`@5WU-LJ?Q zELvuGv~ZitO7O+VQ9RfzK?W)D`IsuJ5At0mfNuS!OyxhmbehW`P}_ih)mEHA=m_7@Ng}uBx(e6mw}Ox|$!W7f&Qq zU5q#y0U^PX4=E<7+UaVQJ>BdQg_-{`CzFLfs}B+01!enfkH`zTBZ%WtSqh=1Kq6vQ zcJIJK*U8W9Sk@7thV>ssO{`Yq9@FXRCl>)7*?D4TN!`t1mbwsV10=Z1DGr`6KAF26 z9*6j>Lb3N}$d(Kww|nU%G_od*R#+OA!vLi7R1(Wl&Fv58Q{c!)Y3DsIzKKLqpD{)7qw;_ZbYr z)H1=zG^j6c__3frpz=^Tl-2Pedl|s&8n=Oj{jLhf%WX?gNB`rrnL=}53!gcQQYW3V z0;wjdpIIirPQNU^ez1*W{WiLK(C(uc+mL%AHMF2Rtqep)vb}eYX)_=xF>yxTn#BoT zzayz@C{PXWyy`^EA!psUK`+<8$msanHIcJLIEf%dL?(#<6MwssmcjPVk8KA|@)0gJ z?gQ!~_fdwrQz8gh{4wP;b636!M+uySRBSAHmMXf(DJ5e_hsZbLn*9+8(Otg&W}*df zd#Gks%IX$zT6xG5U)_JOj3Cx9Z}L=KglB|_gEz`Aee*?s|6si?35+s(Zcov>4ueYwj1)n* z^^JS76NgyB^jkwOB81s-WfMk=R-3gcdwZ3mJ@9F{K|BR2n^pHQ=X!{_HitDcFGdNE z9Id;xwLTiOmi)(fe7dMCX|bXv^dmI0)b~Ei24O#{@86aj5b3#8L?!L&fSPu1^hNm_ zLq=l&Bd~brYT%rq3GB!4j!=#tW+k|%cBjD|uy*j~KcP%v;*9!=^l6Z<*nG>itcW%t z)N?KLLK+v2<4)rp9^<~rGp*@cH!_2m(xeBT1Jt3g`+FTxQyh4gL?SK1{FX7=lBCud zv)AltwJ{)fd`D{P@fo~%Z3JMrUZ}5qW9cyVa$SCl1lngc;)z4 z>S^%%UK+&|QRTa51fza+T^;+Zk|tzAfG2$<2gIRW;2arvR!|+G>iw?1h5D0YS?^RG z5f~8qxr?xCQhKsl_}Yl5&ayvIrfL8buXJhpv^JH1tZ_|)2aLK7uUOF) z(OBc+?>&c;u2y@T?T~5wAQm-ntVbSJ+${%-_jf68ssn!P} zLCj&I-;vl?qdZN5rW*%i8wmk}LkLWGGL1n3jIAQ+QV?xTtCTd%aaaP@9@BH3L=cpT zl|>843)-dM-d1Ooxk{gle=$#TOH~ZUxS|376bXNEOrhqE<78ddgZlxIg4l`| zmiSWYrEe&0BKdGMdFKGd3O#sW=Tl0_O(qCvr<{EdPDHbF|3A6=3& zq9c7^Shzop=`!J)WG5(6t)z7=;g7~HFA{xjHlvvK$XHU72;La+ZR^n5 zW54ju8{58V!4~ZFB>&8^Q})Wxfc4X7hrVi$boSw?%T#DvJl2o#)=mKuKRsGR*e-5U zDUY?{XQjp_H7ghh-p^nm$CdhuUpb>`7Jdx^T@(-Bl9hHs-Ekn2B8MK>Z3~DH?T`#d zlACRRbB@@o=>NtQja(=Sm(&#YyZFckYZQ;}Hv%)Mbh0ss$v(3EEFaM86Oik3)vus` z=GSaqJLXlSKmE-jo&67yZu?SvQTm=`Ws!dZ;052@1Z&qb^yi%+85_a80Bnrd-mX9A zw%5FPU`OXeCr^%N@cOOT-tVnwc(6>&Z9|8n!$%Kx=5B8O&$qYm9jX=b#%ObT&SWtJ zcH55djRN$ZO`GeCq5za$ir4CE`)w1eOT<^`kHXvcT6lq+I8HCarDfE*>FaPg_K)F` z{GY>R*RSCc`gOSEM!+l8PDtK=9WJA9<)i>PY;}X1@3V2=n#Pc{mTG5$eS#lYy%6GP z_9Nyrt{t9vhtcbuhowM__nFZM>MJG1T!y0s0@dYAs=BtTJ5S#RN`3DE`RBPS75sWz@W))3fajvVgQU)de+q7BkWcwv{F zdW!Y8+RL(?+LBOGAWO&_jb)o2r7YWJwNMZ>xF?N}R!g^^L-f2y)x_ z?7Vg*JwdSYDgr?M9bww1fo`yRB=90-@>{D7-41$2(D1F;$d#ccRtQ097sIkUM0BCp z$1~&sN^+qrHgeOj1GAA$yh|;5LsVyShRJ^NS905QiI7iVke#Re|BYNxJFZ%&7m+>+ z{kOUzf2f9czoyG4;H+I~`5S7u70mj?p^Znd{3RwQmV6xIltWFZ*(@_R?H?l=&ry`` zCD1oM3k`ahHDVT~23RJHra50_+MBg!9UqJttTuowb^DM-lUf5A*|vtqv)-b6(O;Ow zio`hHu{w;~FYQ|{F<+TMH99PI62iMPEE#hvyFHG^?ib5oHBat4>wfCz7wtPJ({UqQ zMTCpq_~d(JN#)1cVVFVMBfvPQ%TjmHuc!H<(XA9}-ru>8=BtxZD=El)3akE{1F%vr zd;fVbhyyh^+u?&>sW2PWMI!ajSZ@l7ix%{jw|-jf&Tuk=XDy#`TBicH2RFqTlD-jO z1dfX$5Z7LUJ#t~zLw^!5n?nAUTtOsV%2VUG}zD8}DrMVxUM)nrUwa9BFDE7eSCS1)e zMvjIf-Y3r1(TNr3#(Q2P>NRcpzq4t?Y1G^T<)~XZAeyXzeaDD}SyUe>ijV;9g=nu4 zyxk7tDwSG=wK8NCKu$PRwP51vW>EQK%x<4l4c-}{@o6WD?Sh~X0eiP#8X_Jseq=;q z9z<)>%|v=7j$&qtsxt8Da5wj0iamqs8RdJl?lqKVl{q+K^9mxl)8POjxwC=8uBw_d zuoq(nM2B0}SSNTY(7BE?KkaRCQ9wkK+Bd&IF|~=T`$*uiRNEa`L{YeWA=gAxxZDr` zk2;?GGweNGw9iuge2fJPAFz}@x#I_QIl4war6cB?gW!V5dy6pt5sbE+K|>V96Xuy4 z{6F7uSD>Ieo8;gesT0_{}crBl2q`cMP)sd2G} zuw|5;Z3@m^FrE@E3R@85G%fP^AX!+9=|!iHKJ?i?O^vR9Iu-({Ffs)*ZrVsLhum_k z4A^g42Yl<2M{dL^dXYLSg9#zFFL7vs2U?y%oniN_pe}EWEN$da`;*pNo<8-7xb>D~5iVIq<)mIY|F* z=7^C*)7k?k9TOTdEI}J2gR^JFU(FmRWA-%!RT<7E-;eKNtXq!(Y2Oy(U;0GhLn@M^ zhO6Ayp%}wxB`)+UlM>Os1hNeE&B8(eNBu4n=gbr-(55Hqb~|nIaU3;-;A7wg+uJkMj8`KvYlfb2D!Yr|MWaviTlT(abZPq?_@;>arj(o;#6NQjvTMDl z_QEah-^(rgKe)9+XF(BAn6$xfAjh!CTY`g-nw+f6wSPmdR{xP&NleLe#YS$!tcGk6 z_7Fb@0u3_^S}=fE_yNJ_OGXJMS1GktPj-YazcYXKCm}(9PzUVS5fhS2m;OzRETnEP zCNexxl~+?QGat-mZA~7ga*)o@CHl*y?_oChC%?2W{Hm-9{K2oxa%Iu_hnKlH<>$<)$6;>nTc$n(u`YY!B3%@y61~2@gzAByH`F&A3&Wxu2 zSOW2i%XSL|v(mTSb<1^G@7*{ss7$EPGN1d4}XS6n+ z`V{6PK>pGlU9@3i(C1upuYC_@$QV$O*QKlB!NWSi9kXM*QA2dOG;a~ZTn5tVQoyQn|$)RutsB;KA@dPIE>c=f_0C~JL8f4d?U zmZb}G2Uns+raULMBV2Kym{V+II~zA2#FvEK4wFN0)|^=HYxv1)hylepgt*$^1ND)0 zf)Sp~KK>VtPW7VEb0lZ?Z^7&~IPHEE`~BNb*J?#{C6r(IMf!XB<@iVb^GoTx)ZLPo zy8HG7s9ld?5}ra86;2Y6O52O)vtrlhw#z#;EJyd{Hi13P!si6~hAYy)>qYGZOKgF@ zjG4pDe$Jz5pLhC>?N>pUx&|S79VQr3;QBY6Ks6j@y1s;36IHQc-2XOaYGd=;u@29= zb^76S=h;A}e|;S;#V%t>>=!!|HE^skE00^R~%=aOT43-GTj2HqkNE*x1GtoZ54j5dla5VW5 zavH_U&8cDZF20stm|vw=Ri;Yg)g3wb{8D=T*5Gn)kwT=zE9o;4uHS9+XDH@};eT$} z7Y>OJ)+#9{%Bo@1MPifuz4i{g*4|lXyT!7-m)cADQhT|77!#li`p|on+-Y{Bg>aXB z!IcS3shg34ujU}VsAH62izJTUF90=9RjVC6l&x&hT@*kbLRcv}C`Y3D5n30{S7z%) z=m+8)&=ObOm4`$Ss>4~?TU}_!p*JC9gBAVZb;8`nr;@q$)%i7H5^e*(Wq^ZrsV(GO zu9KjuM?d#TOmz`~0q>txWq}_PngH2AVgnHpqXqB=m6Lvf_a5?Z zg_rXm{)pWaGsYj`r7?3m6oy4>jaW=p8^03} zevA!X%lZ$ng?W@8e1#5-S2xk0A1)uE10*%GzF}0gY-@vh>N2fC*?DPRI1F{!0w>=@ z4%yl<%rX56FaKq}y#Ezm{%-6Ip5#}#@Xhc1!`Q3+r?Ch4W$Xq0GWMXNrk9iJ^nWw< z)L!OGmXpE4U&da{Z^oV`cKp|${uWM;oHo4}Y31eAk=G&&nL-G!#@*C z%|hFH;g<20+f8FUHbEaXfoUIlKw1NLX^XT^Uvb@{9PE4+iq~MoB*IkR_O?j}u36I3 z##Q~Rua_5!31zQ?q?V$pQbhYNzFyO&4xs|UoXudb$FJ;C`*Q2ei(&}(+E?rGJ@{2~ zNp{CS>@HIa%P7upw;-n{`9H`fD3||oMXGzNebOy83p1FJc^OS)D2nX@yyAJ6lX35S zhE=ZUwf48nOPOYqqdTmIB>Sh^N$+`ZH4A6I1(+(6-m9=ShdFa2lP3q6Ta#NGmziiKi}(8z@d*~A9XYVkQ@hNgEMAmLNVtOHq9rHEtl-OnJGy15Xuf8(?+N2J@5@Ai>Bjchv*B#Zl497A9$x#FU%= zsTp(v#UMA{L$BqbY%YscmI%|zsB4Wy9*$+Net!C8?8Q{;cD!IaS5JD@q~DFWbWy$b z%h+@7Vy>!7{NPE=)l{$hb*rI#0pWb4%>f6>k(w`X``DQ1`uPDbCM|#TmB>8 zH=+~)5_$6Xe*C+!n5j|`?Hv4g764>0^g%b-LukUv>616}(#!JF`{G~hliY@jMd0ou z12w21AHCt~^?*xdR?a{bRjDVdlf0hG#cE7dlZMiK<`u+xhbQV(XBxJf3>Bv~ywF|; zr$)-UnL+g8fNk$ONtJqSKEi8x*0{zTNmMQ~+ts%r@!j-CC7_kBW!L$IT_?0S80%l` zCctgwZEP}@34VBG7m`@eis+{x)s@2Q4Dwex;Y4$0EuS% z?Rs_JS4)}#%3c-eU5sk6TzOM*i`n?5@o;KOTvJ~oZ@R}9GgXdgNwYveF4IJ)!HS{3}H;S+nCK*)DuPJT#bxM1FHKd{a z1B3=W%o`EK@?l>?_j^yXqcpzr^SNtc5F+E(DQ(@43%-9-9kh{U2e8sbhzyS~^~lV~ zr30O@q+Mm;xTr1<{C?**j~v9ESSfst{F=JospFnCFvxj^ygGX7)nlfTuZ|x6we~MZ z&l)ktOMAYcBe?dvQs`S*x)~ujH&s{JNgvc>?T>`pY$PGRqaAbwV2|FgGXlV+bSo#I zk)G63i~IQ6o!)%d0P8yqWt`&>ZEjz~UtbqtplQ~R5H;p5T_n@Cx-B&pKKIootI;ma zM!vR<@6TX*6$^g5zbfwhJArg#(Iz~zQH3uD)TC&$rmuz`9mQ*sBJi4|eCy+;{6~_) z;Hc)xmZ;!nSsaH}%|?2Wi2V__SFt;4B7jYfR@!_!N?ot&FfP;-%3k*BU1)-8k=~NB z?bQAW$uP=`&Q)Y`jrC|TSLIaDajPt1(5}ozd`(hR?X%09Vr+j)QeLqo#@odxTuxmG zZ4@aui(%tP&wdTvZ;6j^l1CGOo-JtSx$>e#YEA-3<5GxsaKhc_abwR zUt}%|YyGEwKLxOe3wLHiXX-c~4r#^>w4z@!_tnn>@kx9Q-QihRWA87ZCq>?G6|;aB z(v!57;OZ8zgzl&IU`T)7!WF&qFlozRS`#GNcvkYmR6w5yiMP3udJw9j%lw$wp!qw_5G&U^YZ0`4T z_XOTSMlTNl(^x+KhfYYn{-qO|-{Pd`9JXQ0^zbL{|3D@50AQGntOr)Nxh)M6*HU9+Q9{+ljwPXZAuYLzt|ltSD;_rGqKeH0AP{1n zL-~MrmXpkiT*kl0fPJ8NrR#fFzr}#&(%K}yVn7EN^>SiT)%%yCOI|JI4-Kv^`9H)c zq5m4AK*Gl7^!^&r^35F6dI_owk=V#b;>^MJHsdZ8+5@~j0`6~1|E=h9qaLY(fr5Yx zye#kjK6X`1t^W1D5hw}Bg1<%aEk9+1g)(JtH5Fq!)0$KD7d-;y7Yf&d+Xpjnw)Rc%*>3(%#N9vnVFe6$IQpf z%*>22Gses@Gjoiw6Z?Di-Q8Pv_q%`Hcekc{sx;G6rP4@h^~_JLeGciQLmje*R%&AC zu{Cb>Q~6Hn05+o5q_n!J)XBo$Dl;-eU(9dpv_?#Zl&Aa@bs#-;xP-WJdV=v62;7J4 z67Mo=%<09&#C6FPGtoV_E-k_pnWybk<@nBaF7%l2n%?rHCV)%dSr|3Zvt|7}@3Jy;i8i4O**ECvRq@E;TU-(}sa2XBIP z{1oW?oVmU}*@kAqbA%}6*tAJ5*g0y}u7tF= z0asS-pcF%}5;tIO?F5@v?N&BcG265lCO-WiW|X{A6bWp8`(F7y5a0Tq-uB++2;4sn zzk{1pyF?@LyW~Q)?*Z{?_Npk9ELbXd*-&Ykn*7iaEf^)ZFY~8QuqJt+V=Ng5tJr0D z6o<|!(xZW0Nb5Y}<+iO>)V@9vxl{)Z_@D!#_DTg1k$`xZAjNE&DIh+@l_b4o)ej!G z(HdI6J+Td^37LLf8GG|l0evpnr3tCRl#{Y8?7lq}xqxY(_sTLW(g;379?0ygPrfgQ zuWcF^c{<5TtSt}hi?p}M48~>k!DXkgmwcRLCH(6=r2?v1mmwxxkf_ZlBC^kOLF8gB zC$OX)3W~jS5iB9aYLXp7!{lW&ZHXkbsI=;qu!uFA4t8}wcP&Aha%qdqvCx^#&#}}Q z^vVjsjRH0yaq2Maf0O8u06pA8ptGdyLvY=0vezCz^idw*;Qw~mv2@s-qT2eNug$v9 zU{QA)4aD{}V%mQJ^nj`!-!VQ!hU|53Ju!?}_POAlP2~O*K{}7%;I|(n>`)%CNd8WXe35&# zhrdm^3xL{#JEYn6NM*DZd4rdg69WpQ63I=XpihhcA$#tmDer(ZgPPVtwIQI zo%mhILacQjhro02DZa2c!n zTs{uGjhw@A2ivlp@gEno_{1G(oD0d1Vn!(m#GDe~TOOutlxCCNYo-{|4Rr*ay^q7? zPc*-n>B?UDa<;F(F6Xbyw)Ql^MRCV0G|!=gK{T<4>3Y*Jtd(NJt^V5Qi*rSXvz0$k zY}2#oY~Yd+tRFS)XXzNig>U>?vP58ylz79S>@W_J;n`~LoR~7M5o2RISj(QQ1b{G5 zH0_(lT(0+>^?gxU%mxy@t3d05(9$zrOsI2;j925^)rxuxuiJa-_YY0~PbFS)AJ- zyz|1BYvHNm2T62e6=Y_hvlgVf?6@?~`r6j)AUTF-$-FT0BG{7cM@9Goe#*F~&r#l7T3_n=f;{I?8q&J{UzQY=1dgovp)Mfx z^3d4^dp>kO!iq!3gS5EkZr6$zEMO;=w>^zwCsxPIM^c8@R@ztG^VvPGWU~Penn{tj zIY00EJZ#8usZJnDClvWuj8Zb3p|F)>`L(|QD1g_0=tjQUMsnL0;eFxY{j~M|92K2@ zbi8MS9pBKnIvPH{7fH;GP+K<8DoTW?PKi!-e>>*2{dLxsJqY&4IGmN-5@GGI8Ayv` zvzXjFiDQ1SxE|KQ4bp}x2B)V!2^TYk!A`Zu^!8Ctn!94m-mUz##)2fBJ`yULUZJjC za2@kndA2}7X90Qvv;c?@ToD!>0_SNbnY?RT(|h-UVf&m6L7-;^tQ34U=$pX>ge@2k z&|4k^?EMSex*@_LRVTz(@Vs(9Y=N)@x_N|db!ykT=+}-A<-HVytFTRMKZF9Ddwl56 zptpSLQ*1wQV*t+)wE0l9?)E%QILEi55SIO^(osO)&`w8xQVQ#J(Hv1f&aY?1qFpcuViiI3V9?KqV#!d>XpjjA|UC;H?hx1^^Uc#davM)P0fKth~dB< z;RD}T`;O%m=GQXbDo}k8X+uM-M?PIoTzpO6^-FL}19;#AT%& z+C3z+@JPa0ESJcN0-O1#xt0|bE>Ye&;hdo+l%hXR!m&bCtJHCMW zz5e@{U`js2L6&=A1PTO%fN*I^f%3gIph56ofjPwB2bQMB8C08!c&#eBh=>~`y(Ucj zuo@J(2$-)3I+UL(7GtzY_;YYc;M+h_Hk(VS6@5tDdZ%4shz$a?intpXGT!k4c&R3P zP-{7Th#?wm;$ACB<|O^?q$2$EZ-RJrrUG{H25R_Mrn@2vnGkVNe#q~+^{IniHib|z z6mlAh9F4^-??OJOnIl54)}rMe&DkHuC54#SdaFdrEef{^j3u;sUn1BO_pqQP(b>Zk zR43HJ9LOv5SLKb*Y46luvuHc8OcO=$pRodYltNU|*+DB74wwZ^s{TA;Ir|o>6PC8| z?fAuT;^C%nh_GX5j8%PQaMi3trgFwv0l`|gF|g(aWcllr?X(y6@XCAJS_(JrqhtAq z2=4}s@lNu!7Ro+9vLVQyQ?q5B zf=~9WynyhnDs6kH+??+e8$Ve)DY|bO6D`lbV%l$>|1O+s=4fJ{Mqj=#-x(7T{MAYX z&%r@$WMIg03Mvi;C2G+iP-2;uvVNa851bQ)lZSn6hc?}qBpN{jJ$Y5dumb+Dg`1Sy z&-@&d0aErK5v`nR8p~B>yK#oQoJ`V&PL0%)^aN*~tb~9_dT0U}FAoFwDUVB_yfcM~ zqJKeL>7?dZ4a=UEsWhwD&txU*U6J*)}#eeKiTS*gc6l=4_HR3Od z@j1egZ7~zA(jyy_cr1o2ON}k-EKfT}nY*#{zGQU$!+&OmAk_OS0r8VTQpfc$zw>TQ^JTL9W7~UI0I$0AR zzR8sHrvput`T?p9sW8x|l=%Qw`Ziue3`krOv8t7mdC;Q2ffwxo8WJ~&M(HZU9G(0c zs$cEpW2OTYjxx{q&u61@|IJiX#+NtmmYsLp8ph$!$FG9dvx?R}LcPYg6GyV1yQR@Q z3<__}AEjq~VRMR;)rRz$eiZcc;8_tUVwO8W&>#g9gZz$Y!t4y65=W%@R#DroUz#EZ zl)nNK$RCQ0ktE_8P|^jcwPBKU%SR8A67M}L?BnkJV`rhK>}TuGPGn*-ah7bfZ%Nm? zz1H)U>g7wEo}Spe3poUvj5}>`4*dd*dFfm1Z6tYA#)T2JXl_){5f*Qj{y-(tnz=2Z zBpP$T{=UT2t*M}GU+8=bv4?_hX0x@KaJIm`l(O4xJ{kSb6NUJ zoz9K!LL{?bjbTuzhVcQbG(xE64#R`y`cm)Q=fMmVKyX@9EN9e6yDc-1CUI#~cjO-S9mZ@&-UpA#nb3Zm1SIVrB z>vW46uj1RT?uE=Hk<{cf75fM!tBkgDhNMdC1HwsY-bdYhFP`(0hrD1rKpe zcv!AKi+aV4rXAKbbpf0SJG2({diATDI;ynW)NTv45=O$mTmB{rBd5!`3NPYxl1k`B z;V*nJW6-E`EG(aP?{3~OdG*p!g4l2hwM%Y4B0F-3_*K0kSBJi7T5h+ANNK-eidC`( zUeP*evxfI(>b_~hnDYu3TlmRmLge{v@D(M~MWoGhpbB<^r)VoebkbJ%$!j=&xzOJz8HWl10>oT#8gQ842M+?ohjeT1hAW!zN|@{u)q zaS!_QAa!vczEPO`o@WQv2h1)gVc(4hMV}3`e!$d)Y8G^B235b>TaKSA@s&aV4mDV; zwOoX-Reje32*DTB6YS+28eyve@G&=K`3>@m6rkO=UfzIxmt|h%1503KDP0wPWqMGx z6O_}aJz_9@C01=3ytPddd-!FP*Sbc2Q3Z3VN{hDNbeM>$gieCH509RYwNHmGrSkRQ z#kMprevkt_zvzWU^bNwnEd?%+2Z2%&_>4F~^o(6~&npO)q|qDhAA5MC%*4P3*4Qz> z4UQ>z#2Z28%#y2Rj<=j9{Y-yE?U}P`A0fMCT6@Dav5TT~!hEeV|GlT~E~sy**9W+H z{-US0>Ew?UvWs4&3fSeZkrs8v^iJ(R^dK zo;69s^t;1{WFA@OVypjLDL<-FoYbZ<0O^6I2H1x!6{1cP zXCkO&W3N(ZGk&n=b4>ygHl=K|TJuI+6^m4Mz{R)f&3a9L8ZSnZi>;kkhb|;( zc49_*@VOOT$Vk&zvrLomu|G6skr29TpoT7vd?*pOT9s`Ea@D!F4-@HmqBVaLm*1yM zoRMT@?O+vz$-(*_8M26CjRvhJr$6^(QHdYriSKageZHEu)D`QMIhR|D$*!?Uq7#(5 z0T>IA%qX}bD+CXrOT*X&$^kLy*0EFWt8JF$^P8guN`w6@Aj@iTfMCp&a2ry6E{Nds zj!=Rxn6Fn0{?@_*l1cXhgX0WG#J5A{E>I*9bNldEd%PY4q^@uR36y)#xlk+z8bEmK zfvFBKqj9P`+{YopvrukV!Qa?9`%J%adMe(}+Y)6jFZSn#1N@Hj7wcrMokkQHi%d74cEa0Z3%;c&ToY9>n4B6g?UKSmpDCg4Bzf z8HstFB4ID;VyBR=i4pPsXs+Cm-$Pjz%#RnoL=zQOBI96CGwMy=*EgqSt(kjv&n~N5 zFv|xzx}?KLrZLts`jjO`R*w2hX^7fMWLY3JRG-|pj-*U{t1qiMBEk~S%_Y$(tKNdK zz727|VW*wyRW%x;s3q=!$D)1o$*oN+B3n1kHl>@H{_FSwuU#YOWEDjtKSR-;#~9tb za-}q}7G+(dBpBVbWTe#Evbk@driIf~!7`=l&S%<{onn%pK5-sCgixHggu)=$UYDhT z-sXZd4++FTHCg;!oquTue9X^C{C@ppfz_o|gRdZ-WoHE;#*VlWn{P+H$6Ekr(VWi- z1-7Nuso>L_$xh?$;?0V0ptfwL%&-h?z=Z2&v+s3j$aZd9dmMdFHJBpr=_!`q_k9JX zO>phiHm-*FVuEN@sj+4i@|-+6-h(yTu8BT>x+zr*Wsef!BEj|uv$|fmY+=p>={F+v z0Imz4-(TvWt%4$5l#GzvcE_n{K2ELx?oYQutBrwX7>d(@Z`nbb*9eOZB>J+4T|Klj zzzBqG*@x3KH!g}6cj1X2Mdv}6KHwk~DL zcc5H`<)~shjr#24{s3GJ zCCEjhT*;7z`_b%*ZF`Mr zcZ@dSw0b$sXbO`TAY@rG1f!$;R;Fr-n-+uwG(<|q7?5Xz1Xfvsv9$5r>4nWrGLtUl zAB%N^_oR6ky&1snv<-Q^enaWG47;50LIU36&H@lW(u2Mxu;PA{K;CM(sHR<`W|zh$_fI2{^x6X zW5wTe%4}EkYZqIS&g~!NmUc94(^KR?H0L5#fjySF(=3HTQtZOIXD}Fy!kT9g>Z@80 zM^0<<5u|{;9}k{mBF=2iQa@?rDL99f4kr#ybVY=ld3dbuzsSll+bqukf^V|B;U#*q za(|8QiB-qC!sdd$QRm5Sm7n~rMUg>r>phWSR#i8tssQpt435|)Y0xYJz0F;A>~ck- zc~9SdQD(X8C&+o_1=ODn{+Ww<&l~x3J@SW`73Ve#qKE(7K-Iy`_#e$>?hWWjE!M{z z!K4{)O6L|y>&n|0`es4E>#)kt{35F3d?E0|u3X=~sAjT9`#g-TY7ObcF96%*i(CQ$ zgZ|`q)acSbWyNJ0-_zVi^H}xUPUWabA};nc@^UZEQpI%<7GfdwsjBfv6rZQigpb3s z)VUtsWSyPQ5F{R;y3yQZ_I%+B1Ci->P!F5ZYCpkJ_n0#yx~oF`;nI`ER`*DLgrN=} zci`3!?G7k*paw+4?x9?KQVH3O9Z}Pk>h~iMzSV(1dkkvZ@=n@X7G2Z_f6sN*6ny>V|e=GmW;;J->vRn|>-8VY` z&sI@zU>(6w74wqMlnAz746-9R-StN^4SxGrIi|@fwx(-fl?9#W5uaOGo=SOiXFP^h zr`v&&k{=_@tPO*pO^QFU#ct^2bKH~JZij0&>WiIq^{+cNykKP&0Ri}>JG#s$WpOGY z$f-MBo$$F~AHQIPEZpeE~IpKq8EX9#%tF-vB~FYth(R6Af`~3=Otu% z37KvDiZr!e9WP-ksGU>=63hr!mbeTZdZM@MDoV4{G>nYmeb&b0;rnOBS=4UvOFDGfBciqoCZE+5m9oKH~cy{)xR3r>nhzG_j{wUG8;!g*PE_8YApqCm%0} z`qOv8a}c(COog$2{{!(Y@wkd zpN(x3s~%(@da2$~j?t^u{OW^ES$KGwJ;ASPs7r>}KjNEyp4$q{)+TvKzH+2#D~Ak3 z)Y^g=d+w(*OoZi2F?@KG$9Y1+2xYHDaeVMcXJ2LeF;)&rkcBtu`FRGc*3!8;Y9X0- zk#Vi%fBX0_bm#Yl9Bg}*eH*)ZtN$kDVR&4ZWT5S9-+WzvyU=jfHP%gNvG!sp>|%J? zHS5`HuixH*>SptV?C^xTenGf?LE5y!{a3rJ3B0-vy6zqv69O|?gbn%vH$iWT!UzjK z-iQ$zeO)mB!2YvdztW+5g;_mr_*2(jfQ2>xlyHvVuyY8=%$+|KYu$Fw$enLF=H7mv zYbsDdI0tJ2d)3VU@oyVtjAJS;)&C8)0}BSG{lEJoq-N>v?BHeVWc~lz^Z%baX0-hi zF`3G|Dv1ycgzeO=M}*jk27pA&7^(DzbJ&)yI~X0TvwP8iPjj!)EOLKCOqRr2mvd5R zs;K8&<$tNz{nh7gH8-8nP#^@UBSId-yfsOY5XDH%W@)OonZ?LBt>?lxt($2*!T7$I zppD1(6H_1QTvHPpNl?-_T1~Ac@{Xzj3hSJKJ_A-?;PI&oUtxSRV8lc?O)abjJb^-V z%1t#qzlHLso`rJl>xJk+(y4^g+1jB@hwRxntkQ^OTk$h7i-z6p03CNai$rONps^3A zz7nVzR`LK1Go{()gOOs#X1ulUy9M9zm}}lR)a1g85>>*gTFN-C{v9z)+(}WkVu*6z zVb$DNmIRH>9p6Wp{o++<_-+y?J9t<}Cf zId2%>+1)*OX=Q(I&z8&Zo`2+up*DNT_1v?yc!hv{_v(xl5XQy8KNRN)S+li`a~b8) zhA`Z%rLuW?Kr?w9c&`67pQ#4mG1}E2?Q@3JtDC1BVXoK}9DUZ0jP6`h9p25=%;Ri2 zI7X4qxYgoHiW+%Ud){=9mf-NXPci(^UO)iansgqxyXX&q0?lwee0ltIV?OtTI?99_ z*7{@4;Bm9AHfrCXDCpis=@Kn~&QTMV&#bbNETr z9P4m;B;ELOR64JC8w+nV4DGXt4CXU0pMH){k*X9R&!x!4&G`qsYH=E!4@F-3On?Jl z51{Cl-7vT3gmKFhjiUCPxNij5K@!ZESC+|9$zU^)o929K{R*v(Y#vYFrIgK0fGyM# zd7ob8&H56?0m@dK!3;fVr1mt;8dl$E&SpQSUSO4qB_7R;iYL_F7Kv_rTa)bOiQmk+ z`Ztut3Jx~B=3gd)3-jMG5%zzY$hHy|8`cNH6be652<-t2SqP)BTHwJ7MHIQ2f>dO! zTN&>5IvB#J)!TJ zKiT3gg{)kL$HA-wr@cGQP4kO-s$1Rck^ZcN6N47JY~?^nDz=?jZ3K%#xN%{O=0#Bw z&6Ks@*9^@kud7qZfyHgDmr*k6vQs`b4%08C0VN&;bURATnH>32T5X2(CDZg@2h@vl zkRQ^=S2O=W8r?E}v$RY0FFOoj<|wASzS<@T#m&;#D=4JD`dWH?3UOtPP)H zN205vk$ha*EyIDYL-zcf#lxE0%l)<iiaYWTo6p?lFJBs=cvFxOcM7k{9xx@tELI z0(6atkJ3U(Q>M@9<)=s&Uhx0iCZJ8@LM;(jFpJj5?He>14|>O99*70!;Q7lO;daw> zi&qkzGM!wNl`Kchop@Hs8x~J>s7=&#Xz9?{3W=M?IzW!1!@z%ulDGVX{Z~z<2u;uW z4h{zP2lBt8$^T4CQdd;o6hZUP$?O0)!DGqw%Tj4j(GAILAzVNQV@;OX5g*TQjv3{K zTXO4d+P-SLRMJ1qlg^WBdR|G;(`zd80>4`AY;rq~Zxr&qq%{IX6Uvkfs~D|g06SY6PpW=^J&&Mqob zaN1FLx|IU6VL;?*1*Ov5ts2QjbFmGHw3(XX>Z!;Kdxz5ZBq{uyaWZs_@ zJ9|rvac$=5Pk&5gvxJ*dtcj;=%H&QA1zrj(evNtHh%zUpOTlYHno9e2nXyF-syPbV|xUNbU$UYkr zj?4qsd(M9wDAihTD7H99#{H}gg&X*W;Cb`fRS+~TKpngWb1o(}Ujd+1!Q1;u<#)t21< z4)HB#eg94Z4F>jt2nMG6Ur-;8|0i0|y41(g#QQ|A--Dhc zWQu7vkWeQC4YRWHTjyj+MolzNN%t>5Jl6FO%AwCN7rdvuq4iqBM2(jH4tfwcWxxI7 zwd4Nr`+d?GY-fy{?rQMejjzUxWwweZ0nx2%V!nccoE+XSQ9`Ny4@QbMTaY|msjD|4 zjA~{0?~A$Ic&-6sdJt>w5S&y>#}I4z0eyYdE7}^qQ)THT^{_M&C*>Tsdz89&VVS$v7AFW@z`~t85?!n{u!2;w}iNGOZF1}#lCx$z>`V3bjWG5Ei?ro zIxB-L5K>rQ8f(ON6iQpApv5qs1$VrY+ai`9YCRpZd_NX1pXpOO$Q2(KdN{|~e2P3; zY^z8Z8kXH2i5ZGy!KG5E+3RG6IY%*5{z7G4s8p(=14XcvbMpLSjVyK9G)tOQD=BVYQ`{pL0mLj$# zz3TzC7}y2x8i0;qHGGN20W{@QWyHIQu_|G ziFAV_bWSYA9h?-)wO9yE+&5pRr_Yme?g0eoqlKr8>YyPBU8Xchx7QIV$-59mph*f- znUqx~^~?Fk^_52Bi*8jaRSod)=GFh!VljUopQP5WF-o$)X|y zFZ)T3C3J*Sf3cHhmVQY&*S~C(z#N{9i!E9t4+_bj23_PQoVC$wkHYnSeV#{zue7yE z%)={8+h7+p<9IHDMtEMT$K@n1?PGdJlhc0}Uv3o?yON%ahUB1$M4LEd%Fcn4DxE0g z^pOguI1fmzOL76Ic!vNuh6D}8diwp*+kQP8C*t`+tu{p-wPPvj62z$BHd(@NEC4ql zX;D?A zIAeBHfu`vEU#V_LmFsW|xvEy6D>b1?189u(78KVIZ5YGs8Dqy}f8uw9@i0?L-cI6R zMY?6$NH2skoQ>o40vZ;fITZ{_I^-LDq2BXN63!LrN`T<8XU#U!u-7+rR~q;&D-%Ma zqGwoZJn8dM;ugvX!iTJbW)k;Iz2W?DU&CnM(e$Z3;Xz;Vb}V`1+;jH@O|m-d7zR}E z;+YPi)Y426iN|e(NUVBmiXwil6CbB=tc2+*w^@D;)JtCV8Mkq?x)kWS?m;0hO6^BC zzzK#g?QevBA!k-hdx5$3N)bPwKL%(zJR&c~^yI31{##D}AM6xntiR@S0r>yUoc@oL z%{EO(^(7Is02G8GHWBC%?U9V=GFsKaHHt_bsEVMCkVDvU*`*eu@(iV9ig@9H{*u|q z_*tHSXKCJ>GU7U$rZ4FhZtL5dzWKe|Qzx4*%En*{CT!780MADgg>l~8Tal-WI5VPh zcEfoWRcTyiI!m$PQY8jv`;Ov$`p_m?s+SX@sh3XWi}K@<0?krlIBmO*v3Wu3^-I4# zD>GoWYj1Jq;ITWozka4=g&JV;(NQ@SvS1Ti8Hz=5H#xkg+Y(1`ZQIY*s^&ucrhB$L zaWs+jP!IZMG>}q;%|i*EF2ok(XO_?@wT4IG?iBpx(JJg{DorilmzUu-v5e(#BfZ7S zTkM?E9H4`nTJA6fnNu1kO_Yppg^`79zr;=~k2AwpuxvBtC2zIh8H2(t%h<2Dsj4SBT2fc{hxA%w`@&%_i8pPktUSu1lmtlCKX@I) z`pL?6cFvTOp=s%dyoPVY2|&TG*;nL@eCrR)ls)R2#@-b62ogV3? zJS{a0rlYS~xojah#Kga(pkWe1OQN{^Ev9;+WD;aCRPv67=tUD2I_^ z>d3)8SVI_mVqwFi3qnB?{2or?@3HmclTE0RPU3+rkS@GXM77M-;Pkd|n*SFJQQXwr#!|}D$^36sCjSv=C-y20vtorO z$jjQ;6eSD{sHFRtP!6b7sg=p4$!Ni>wZ)1zQhsVYuHXT|ND%hhlwS{&& zpT|!(`Gf3{)N{*uC}ldmZ9{OOzLt28uEwq^$n#ZYxw{W*4V~1v?$<4{_uIwcOv$+v zU_K&HaXt)6t-hkxHpiYUIC?)2`+TY^X=R#pW>)jT7-A_jq@O&A@~AgZHehq@WUJ?5 z3J@^ZQa!dT#xrazi;l(fIcXl|?G4iR7da&qFA}uu%o|T+V2%}xaM;|WEkK1rZWq`x zUCoja4c^^hL&XH{(1OG9{UBMS;t}J%IIQH8Dc<->r{vWZAzG{3gC2y(!&;yz;Q-pkFu(4IPMy1X;aJRl_$Q3!cFUuB_##$IE&Dyr~z zTQ#O+sTKLF#?Ea!1d%vxXE+^ z&y?2v!q33@(^w_h1QuO}4z-`$4?j!g>66WyW& zomv=d!-;=>d=iB_zFv$b0hz<1Gi`GMSR7qEh(+oSku3dZu_W6Coi@fythh`&%=Lzf zvo<

    {mNZvi*pZ(tj;cAJ2w&98#~~4=ZK0SLE1jd{@~3R#$XeZM+}}9%g^^T-ZKq z40YC18;)n?XiJBZpDt$~-k*q)cYq%vb+3ua!&@c+%4rcx?4z>_WEi9huO zttu9wPHR%d8IZ6O(Ka@FT0+b_&DznK^YCkW8q^C zYq$!IjE#+`zEg5}Q8?>^Fus6pULJ9>9tPe|B6lyc0R|8AmWpf0bN1&K7iKom?M}-I z6l~iF_G-4NxTM}17M}f+%1eF75VuEK*hC}b(8OmJKjf#q%5!a5QrP(*5BS7M_^GBD zfY=SPK*K>op!y(kPuZSpPu-rX1a{AG#O;9~D19buZvHn5w$9-DYf$_)eLVKoNQ4H{ zH$KF8R}3Mw`FPs4r{66s`{vLEd+8y{t~GG^-JPd%Sbp&=c%b$lT_G&@X<-f6exc4W zU>|U>!nJ#h`Ai{CB}ksob9lqY#p&RDyfwd3_yYP)rKYwEto4GPJ)@JQ(xvoSO1c21gj7!SssVO(;$rq4JL}jGeXlmRfl!Q#n3;ATXTdTLU zC8(_PM*7*3>y8f2Q^f)?(a(&f;oXt*jX$L)sDMG$&nha-Kc2x>LW%o}yUw}JJs8b= z$A>WNsCi0>`x6J*!Oy`c9=F5gAT*>|yUyKnMMmI;WRu@q#6%n^|2P#N^W+0eplD!y zf;fEOI5NzD(P9@=xy)vSL%!Xk=Ug0vTm{#y?PVoIvgs; zusI~k$L!7#cF)fjMZ3?2Q)R`2Spaa*PIsGZ(NDGLNtdFU@+@EBMh1(wA?tGNjPLlG z$lGM=jEKM7-w8^j&ntxbo2mHHSl8$|j5ukia@`aOFbzTnn}_|3aMs~*%4Ph*$@;8mNqeErYku*KwS0tav)QlBW1p zjV8Qx*lo2@HqXh949NX{eIF3URccr_yi)~+Q+leM=HCY()KyevxqW}gsoCrCsL08Q zrY34jI8{OFfmOZC7os7d5i07$VFZc918{sU5KL2PpBR#`@({`7qH@tkLdA}*<5#n7 zhPBz1u>F=R*OcDj-qoemREC(eLOc151{s#pY&d1ATP->VU4mA4ofYww)^e|P=1hjV zr_;g<>xPhGm0-PeIv4#Gnd{28UCQZU5v&Ti>!eE9$Lho4Cx*S26GpD zq?EmE&@28?sZBjvW?!@5)wR~Y6VTb>s>3nrj=xH{J2H;-V&%qYr|2up_Mnv9XRiEWQJT<0NaUya4p_FvtWW4!rw{A^MgqRhe%C1Jjq_&)1Yh- zT+PC#Rw$6>aimg4eF&spTq0amV;i_fxMK@-kwA?R2%*bCOraL+<0XN(%tKcTO;)Bj z_|v+%L8uJElS7j}`!tPuG&MZPi2>1#s(3RP7Vzn!shtziTyTB+;ESF8I(YO`#wBkf zXQ8q^J_U*=pO=!SD2;2r-pw24>MnGlf^(~9oy~7qEkoLl2$@)Tz*v8l6?&l zVc6b$@OqOjb2WtX{hl@r@3jsMI25Hg@z~K&A<-2st8!-isiJacf^mcwYsrUGs!?o^ zrCr#f)Q;|AmUU1!qI`z?b8On@V7z>>#jCvw)dl~~_Zsq}Zdu6-rY>S#X`|i5Pn)?S zgXi!OC49X=n+?6ZpK_?=yN*14_#Uq+o!F|U1?dT;wp$j&f6|naUDC=5=C|JiC9T=} zd9H-Eo1>7pq~Jc!`bRMk}0vuLCXmyY>C zFUitI!noY4!v7lBdSGO*p#DW6|JCjPbG}Q$^6!s0yU9AbIIvhai`!V5+yDJ0v%80> zi-V;bgZwm|;tHM8w7l&11~9OH^{L(EA22e1QHpiiK@S8D67N7=L#Vl=a+KpW(o=hU;P^lq(7zF}?WM@8t}Rpr6J{y?L^$u$ zKpJ7R^5Gh68$)h@I4oQGhPU{^!t$^D=gT8|B>qLM{?&Ece!eA6f> zGBgWwW7}>_;enA4f1T^gXl0z_VwU`+-dMFU^=l6CU!@?ZF9VbI7u5>(zxa^!e@j75 zQb}G`Nu7y}lZ%yu+0I=swh1MK6-)AKUoj*+RgxB_=fDC_C47vr%4UVequ`RZ3G44} zxTvq)G`!OGs^hOGYW8zm5H+wShe?Q+*x}uMFcg zOdDtL;r`e5prJ{^@YkCX~bq06}rdX5i4J!Zd9} zXJeh?7&l^o*~&N-`g%gG`~kd!8+d;e3e|RD3&%&kwY2#RCJ<6E$04;$9H8PfSiN(Fv7NSL zO;BELOdQ1a%O0csp)08Ilv2x>w1!v9@0@-r#sG#b z$B(qqeATYaP1$W^3ie-PHJR9Q1+EG4Fka4^Pd9D{?LjIz6Jue0>d=*39} zeSR6vuEPgj+m%rHt(T?TI_?O_rd-_tcvf!8_KrtWag8VZ?dULz?9uUS)o${uKr-SS zzeY!pyec)d>+)Vvf5fuK)qp!=$zH30t>l{|Gn1ILR#_LfxiW*HN-e7e zDvdYow|&9FsmD(BX!D=Z;FpW4G3U*F(2f|v%oIN$ns;PnvOpzJ+bHXoYl zkWM8wQcrGzR6ZO4@5GxUb}jYM>YoxUKs)sjxbXY6*Bg0DIV>wjW@qygR#hLpPdRZS z=}usDXEZ;hKBpSCt6rc_eMxi9i=|+Vp}cgCsNz z!GPb@-|zGHe?CyGf1yI6Q*Se;NH5w6y~cffM|z84IVCoO5r4$26@tGISPr%!D#{c6 z!b$m9(2x53h3Sn|?ob5q8@j?~xf=m*m`7vN2OG2L-oqgm?`HE)j_rGpt=aA)XD&ID zbT(v3)e#2^Yx1s~7OSk(5! zEv?ehXSf8`6BalW$>JNjY@j$!{~;rZH0`WqQ8+n)(_mcfBUY6%4p8V`w`w!c;{$K;4;PQir3R&zeAR*K@6t{q_a@ z%p%{CT}nM+yO}*miKc=VmP%gkC)(`3DI?!g?ij=f%ZLCCo+XOpDBw<*~Y$Ks2?Z^vwSAAIr zgK7&RZkgAl0ZGc|IMSW8D6tGUidnI?4r+@>MEctbB0_#Zq)Nt;fzCsWmY>b;2x1=-Q(9}rFv<3@qrY^?3>sJSDmbq#i z^*?NB;9{o1q^khnJ z{c4n1N{PKob|tr*;h;=q=NSyR|Dj={ofVz!AP3Nxigr?}#7D7}3Qtp8(g0mV1MXXo zlv`;sy~40F`J!o^uNe5jZ|LN@z#y~fs&e6^ zJM{_$Vak1L4KJ$Sxr-`BU*v~apvrw^>oj*YicRv-)l=4G+WGNib&{R{HrDM^^tAF? zfPAWniWJffs?X#yvTJXzs#sW`=^(NWz-%r`0d)X}Z;oe2 z%cK6ogC|vxVwvh%v`!W?VO=_h7aX2P@P^RKp^u+g1y}}}HhNs2~0>#I2fT3N<f~;GXs=+tFAuxVWftD@XcS*R{jC@>V-ah=Bav>%Ag2 zFj#J_l)RNYt9)@dG2`C3XEf*e|IqeMQNA?Wns=?Tt5(^oYL#uxWkqvL1bK6QV>-nRDlE<^5D;gvY|*|>QKV( zkpdAZ1D-^2S$bg+kp%@CPOg~}4|}?JqPn}An@sku##)Lg!-W)BYD9eLU>Ajb=ba1@ z@n)kf;+v$A=7l)~+F$HR?2?oUCHBGV;b>K8K-n4;5reP$<(Eo~NDCM3#n*b{mt@ug zGM>XWli*;c{gOMV7Oq2A_ST#&3%edv!{JILU-CO$^${9UAwnm_*x~z{W)VvvhRMA=fW%_i#$XYXZDAy%1xiv>zLP5 zA-e^SYbOdvm+^Jw3kh;xg;7o0@1}ScWv&_}mkW@3NZ4nuugh%a2r$R+$$$a1PDD?4Y!JnvScMt&dk)c45T1g0ykoXEH21<7@$?3 z%27nBDmtKVl-vAvK9`oi<8^M59ITRGe=v*;!`#HQc%9hzR_|JRkV0sO>X(!l)nIRb zSu@4T3XsGhT+24>*vA2efndcPY*KPjgIYkPAJd&ZiW!B%C6|yhFD*(U_oBDJId`U) z{nOrRGFp(zoRn}OSDqpT>MyjW*QrD<6PkS93Yx>Y0>s%Tef#}p8#P;78DFnm1Vx{U z*W;kpkeB@CYXpV9tDP3v2=pQkNF}p4oGxy~kA^X;+T~1UoNI>WVVd6vP5H_*xTrRP{)_gNpXl0gJYvH{3YK;%2}! zf1Nv@Hm^Qv=KiRW?F#8eb9To2#S~sT~ zfwDegEA4jmhCNhp*9X*-z&Xej)^JlMXdm8EbvkZXBM$_99=-(0->wI&KenG$2>*o2 z1qBD*EfhwZx(>^d%N`mknVTtUW(_F_bUSC1$g6FUQ%f-Pvs0WHtA$`C_72y*w=-!8 zRj%TeOk2Vi$xAtssB7JEuS4Hq&U-7-k@Gc@9|`W=wICpPGa$n!w7B+Ha-%1KYmg_s zK9Dl7q%2;akUys#k#r2A8}y=elPmmX#cIoU@lBi)qZly0@R~W%Cb*Pn2G@eL&7wFF zA%b7yC~3ABn_SpYKB6!Z1(aKPq2}s?cUfGW4{Z!d?(F5({o-KGaO}l!5e%SH9=9f) z%a5D~$?}fnWiXDZ=9^k!VQPI`mTg&#!A;BSU5-+JUm%=_I!c;f;XJ3F<~N6wIW#|& zh{W=fp#^PBRIoV>Tz>JGd}*kfR(QBuETCbD`=tCtw7IkZ->{4EwPWs}mgM&`;;SVB zA5Y-8w$dvezO7Q7z`=S1by^+FyvnlFH9$VFAX<9>Q$Vc0r} zU-(5%qRDf_#xwcR$mN3;U zdxR|~kzT`7xHOUJOvrK8L}AQzAP1Z=$_AXp+Mt zpzK7bZWet9%EJdL7W*mE6VF?LM#{Q$h(C3m33YT(wLfpRBy(y3<8Hs?Z#gk@?Dyun zBh1>iHBPXH``{pzuSwq$f}SPYH2Rs5YKn38K58JI45oA@+^|c<+hqD7o{-CT!m5|~ z#a?!Fq*g-ryQKZ~Gu?(C-^CXrc7L(iYRgU|u;U>T%|PcE$tDMKws zN2g53)XdOS$H1%%X$K`w$3Vw)EHO1rNkdK{O*2Z{KrKTvMnyLJPw8()d`g^(hDMBP zbO-ovDxmFayj<8{Pvt=W^_3MRV@Id|^|}hihPDny#t#3ovO*oj58scEDEyoiFi}z& zaRp26bPV+XL=77f0*qN!--_Ps`|P8$Mc1?3rzc$waopW{QbMh?sJvG9P7(RRNsSSyh4LYnAp|*miW0VCx{FkuhiQwQHIc-i zu-Sf(Q59e?atvL-Jot0*0_0!+)VWxLxey2tPz*Q_ki`G%hySm7b;d^ghK9zDj>2yL zsHpZI{tiWLTVzobZe-3DLryOY0SP!`GTj!zhM6WgF*3Ly{DMA^5V4yXiv~QF#415Q)5Z|||ZKPL8uf0vBw zrIG)kzfVT0$#~6Vh|zO_4ZXF7+D^8c3|fwS=?TI%R-)G&nEZW&F@$soCQw>$R90;j zrW>>$+2^>%q2h1K;$%j?PbZDB;uzB31)cQezpG&Q{q&iBL?QTPvqJC!prnY4ox942 z$_-=QMhdC9Q5CM5;E+6e?D$<%oJ{wW1&3)>fp4FT{8}y=pV(F(;q-fxNyTGbTxdoe zrZsEWL9j!^1x-tpWVD-FRYW8BPu8FH7|52Dcnz}LINJ}(T?j2fnaavIRvfx>1hUpa$&6pF??9TJKO)cns(ems0 z(+CMP*I%T|W^?yS$mI|TuS1T7mR;4sov(62>BEOEX+t$y3MTevK-VU)ynq9pO;?3O zS0k-K&?8KlR!@X}apG8`4dslyHhA|8F`Yqbxmr4CoBb?yfm!(l3=y}$yGe?eT{ggS zClP{|u1CLaSa%a8$Yv_Q&XQBA-STrs`=_nLJbBl4?uTmeGC=8`D51uqURt4j2jsK+ z#n%Z%(7P}0Vumj{#~+q9j4XjaRNq6b^g_}&WiB(tX+?6E!nrNkeS*kC zzkTQWh zb5w^)Y;~>|`iBPxXjs0#iA$T{ zS@cqe{4JE|d9VHlNX4|pJ}28ly|Qm;M7zb5z*3bj@2s;Ai7V)zYB;UdtzMf=mtTav zlj1D=yGe`AuZ({)X+0l?5B`;_Pmn-BT>r1Rs%mELWNc;b_)ncBTg6%tTN%a2CZPc_ zph-`&+ze(h1Ks#s@@FOndA&J|WSEG)MQ@u`&1mqXjmdLO|Mx9K69?f^Ow2aNzU>(s zw`*fi5D~|r;MEp4?KJl>`|-vk@Aua&x?k+AGLoX2%${PE*^3n5l4x&47T>?0h(}c3 zyhpA<`O!lZpn;UAwj?)qLo7JfblD7WzPf-JHcexBn)oHn9oN6aI_Z+5UqFHtLk<$l z=(Sx-jp2#Q+kLpyUM))I4tT`!C~64WY6%QsxTBVBDxR+HL%x%HmM-l%$2;MaCTOSj zN~fK(Hw4>-R5uDMWn701Mm_$lmZQA3gW+=2pyi(?0``fq#dki{f^|g>!rIS@5rAyG(6`j*>V4-Qo4wl)e zd=1yeQO7uBNAH!DLslCm*nB~V6Z(}9<~8iNeB!0TN&@%<9uW&5@GYPX9+mM!0(Ee! z1jME{6?+!rFK_6ctEr|mQO!nV$*H$GFX8@6#7pp+M$3R&isFlYUQ zb3KjFOFOq6?UBKuRdZnq8qL6VL!*UZ)~EDS*2QLjjq9sGp@8IZ5D); zOoD1A)Dn8Z>Y=lsLVOzLAwN0T{zMqk73wxI=Jq4Z*$&kT=)+cU#^o64oui%INhb&Qy-PJ8E#yC$f8em%t zqy0Edb?icFk2UI-e!tw=h9c4a$|yHUl$+S@?;&$-(hayZ%mKIHxvc`CiZcX=h_c`Z zodU;GTX3E}q;6_NSN7i-^2U6Rk==63fJyWbvlql)!o~9JQOINVsI{T>Ac@m-uQ!n8 zy7W&(QujqE+jDQ?Y6+UOe=}3FG5B9v0s{eo|5r{icKzRYaHQooaCCCeH+1?NF{+cc zZISp9hfQBs4Jg+%nwmArReogZAUB%>o6nFiQ&{{iA$e=U(NyEQ8ri~I`yNHlmGZhl zxQS($!h_-7saX-ib1^k_;knP=)b#ns;eJ4Pe_X)#OW{u|!ltexoOzL+j4d0OinD9X!4AmYtv33JDKOei>reJ){KKyP_TzvPi(YI$?Jirh4O36Q?#B^9AR`ahc430< z8`(s-Z{`TqOL{TCk|~AdPn!2z45MA>n6VCMNiw)zm9`MdLGRGYmoRJuyH@Y3Y<{Lb zZU3wz3JutrSf|PrxF!{EPq^yLx|&DT;WN<3Ymo)^dEPR?9kh)ccdS@K8AA|CsT>hJ zYGPyXrd_|2?jiwKcJYF}M>!=d4l$5=qw9u7@yyjWtn=?g+>QBr}_q zuh~tv85!T7 zd40dYc95+oFaTb_g=&ZZ17KY}aegRQ6smp6cx`#IdI}TQK|5JkOM^Dr5C{XZVfpZV zg21zNcvtRWCN3SrG-}rDj+V*G8b@9=ei*@B|;8T;n z#|DG8Z45uuKJ0X6%PBQOTX5p-{lVTGu3dL z^n;FwQSNZ_#%?z@QM4RUH7X2Mb1<8`yDm{DJGN5YW*Z89x^vIt&1L zDE7w2Va&vtYepTmZqfniSEM{m260cVL? zaS3a?yPbO4#1_LV)LWgWUCZ>IFa`?@{7#%^elR3)UA?&8Q-8q$VZr&SNZTx4ImGH$ z{0ftLUIhU*I7t^{$ih}z7F0xKlDrmR^m9{f^*S>=l4FG5I_TGfmftFsqEM`m-ll)4 zAeWlNW7k#$Rvc94Qm_XnM>Q|ndyyD4^uiTY9^}XwsL$Uc?~%8MN>%pqgQ2*45gSBd zLS?m=94t*!xy3Y5-WZ||Y5u-^V4TRhxJqaR)z&=?(RIvhS<+V5pqa0)u~Au8r7*~Z zaogN+zSkT4V{OP{eaDGBW69}+s1EX@YHREWKle6 z6|_t45i)$(jKC+ZDo~eb6aI#xbD$G6Ovzy}7f$*Tjr8?HCDkER$nuIlscm@RR!2+B z?ZsG4>0^>H1iw~1km!Uxw!@2KA~Ctzq9Amhs<^LvRWf+T<4yAG3*+FB9yLRe*GdxSP6N3jmo3DzqlSOFvN zSP?min3}iLN`B?4+3i3{7lr?&a4D>g-HnvYVFsh9a`F=Hu(#-Fjr%TkK|p9+w9Y_= zr1BG{oGe%)^}J#v-_JgMTKo#~6m$V!p$~Ktm^n2lLwvOX8>N^L)oJ)jxfn;j81`&( z3`jY9%SkDina(~5)fu)Q$`1xfm{bHG*3zN;6c@gP zWrNg!D8OG=U2KtD5bS5Qh-MiG30GVUt|1MP{pPf~5hBUjnZDWC+)>T%tuf0qeb<;bCQAl&YJS_U`|q+gE?6-2-kLXlz!vfr;c0M8lcvo=UNSC+ zzg4*$n>_xaBIkjQ_pq-4?^d!z8-r4Q_nCXoUeWEk;Chn@8Kc^U^XIU}M{)WY-u7|J zcy|bHSZIx1Qui6#Z&+gU&dx^BkUOg25wDQbeH@b!hiSvM*WO^)hX{`R=miv4JnOrZ zQ8d(G{jyTgC0mH)`uNA?3+CPJJPqfIMJ$30fGvA zha_1#)%S4OEU|gc3nF)sxGg0Nf*nXtzSpE?EQ<{8P$`Qijy z$2LOlT%Q6A9r0b+GWnbDZJ5Tv8Na|{)KWXjOg)c>1Hh>bFwTWx^ZVr3{Z%*mtAE=V z3JPwK?@4F1+@JzBk?}lXBWr_KZcE%*3QgY@W*cLVBm~Q@Pz>4`AV41s;o(f9OPPnt|dQ;%{8d}pATWZ(NDb}5@>onG#WcDx&Nhf(TH zCoHgj#nFTy*ptJ1aKd*)W=reU5*vscOzW`>Ernqkgpx~p+Qz8diyVqJY53s|*DnE* zqxM~(Ras+L(4}R)yr>2&p!Y}@a7HKOwiPpRNSB`#96lKebg1Vfj^7y1VpyIY+sxk$ zw`G|uXjuxsICK`aaWNkYX443`*BYg`Vec8#%68zS{w{*Hi-6lS`~U*V|BEAX|5k?l z6CVoNvMBT@+{O@S!>9+6ph`MkYLGLCc*G<|g`*j8BxRNhr8cGd<1Xvc>*_bAuR_33 zynVlf+q9R>e$yhE%z4__PIEuY9DjB7v;o(JqAOD!>$znZB2g+=snid}F~5TV_Qgnu|xga&2HZXZF&&s0U>2E+RwNd0zbA5+(=nsBr zofdmkDuy5Wl5>(8kYT9ANMJkbHapKOOs17)!8;!L?dUoxJiz^ zoe+A|V2|bO35@knzQjY%T8b}{`#O+GiBsRMr}<1s8GOtXcWgo5laq~k`T9QJGkgwW zmE2OLAH!Z>$!6xeo6?)Z%LJ?Xnj1gXnimLsewXY|6Kyct7Dj6uF}Q zi(u$4o|LSkO?H$bnt+^^f8~cjb`xVE1#PJdvtAgf{FLY?oIo^5!=?Xa7-JVPbp(ZO zHW}9|W_gO8Hh`=os!<*s zLRFdd*oFXhCq%oAmam6f=kGIXNo{q$SsG$E$!Y#j(;LGCwr!EPqB-Bie@S(XcN@Y3 z1V2^myTyi}t66UlUvPErk+{+~{p|{8C0T;o3vwOcqF{b3CD6Y&=IJwV;RA3UpiWTc zg0hd=`5T0*TPBY1540l2dWejdri1o`l2GYajgl_GR6*-P)%*%yUfJ>tqH1A-jW%3N z`Io1}x-mc{yN6JfMlA}~Uoi_BIsLEI2Wf|&s7f6HJdT9zF0jWJgeS=hEcr=UmK7k> zJE%W_GyVIe8+5TGb3-ad=)jUcl)36~_40Zq0Y{cbFar&~_zKv>&*G&X+3;((hukq(b-W(*^Ncr}Nb+j4|J%&Bd|%V`j%l zY3SKNJwDY%X+o6L;|k*-B_jAMVeG)$;EzWR=Wv{iZ6%gH%@D$6dUvE8^GB+Mfv3tL zU#LeUEJ^>&wx-XrJhK>p=fZYTed;Sz`>yIEv4G|(_P{jyHYoB8&@TO^^gwRz;sHP! zNnLn{e2$PWlbe(dLPk_ka-`(OrCa#r`^dAddyZEy24c_qI}gExmb|{LKeJ_B(sP1f zxr5IQcbxVOx4q-@Hy^7}(G3A^%A$0&Mi-AC|D0oL4b$)ait&U->;8#*>44#Afsnlk zqQ=48DfpgY+IOU63k{|k1umDrw_}UQlNC|Zu2?7C?UGPOkaB05$9X><7)5%+kY zqdH(HV3rDrKTGmTJStUUJ4uv+J&^r*Ecv7&QK<>{a?jn26RDf_J!jrJznNBS>=Qb5 z5}M=p?RV0^+W39xZE2_}!DsTRB^!5K2OTGZPpG>c&~8Or=g66d?*X!oH+i|~x@is9 z%9CjTDQA1`>@Z2`xUy6Z2e0>r)D#JoaLW%QekMtF&vzO7dT8{)n=*4~l)$rCkFMaG ziW*v@1j=5>Pv1ZjW&~Zas%b|i8Ui<)#Pn1OxQ3I+U3VRw@6)Ysnh(#1zirlaTYt5; z00sgAf%q@1uKge25U|p>u{8eAI?w;Cb~ZfyAdnxRT&IjEe6xfGFbgwN2mnQ5DzY$u zBY9)mguhI>v|;4bJmh@cOAJTw0pd-%op8}?8K=QUdarXl((*axZT)(EKW7E9>QCe< z%1;QifM0@=vWAf?!v#KdI1J-6*|xC<4(_OX;cQ(#xFfX8I7zQv2WN~;L-DxcWUL^> z0SZHiEPv*{TK|vs- zqi+8*=qK5|;fSL}lW47VB_M=cb>n=!o>Ew3Nrp{7T{r;2u=k)tACQy`3h6(0aGLcb zO4{UNf9m24QE7@*U`>B0LFJa7r=~g(rgP((m!F0-bn=m-W9LJk7l6QlxM9WI_PY)n z?6&`>D?U~R_tGgSnxS|aqh86lAh3?*5A4*RdF!a8Ec;f(KYeQdICz0XkF#2-Vtw$P z2vUdSPKMBA2g4dcb-n7^#`i51%k2xefs=nk&i0z)YoPj++KJc`v6)oz`hV~dg0X+d zxKBZ(!P0R``C*RihavpAKiLyR2xd;d&L11rF?Pmyq-LJ4qCESdwRT#PTVMpo_#lgB(l;uu zo7(6*IXf8tZ~MBoDzYldCMzZe13VFEB41>@N&pdl;Acybl5kne&+2-)rnN088hyKe zlo~y@&~;P^xm0jH=SDb%ypd3zPVe>^#yrB6cB_? zHDHULK5Qt;52IyjNMZI0A9wH+)=*`x+R2DehQp#6HI}B)9X)(Qv>8dNJ-mw!L9(e( zQ?-{Iv<4sa-9UsLoul~<#%6J~5LtDlE^Qg0tFwmc9HUcf!Wq{!VdGuLUOFGOMZA#c zmZ-Z;I1GC{(h+WRbsvNeJ0odjZL=KpK2iF>9XG^KPxLabJhJOOP0;P#W$V|jZ$F0e z_|8*uwH&Ik!&8qj)er&LER8U5%{OE9kWQ3XzQ)4EaxO}EZ@)MX-vwK{_n203Omgo! zzlq2#D1708ted2<(Ff?aT{zy)N^e0r!+VAX*f=heZZ^L92+|)cud_@B zucU4ohfRS8y~8SMlb=62%x^%Qs60#0-c)xq3Kb6LIX~q@*qZCsuxcw-sVfW%^#U~F z>NrU3L`AVMarZ~c$j;j$uZ!MsQL!Bw^9+%<&sX}nQJozHC&Y2IPmsF z+dql`eZZ(Fupdb=Fi2!@H3bC#&`tk~^K*l?Z{8^qz>uc!H89?LYfX*837`4G)|qqR zXf>2?_VrKmn&*PY?B)rmc}Zk|w4&SA!{FfH_lJ7HPh4WCd_V5YeNL8xGQ# z<8k}%_EGCGG3Wcuii?CAAn!OSPeqKeF;odbwu(+dG5J62}cCN<8IY-&%k3JKfs9u zne=AJz^P_6sN z;J>3cRfA+%IG%);qlFYhHfUnJcF(V~C{yHFEGNR1r+wrFm#GRYNQGrYAshyJSdSA! zDj;Udgt|y@KrtpNbQQV*V78jwM76ta@Fm44J7x|w-u!4D`Mqyr+q9^huG5KvjM}(% z1gDI1y?eTQyPw%j9lrKf9f4GK?twDutASzcezC5DtoXkR2e~yc`u(`rGja7RK>)3T zK)8D#z#IJmE3i8&2So7{fdQX~wLV7Q-#w%$;@%XS3Ce0nHQz4okO-~E3u_~lqroM? zxZ?%{K}WEc7Zxha^_!1DNqBKc*>L?&5=CB#s}f#nA(Z;Bm?3Jx`A}7;o7nOtF$Xce zVk_9vVWmw2W5vEI?V7VnX6;4}FsC4iR7qkQjNMD%F3Uv013Bf|lFHB1dj2~(JgF5L z5>)njZ5E~+(_kG-A;Qp#H!IowwK^J2^FXvBJZ;tJW~f!B)qU=6aYHtainDj7O{FF? zig>&d`2{6dYUA{Z1Qw=qy@jO5{67am4}D=KBI`ZH7h9cuI?b^Xd2|xw5z%}=EUEciSc8)3h-sJiG3QUoZF@>F1p!B zG{+!^UycOG1UEC~P$|!ZER@~&{OCyXcE>1`Ozb69o*qZbsj3|)Pt{06^6NB5l{45$ zGK~QRNJ^w^=D=eDR`hi~0`iI}DGSm+V-@%$5}EWoOJ;2a`aS{5^+$^G zAaBxBtYvn(K|j;NlAT&WC!C{8tO*fiOW=ez^XMBrS}QdAHHm|6+k%44;_WCkPPXE1 zKy#AnAPIhk5^)8W65|XpR$=_SrIPF*;tD+`?(!r2t&?ayE1Kt^#Nw;ORs)@8U#EG;uSQNy(!e>Q{1NaGby;~OaeCr>C za2p>KdmA3MkHj-*3VftZ7zga~oeQDR_k<0t3&eGf)CkO?ENtFN87C7eD^BFx+~&gy z7o)JNNWVFw8ScPdrSW-(v1&l8stK;wv5 zdrZ8uPoJozTS@_yPP~dZ)j?&}u36XN#{292)0|x&4fn<>$??QVP|l3%c#!cjsZF~9 z-fL|RY1Rf1hmc_M($Hq4uPtOXN?WQ>A!Bdmn6Hx&a6zx7eBklNa|J`gv)TI3Y*ESe zOjW#_gi6}dkU~~g%X*|Is#&eEFIJOQpcsu^GRnRiM=@4JB6rU73CeYMj3Li>`1_%9 z3W$^BU~z_AsKepn;a;@lqRwS{JY)+kd(q{h5`evo`+=_AYUFH2uqge3NgYf3b4yOv z4e0)KzaqWoY3u|!s>T9K`QF<4*-2iJR$vDvV@)Lk^QKSIThItvpy&~nD}K;a4$XYL z?;Y36bca{C5|65+1)j&Em*{i8tPc}3o}as9Mr1B5Fo0MMe1))u=CA9zUi5+=6F&a) zkVyZ1zbV!goUnQx5xrHq&5oAIx z(T!}O1yaE?XqPeRJQQ+!=vD~{b4|+_rtu=N+Be88EC&IGm>|XN`cU^0%{fe`t0Xx6$SrYI zS&4kJ-Q~TPq6@-nW03Yp$~B!aFPPCU!DQbO z@ZTXiShj=LMV}B-ALfb|ME#d((a}oLzs0%Ru(@5TOYZ+j?&nxlm)O(hyFqdbvungx zFOqc9U=oU8#PFzA7orv_*JL1?Xz>Mx`A}p>mZ8*(Bhu*QP`^ODVD}8s4F~7te1*hjv-AdRKpw0CrU4g#(oBJa$UZQYRe5bo=@xBErQT0CB}HkY-x}!M`&_87w zWoeAFgUYjL(S{@DbaJiZ+R^SDrY3dTDq||;@FgUfeD#n^9CQ1oD&ax8@Iu{zb^lz( z2CzA5g-&I@mO}eEktHbZ(NVOcg3uKBsFieJ=WX-DRL~+ngpqT$sJJL2AGkX@T_Lz~ z)#VqsY;h^n_WS|SLhEv}Q5CtwkPwzbm4tk%h<~JO&(;j%^=b(LV`I&}WUZChawbkY zAwH~sqv%eVd4mFL3)-Usb9Rw0;Vv>#pQAOlh5%$=E+!1_kBp(;*%oSu-C8N4S zqj6!K*;qhk<~sdELX(zhf`$fP^`$qMt9h>Tc#i&*CXSfz0()$uE}ZFYeTeBTjy5M8 z2Mbfy5GGhi2#yrobDZgfUqApjOeS;-(+Zh|-3|rW?NJZk_HI{AGBu~Ehm~X`Liw(- zR(^=f#N`h-W3HJSq#C-%mJr+viWeb3=}Q!}w@^Q@<+o-pOcj^%wsTD9mvki!Du`3ql6Aw0IP-FpErMrhq-eMVR>Lk%DX^$? z(TumchKtG2{e!kZ43m+%Gj_f~Nlw`9^stB3d~=qvBw7RUrrsO&WZ#)^_=na9X=u&SzMCE}PK?ES~q{5z0wm} zLD+1rIV3h$Q=2moWf0Qfq~Q2#H~QFSRN57ZWaCy2qL_~!}GpBuYGL%=dw*- zQ7#_=HXjkk;*5w`hKT5Xd0u)Gj2y8PIxqipYkvAqs8}rgiFxv zu$sMePq{Z7afwff0w0SzlkkHpB%mjI*sb96Z;~2| zMJiGGU#yJ#t4ZtM&C36*JNyMo3?KN{l{g!7PytC8W|X)%zCxvPYLxZg)k=Aba%FqV zBo}>ib>W@Z#jP^exT2+uK2eM=BK7d-ZpgT%MqPLP+q>1zF@=lARw+zG^-lsc4m?}; zInVA>?>xs=KJDKCct6D%K-i7~M(S;VRSCfCPcog(Korj8*dqJtgv7e?sus*XaaR>S zHSQ5A)rgP$A2_gDNYG)pc{>tNuiHOhKfaB8)X~|P4Ab7U?70gy?=?t7CC{aJDi6_^ z#^W5PPh3^tH1pW4tI{$Qo6HnSr4?IA95bxXwL0h%TU8V+JKX2#F00}x4k9RViZ7~+ zQE)V;(Wn9K$qmeo?1|fA-JKR}9#eI=oXo~(%g&OHu#U#|%WJAXu#h3yUz-f=)$_P{ z*cwZ^3yU2x+7l==PiGraSSN%5>eA>#>cW(O(GmR99FeBKk`nSXOa=x8qha;RC*@Kno%-rWua6J-Whm4kg0$_$New+qVkKy?Wz%Y?eq?#D?>}Yo3B4+x2XKa+TP77NE=U#>iy+M+$4BHP2 zOV4cmC?COC9choej`27l-;k$$3)`H&L8Ac+0 zN&HAgWLtiuPpXk_nn5dyCIZn!b8;vtBZ)mEs#`+`o!AjdIwO1@3RxGM{qc_3XI zL`_%M;XvR9C}UQ$zA_pJDTI}I1(!kKP~@j8ePrIe;~|nQ?>eG)6?Sz4Z;kJ9k3Rae za!7L@6XRXChO+J{vn1KcqHa}?U!f}x!nqG7fBWT>9Ab`y_mPcHkop@L4KOx|LHi#( zv(;%MUyW15_(Kl^yAajEJSn8Zd*!!U;*Z~8!I z*Q~wdLND77Y=8Xiu$7Cru<`95KtMQuVbb*9j!37!8VvuhDy^cn?K~X{w@had^(wfr z+>q(;(AytTJU1MHfN=3diXbJXAU6j^+WV2LF&Dp2O1D#@xR)OvWCPA?{tHj`L=soG zkGI^dP_KbbiSX>)5J28!}mSsCRf|EecAXW@7(?tnS?1Gh>oJYBC zNe;UvIuP{HeAjrm+?%nmfqvl zQO`wUq2B3aHjdJbyFCg5*CiVm{8eqKr$iiRXN5{6DYImg$O7@<%{c0Wds>p7SeP{- zfftewuVNbU5{A+b5{IM$i9r%u*`BRv=2vY=GNQsAj)(MAxEw5kgC@woinp$)43^&1 zYYgj2wUf^yA9CB22`t(MX40Z>7*bE=%veAD-t@kT*I?_TRl-=X2xpWPm2(E?b) zXLuwYfcc+y3mNNd!Svhn5aX=@rqoq4ZS48_sE@y;{3_K7@!_=z4}O+RW`}soNBIvS zxLr@nRiqB9oyF`XViLZ*sSdd^Es+!Tk0z7{g<=o-;;N1;K`Gbyj|<~suLwN;Eyldg z6|d<)fPfGnfPl>Y|B12xyB4QpEsM;L=z~U~dPbn2OMZW1rfwhgRi;GK6`l|cW{Pgp$A6b1` z(B@HCYzYD|RYWmouWHcRY&FHmmqghME6#Z3a6O zg6rLRAFL~`r5Rkrl*0%ryv9$|Q8S{P$I+gPVQMf6#$v26 z2!3;Pn)k{%qZO!;rsC?z_dlPgK@jh9O|K`{Kf|I(P3-XqUh66_@o_7Ed@oPkyyONjwO?D!Iep7?$J@AlCfR3dm)pgz zSu%x0PJSx(5^f^=!Rm{mP9{-X0fc_-krZLzZWk5M;7tt7dh#3z9fP9^s-{<3ZrEFo zO1^SP(hTpgdTRcPKakbFBBQ+HWu{EmdWR9#iyuLJbp)QX zp7bGcVdFQg#&4V8C2?sw0lf?_=OD_a!fFqmyu)41rV?aktFfgS5kelG_A0@R#rBae?xDvam*ULJk2gjYh0EA)Eo5Or_#y{l*ssUGq$` zd7pnXOZqf^W1&F;0ga*q0V({Ov-Y3)nfmUCt%^F_YdNl=zV(92YgdO$N)%1aYeXuI zeZfRT?|iNzFM*piAfvH#H>4w~Ltd%8Cx-_%H-yy$n%`#IKB1x?z`OyruSLkj8Y-4gcm$wIEW3 z=X(ip&=BLjfw}-|laY_R45MtBqxlmPDFZn(cXdXGS!XtZr97!FLp(kDo@9o-4yyw> zcaHQix?1G1LP<`ug9e4|+0vn#4D-=31Ldt`WJhuFV^(q`uhl^+bK=2J|ER!qTF}s; zg2kEH)ukj=Ir^g8*4+GU5aaZNGhSCIS?5?4t4o+fD+balZ*5Iiw_dIRggLqQPp^;;6yq5XS~e)DyR<$%1M?`cg|nQr+IvZi3Ol%O)Je zu<@&=2NMbjjJLU6=9oa;CB9XeBn6SaIjKZFqHIAB7ALmY zN!f^UOEh_XG{ChndMA2OOKIMnqp^8l1v}-es!D(gLCqv_6ec5CDV5xgEyW&F=mZg ziXRpifFv4&_4F|HM$L|0CUESVAU@igM^%iP+Vob!K{6!~_WHQS0-vN z>tRoxo^?#(jOW*2q=kaD$jw+_K~a3@&>0dMT5eA@>)JjjAYKn0)|MH9OQFDu0|mWct%5y)4OJxWDrBc%Q`Cy3NaV z&D0fd@Sn)p@RcTQ3?z`pOi-er1AEGMth)+#V86)nyEl48?>#(p7zc0E!F>C3PXX#n zOM@42Yyk{5WNqQDZp&1<6Kt{MZs^zIZKY5e{UENyQ;l-nr7Cb=M``PzxINSHr=FEY zf_C>q#IAEl$b;NP`BZ~+tVvtJ?xypRjq4IMV_@V`sCNY!k`|OHpV3T&z#}$I4^OZ2 zjHa}eDi-oY%Qfp86hT?pt{r3H;?Z8G(WWVln1IQ(i5B7r81^=l(zN4#3zkaEE-+Yd zBDrtq=Yz2Is$}x4(4jkP2PHjr7C1}vJBy6urM%@uDKm)Pa8G<6O@i>Okz;&K`xK<{ zj1@Sg-bo46kQdnXY;PQJ6jR_3df*^|->D6xCez)ym!COLAXdC;EIxhnUqtJLTPzuL zA%Qd#*cVWZIN{ZV4>T4;Q6U^^P?BvSH1dhWAE@!O88I1O&I>w%(kE|cqTgjbQ^JcZ zZrJ?%$(&>yBFZ2rc*BBEB3H*N@C)_MBRR}_R--lE(4m*sF*}ekNP-JPx8@SwD=hqv zp=F8mv-dxSGj1U9jLGA+4akk)ezIE-l0s*4{B#lxA(yT()i7wry)K+qP}nwr$(C zZQE<@c~6|_?)lC{M@;lLKdLG!Bl1^eU3upn58^4fq@r<+YQ}L%-P-nDWpYXWI`AB1 zeyhU&O@;bKOzUe6-m|(J8~@<=Kt7gF{OgYSmk{4?U>DSv-v*g4h#Z*WiduoKJvj!I zvP{A$FgcI(L{cf2swn$WF?U}(Y%wB|VA5jpG0e{A_HWx1Y$uy=Esc(?lG81nwr^$Nl+{Q` z=oQGxlr^-~9hr$!;a|J(`bL)?az&<6v@w;nXhaG8Lfz49uTkZ6x1{#bN1VTn$7~=!KsF@j z_%2C)%F+8XTWW};KVy(RYAi)hMDD9kMCOA;u)tb%(UqH!8M^Q)ZNpM3+JigVA5dkdx{<|PlwS-g7=`4^eC~F#$~A{>cDwS> zzYY)0B7=+Q3^+z1l4`+gUHu2OZva9bK(WiA^qSB7AoKJ)(tZvt>yuio^*f>-6vrId zYuxdtc(3E?uTA{$r!>l6{r+n>ZEOmbJQ+LyfOQoBfaHHeg_xPxnmAe*(f-e&8J(%r zBMPLQHTCoC%pGSh#-6G_oIm4^ZTK$MW-HEf8A)Nu4YDP=vOa^|UQeXJ5{cKc9#aAZ%7Cri2n29c4?yzQwQSw1-o&&D9rSO?m_IkY%#)jta5p6g5Om5e~aFh zXkR8MMZ+=i-O)@`pFgAdfbu3BR;k+r`fl?iSMMzdep==Xzp%c-?V(3id=N zUk2_kKS=3E@|jgxg}Em!!Po#N?8xp$Z{ZyfB6tOrW>dfx%Mt=WZhEOL7QMc zl;`0k(pQ+AUj~rnX)R-4zYqgq$B`L(`*pQ&L7g1&cJWFmg7-HLqe z`OuCfJJ!iYDe8>^Viwcj{u%24N zYWnB^^eOI~iCY-i1x20YN?prZUx7vw(sS{!Xt2tHRQ#3H_Sx$#si4@3Vm@X4qjDm{ zg6#+gQqoX&dincv!W{B0R+wae1||M29vtM8A?GK5N_t|{FfXUHw=^U&FsF^>1|8|7 z!QTb1eD^HS?p(yYHK4dg>#iS?9+`-wDLTeYdje3Qlxa$N_*noVfZc!cQna+XN$I?o z-9dVa@q5E7-V0OFruGW2D&`ZYzqtqFXGO7p%jRZb#|XyDvT(Pof`LRT>XSL49EOfi z4_X6Zg&fASq(05{ev#{pR)i{jm?9e$6xHg&P-2>=9~HA5k;Mf^T5?6m5b-s)-A#Y| zczti=@x``pzd4SW%$q2)e!$~^&ANJ?4LJ-40{-@gokKqO`TeF%YRE^YkYD%Qb`*dH zGj)Fmeus@&1WyVXOoO`#gXE!%XT9K5ZUE8uTxuWQZL=q^WP6Sch zUG(3p2UsPq4`Yg3rXDyj7|sxNw`G9_a%{3n&--L&3OIH>x%cB{|y0+F9^5jto6>k>X z`d-746irx68zHsu!7|s4s#@P`kx$V>&nu?a2R(4!sj_vTBv+l>HF&70K^3RVcZ3|J z`ZY^o;e3XyD(AmYC*&_)?ojcQQQELXpdXIbK| zezEwbAfvc7Fw0PG9w<)}B;H^`1rrZo=eor0dS^2fP90I3w40;XsfWWg;718K5&yl_ zKM*TcOeQ`kCtmdMV#p&%uRM_#p{ z&xqd|HH*LL)uhbrMx3m(w!$QJKS90fzK;WdAoN!Y=B{M=9m-?y;AnF zX`bRCWAt4~X&()IDco0)P3CyhUmDeQ$wUcvg#KMCrT7>|*Hamx822c0;7wJ{&7UzC z*<5}}d7@PHnIt1?+2<$b+&7px%0ahvhnVB!wnKnufj>2=HdAUFBr3vtKKCV@bX7ji z6Ps4_(a$0xwJ?|TVQzxe7cy_|JGUbUz39G?2e0YhxqR;1oc(g9V=QA^jgVRWwIqq1 zh+sMJ(ebwgRy4Ai?T9|=q`c&;SOR*(v4LR*JY?9C>5D1n{vv1&Pv_IE=Ng{;YQk_g z&z1UKKhtdLjCy6*jN%}JtM){=Og!Z312jv$q>9wNJp-?KNRbCMr~-SInXM>1Cy`4^ zTtm0{@^*MOT1|Z<9UUG35{AW2I-BWD7jh1 z0*N(9hULRd#}vyd35Aj@v0?8};#6zQukCq=YeXX&47hlm z@{!c&7e1(u0~wvwI~*d(;qnsbg9m8EJsaij9vdHWdXJuQ28gUxp;2V7)NGKaidxNQ za~Nw?n+t8Ft~ouEKkPjaBaV!y14Z6>s}$_AQCmahKt-;tpX5=yllIj^J&F_l$=K7} zn{Fq4{W&Az#ovWk^r4bX1T1G>g;Y3p?1MB`}` zIAT7Ved!a-IdMofxxTGk-%q1&T`MQ2s7k^xK8W zT)NK*!)Way-{uI8FMJo_lb0;UsLEI2h-+|ZD|WtB!w5(#u%@RE(lRKmo7yskl0OG% z2FU1$H4)V{eY2W8e8JaEZZa+@zMqd?do-11^}G zy}AhGWsFq_eO5^v@|@Mh+5T2}4D4*X**YHX1+**{=$M(xzNlJpKw~Ub}e@rm9s#v0;%KtAOBzZ`*J&qK-tt7XNH2g1f^U_Rksg2L66S@ z_cM%AIOUtGvV2Q+r9l^aht0);HVZ@Nt`S-_REAjfDU_9^Q#BQYD!$ApgxXQrk`>b?ay=`Tai?F0s1&K4$QmoF4z& zoGxea+C2|v@mznJb9j$O?U4e-n4vXF8NGlZh+^tm#EY_nn!#&w1DXSA=_;G>p>q}D zDsEcB-KW&N8gJ}_@^#4eJEX>3JLSMq#XZm={EbSwu#dPh5jeRN6-a?#dg4Ux3+&4C zg!3Z^u;*Cf*2^L*)n~7Ik?k2xO~{n?1lD}P4-6AQY)acV+%y$KpJ*m*@BGi5iz7C3 z%(3DDX)0ZzyAG7psBN3XN+9v4^7^I-}b8^CtGv52mUB!!J_PM6|2ESNO3uH2a)T3AZo z1_=Mu%5PZSuUNyDP609rAp@JMXTw1SwWWtb7VlK@q-x1;%lhrAjEIXT>(M-gYe5f4 zChsrFB{~^GB5u9ij@yR&AQb{A6(mAc2Pu6E8OmP!(%OoppifxGq0jJE5g3s!;ll>b zm$^{SGnqZzYy(ibMvh4T%0cScYi?Z9yy08}#5vz(1=7?|2FFVidNQRC5g-1}FU3}& z)_=F^K}&r2R>BrLel3HkHE3A^OWDS50yO5DD*AJCwWNOx2EZPBk1c>HH4M1ZE;gWx*tVf?D_r^&U zy658eW@gUqtn}7rPTvsp1oG+LLUTtl*US*5m-dzpH_tdV2x}x%(dMG}3`m-iGgc=uxnng~+_EmNI>+Zav3fq34 zi_MIRwa;B^h!)i&wwZ(F)9GcpWb#8|HJ)_+bTWu>J!G!rEOmi>qlPrl;2K+X$8(C<&soWy*amGdESyZ92ySd6&Qe~wY^LwJ&B zX*O74yEN4z6;z#<#`ynb8AThs*{MlwWpDg4xIack*~c-$oJ2uI804$qP+ zr<}V>*O+OODEqufWPo*KuX!V-k>6g-<+>{5>^3o8R4;w}-~SO$V?|iEhXe!wU;^^r zOHmX5_hI}$Q`D4o{*|J}yOe6HLnYBbQCRriLb?#a_yB5Xh=8z^Z&4C@-@FsMRj`#| zYSx;XcM^q#`~&z)akPU5jihV7J#(Go^_uH-yUYD`_4bwrkZZ6qP#A;_O4HFEp+#qF zv=I`-4R_0QyjGI<3M#x>#e(68-ARgJTBQ`RQ@n-p;MIqTs8o5`ABW{jqx=_RdZU#E z#;%eJ?xR2fw?c-%gI-E#(*b$7(JTfcQDc+X)_cYB67cVGs+!26#!y=bQUeU%x9BRy z_KYBbr<$b+PIbKAnj}s*Z3a~898xaMr%#?iLn;MzS0Q0c@uV(QNe%yWg4(W}rWkcE zXT1<6kfg~)dVSI7nL=t9{_=uNk~7F#P=1wGH(4~P1-0gI-sP8~5a`1b(b3U33+CQR zg>Ch>`5c5HU!OwVDE{RArypyE0UeHD+?XQVEvf@PJPr@dA!0WXR5^pSY~=^tYEY4K z2bg21pRlJ=@Z^5j5*q~BT7_phgLnS$)%Vh=B8Wphw zG3)A*N1mkfCjMk}&oNkG~7d|Z}p^eqrw!d>DUl*eO0 z$jUZ+{f{-J@}#feEq3XpUcz4)JEru~GHEoHT_zqB3w4|ZdrW3yGm8_5jzF5z&}^jB ziPDa`MB1gyf*+t7D{y_l+W%)N*}tqHtd8MjPe1?wAJG4v74(14`CnF0O45|v5Cekf z+Ygj<{VaIB6S6wUVi-~wtz(jf;XQFAuEH-~mqNLe3On}HWA;_X!^?KTV!r{xIpi|8 zI@p0uB+0tk+NnXoK5~n&O><=Ur@KMVAMY`_GNm;FqHgw9vlJaT?d5X7EB#$7Qo%n> z45@!~Ysj6=Fe*Lu(QGGDHRJgvVqfGqBC3BnclrcPcr)ymAlYowgYzuaaE3$3z?mVU z?j5s~6G1vq<(int1#O#3>ZX`x%;RY$Imkp2k0)gXADD=fa?XMtaqGYcXS;HRavpvn zV1+Dv5ZO|q4^u0^n08R?z0K~XOU#tFnbilb+6fz*inhSUy}UU%@(Oev0LSLkoutQZ z8}ssQ$E>jz5lp@ohRzNZrv9q(t-!48xBr^*tSUX$8~6pbSe({tP~P-XOiKyKjC5im zO_H_?#_t^3^Fip^KcXLybRfw}h*5L^#gmjEzkHACpduR?w4jj{+RCO-D?qSUe{wj* zd4hR&e?b3Nyp*>)G9mo`fETHM&-vfsh0gl_jTe-~|ACjA`5T!gX055YN70ywUjV=4 z2V1TG4PItO5~Hv=X}tcy3!bgfNKh0v-7S-hodoF(bZE6+B_l^xCn3h^q*6pru`>F@ zS3Vx162%omxTPSCvL6UnD=F-d?VmAOUlJ89DrBkMm_)Lhc1WX*7O`MSnj1#;0Lx{U zJU>fGY9fp3gD&COu)cAwptcow(KV@>=qT04Dy|6O23qBT!#{&bMfvCqG^$7@CgRJ* z2`U@XCC#v4oQsoMl0)VrZJ%ls!UPpGaS+^9rGVi)^=h9*~0Ahevje44TdDIKdWPl3T%MgfT3Y1l*#dnfrF2ajT_Mu`=)4bMEB~NB_$_8 z`1|62UabWZ6%GcZT&+Ai&AjCDa&K}r_WXQ*ff(XaAV%XOhytxq+!%|7c}Z`n8LNWv z#OR`VhRW9vY^lcT)mn2pdxuWI33ZguyHfSYW!+@OuRZD5eZ<|tJFUhwS9aIlum+D( z?RnMx4%WZG>te4-2er|Z+(AQMocjcrw_pkib&g;f8sobU*{ilUm@3g?NG4k?G`2#C zlYKZalp+|-=ZcTz7MQftl0LfSgOxlcnRkzMhiYviJ6U-46yGD9OS_G{nVk2KQ*~`? z#Eyyjc9 zgh6wV>cnYJ#~@!}dC|A4T9tM6+qW(=j^8`X2Xh01C6j9}WEWT~l37MW>VKH`N z8(%PW<7q%Lb>r%{FeHCIO=5_Z4|2RHU$i>XZ>EmO%C9r6(}pcciNP)%)7ji}ZXWJE z+i~-COyG)6R!NADYIia2+0q<4rk5H~36Y~kjj4_#u}R4eahz|F)%X*32AI2^Vr#r| zhxW#^25an3NTec=XIOh2;E?$XD4RQj?DLRaWDI5!Yt3Lx`GS509L(uRSOF%kK5~jk zf$=E^J5`{|m1CRrkk6_PcC1316=RnJ=)MkjNOH7!9t3wew8{-&ic!tm z9>Kj3e)dQZ3uZ<#gs!>->^_THrd8+^;FMPM1&sekCB7g`@A?l&0DxJX|AWRq|J$Yi zKhhyT+>%!?eQ%A)^q4-zfsmp%!y!mB{2}+nkdz6lvFrno0Nd!3;*NtRWU@hpu9VVN zODwepNL#21*~;4${zSJDMYt|(;$Pe8#GXB; zylP>imYQ{vgBU@U>1q-<{+=>j+LX}`==hmaji`axGpgg)=SW08y&{|KT)vf}K zO%!21uX`fFos(d7j>KqW$eEgMo^(KknL25o0aInqJ7-)^6D3x&12jHtNK>H2l*eUb zHHJmKI@M}ih)XS_9kxP7Cs-6vL>3!S%Bp;n+Ytq_(M1;ekQ?wO`;FFT1kLgQ7@PHu zK#oF}rva^;z9=ow&0%PWf*+wQKHNimc#i1s5k6R1@emdeC;N@($M7KjJ<(5o_<05E zZFxBNE~7odu^qZ4`<%Zff1%|RhQH0I$+f`erc+g^GCh?Cr9}l zaYTAaGN`V0%9e{#$(l#34pqwzzCDh#@-ei zwB=WX=WZ9fYY071se5R*Cd*kBj*PD5SYyeH>M2uOMqzB*f2NAsnecRJSoC|UJ=1SG zt)JKizT1e>qaI?ZM62{fno=*7R8P{wB6VBpqq6(X=FcP3E*NxPv)h<>E)yhD!HHRRNMeebB;)hu<}j_D^9Te zGk4Bzjc};WeI70YJVDk4P+I2!Dy2R^b$w(?ouQl+IvVXLIDt7dtG1e>?c=(xMhaPL z-oH&vifEeZ3W>ZW0sRKa#$ZWV_476s!-$WF>b+j?6h)tkD;E&TKG7V1KhUIHH|Uz*el~OsRCG zS7vsx?jWbJhGkiI;Tfwo$Um*T#T{-LEZr5V!`*c?X^>uVU{OB=o z1N%CR2h3$B9)zR+IpipUV{`#u=6^FKB7K#mJNCcn?^}-gV460nS^mL)`a>vN9aADR zWR;<@Wsf|#9u3mE4`@Atw;lb{4jN`pJ+m7s@4(F$7D7K?f)ZGXvNJi%=0fMW{Bf=d z$YDv@TE)6vz=f!^HD87ktQx3(0dT7@M^yPrSgI?e-6Kk!7-khtC3wZYFZN#Ug2tv# zyv8@C>z&teQS=LyIwt1sv0Jv*otRUBbU;%l@*J5gL9VW8;2VMsyKSuhly=Tue?mRY zUauO}MEMJULf&voGATFsL^|2wi*#1UVE-hEQWkZs>v^7PoIraNDUSYUs4LwI=IJrY z+F!JlyDC$9X+>K~zfDOtrJFbL@>iIP^!sN1Wc3;svUn+xGBx@KCxdh;abmMx|3J~( z4S`f9cp*r*>~}PeFqzS)r-*g*p)ly0kMx>is=|#Dn7(i7&;HbsPem#n*zIv`mqJ|E zM#$~Ktk*^DSNOMY%&$^sS8j}QwjK?{5)W58h)!pKGeNlROd~KGa=8R|4_0p-!;s4- z&!tqv&N2{1XD;5(v1n;6QgWJI|(eWmTo=FR@Rw|Yz3T|MHWh%n>ILKWidCC;=36?==b?bY=A9iBjS z2dbMq@q+HbAF_U@Wqa|4d%Oo&|8o1|Pu~3nxhMYk*#q#U>El|2N4B!tN(+C5>u$lCT-j#LuU)MpOFs{aq4fbIDAR)Y2P6f zJc1K(B-Y^2T=4ms&J~?e2rRSh1Lf7?HgVtCS66sR+n0PvB1# zB760W_Ts)ZE7AdAgfetVLx{7Vf(!#ExKYNqQFb z$S9nPg+w-Q?#%7U^|5}j4%{(f&qcL#0UhDo)Gg$Obi4%S*3fSyfVE|J`UGUqOs<4! zbL!)cBGzMaP8du^=`tyGR(I>CO$Qx4E?dkZ zPxbF~11ug5EuP%?HFGOlmo_}vu%)5}`(cn^94LI5oMwZJ8pxPI<_#+SR!JY{B?Sp-JOUk6S4Jw2up5bOL$Jwxw5As+9oeH!P zCsnVB(p^yX>l{j$*9sruisABpe`tbQtpgpO9lL}5q#j3CLaWY#=da6ZY}b(_i>R>G z7DdBOS;Q<<2kxHw0tZJWX<4FQe&fnv8R(qk=-An2k+|f-(&r%*`_|#>@wKc}}2JRfTR7w;Zgrxo~*WbXnY#dry$ip7>_w zL5&LMVX5L$MM}f~(lvz9b+;GP8rerVcs<}|ZC0ChYFwL_vlT@kRJes!cEB0vI9~Rc zOKd~9T&3v#Ztrxu+DVd|F-vLKCDp@JnXb}l%a1>EbSXVeo86LA7|)3r#>b9T+0)S) z;Lr_RG;PH(>B{jcsCN#vEuKj9lIr#macBXf6YV)(ua%aDkhGnoqmyExpm-$&_Z+H5 zANr=ch;9(oG`dWpm>y58O&)$+8OAG?uC^ORC5PMOs)zigBa1ZdrgnGy%|7DdcLl#W zKDai#!JZs8nxpZE3)!Ew9>a}^r=#}kGR4Y>KH;ZDb*YkM8A+z>SM1f$(BAn5o-o#* z*U#+&XU>dSCg~em%Vks)8Utxr9qLm^TJH(N;Te2|VJ#BIEQlo#qhP{NglqL{k;z?y zy@R_VV-B0{+EF)5CaTEhF~i&JLKZ>Q`j3m>uQ>X>cYj#lw$Rrs#y?vt%WXAi+bTFB z&oq2ar)y|a7Ujmh%MS_QD#9mCHQ0z7IRk~O4STMN0Us+TY2n(pH|Mb5lxOABql}{9>gyM2bcp$X1yO?&D|gR<2w*$X#Uy z-r_qRe*R+8WF9xXF$`pUM2KVsD5L+~QaBM+d&qjH6}>L`6UC>tP>B&w|H@rf5p9PMqoXsO)4|}9W*W=D%%oQt9$=M}c&E5f7MaYTYQVU>Z{Z!V!4gIj z)fUA+#MGSr;Gf%wN_bKRTKWRpkD~Ps4oN$8bqAD6$LW|xx-p)$!OL8yG@9_eX6Oqt zv!N4eS!Hu3LFLf*L_Sd+*cqHQG_qj(#h+6NHup)cZw#-ZtgvPbH_18~Zu)vTM$28Y zQ0DF6r7-UBmCw>6o3WiBtw!@R$$cwpUoiCk+kcI`Ac{Zy@}&63bKu!PGJ})KJ-CHN(bi z1Qw%QWu9m-9yJZE5FkNhYZ3Zwr^xD&wP>Lf!4lRR>hro+uDHXZ;bxkYAyew;E7&`4 zy7#kLKD*iYc#>uC|P@4ux!5urNLx9Zu)l%F* zKsIxH&YC{QwC`K+aK_58;5XIoL{oQ^Pl{gHqwQ-kUS$bh zYrCg=T%aDXuZ#gFDaY%BF4Z~3?wO`2b|2|?DtpLmfg{e-+-4NXZ9cC9JWw}bMAhh@ zbWvrwjXUzAY=3`mmkv$5eandYS_>G}t#y^Wz)h7u%%Ph}_fgEUW{sgr7J6fRcpm)l z4)iy#`Ly$#hB-A5>*CJ^#hUv;*C`IS?C(GE&lJ5g_Xe&&jY6()WPBpxN$z@fmD&uU zowo`Xo0Ff6+HOd)1yLq(0zLjAz|H=mX>|Gz0^@CL$9mXQ&^kO}r1JU~9JTz!ZZu79 z+X$WK^xF;F8wpyygSx$rP=HlDAlv$P=JjqgC$Vv8*XGV{(M1!w=$VE(POdsr88n<# z8$R7E`tgOAu3-*k2)W=M5i?8W;g->o)CkF}?S?x1Vb8k$<_s_I(oRkmumCCTGu z9ae$#Pt-Ttg`l;!7QZ-ZIU=e{M-*1RKvlEE+JU@fi~^sAVYkFS4xzhZo=w8=@w?dt z?1{VQ^Qep1%+DSq?~)p~2#yPutW@j0Ib!c{Si8a+B3HVCc3mO6t5Wz^177t>d?iaR zGMgTw$`RaGh^aLYYH%t?l;3o7FZnj3ztkGV`)`(3R!=Spv3q0U+PK|WV6-{Yu=^mzM6C!NR|-{K|P^H^SIy7ftozi93KX=eA#ZI z)b1YmCu?z}@4(*$UZN=o$l4l(`-q@^AO-tzPxl|^q4-V@A%xau&(c466QSWuS?*MG zT0gZ+*&0s{Oc9<;59rJvAck`RNmR zIw6vrzsXN!Yi-2rxL)*BO*$*@CRkK{g2tYs@b?$3r$$%l;Ftj zj&kK>M~9P#CDZ{8-pQK|X!2O2+lPC@E}u_K6t8M=TZ-L_A-{CX&&iy9gAs-sVDAj* zdID(s%#k6sBS054;Hx(5>KVk<1dj%;%GF0z|K~KTKI#?i^V5K5WPPshQhnefJ`wur z*$3^BTM%`7S4G*!;pw7FujO8$cztunW}9mfKa1N1hUZ)GwMT8xbrCYXO zDNc3Uhj+xtWE!S%-iYHjtmb+jq+gmW6?6vg+@r3E#-4vvJSJ~{-~%MpD&^neB#x@w z>Dte3uSCs15VIkA1A3J2R;__%KZvOvVqYlmuPnU+9F20CLL5l(Y8|DHUl^{|Nvypx ze6lHT>l5VT&dX!OZ&M`3=s#7O8jCNjOnDIW^(?*# zUV4TG-$ZV`$vzazMom8y2-_`ibM>*5g1~NEBToJ%gU7JHH4|6Rll(}Wgs`W0ZND`hC|%|L zJ7*1L!KCu}6YQZq9wT?wU7_wd^g;OfQ2f)(vsm6Cpz5m}RpFQT52|Z;cCH)Xq+VHX zLs$t28?+kSZnlW>f~)=zOb?Qr*C|EoN5RD6vV%#zN#R;Q2*6Z z;DjcrkpTn%fC2jNMT7iLGr`38-*!TXqO3d+BSKDNKh-;+?`Y6My2P^~JRUEm1`%Z7 zA2iu}l~f1XRJsjU#otVDKY#v74lf0sc=z+?ndvO{nX9w4FK__u!Cq)?y6evRvHaxx zh6otiSPTxcGhnb4s~RKf>S!0>Jdw(3VIzTU-E1;Kwh|$*lrS?3MM>c#bR$M&7`Sg0 zEzSWii(`cYp-~17YcrhM;Mn$en|lEg#7%0jkPHRNiCmg94=D0-8-_fhgs3)M`z0F& zinXpB-Tk*K!8P(2(`M@!gH3!;mzj_n5xlcB6OvqVd*|-CO(U0u%e@XG-Wd%joX8g^AvP z6U^fRfjHh6m zx+keoF3kel8`T4j*RjD`;gtz;;l%$z)Yfa5k}pD}ZbM2f3>H~wbV_W<*uby$T=#sx zxoX=pD3#sYmbef?(BU<;CcTm46aqWZr>@z?*&k5LNt1=BNkNis7+7^oO!MHb}o{gE*48%A=-c>tXJSXZ^uFd(;<7=1TkGd9lSAJ3*yN3iOFrATI`a=4Zl59le;B;*uiUc5AOHXv z{|pKJH-GJaW4J`+e-@rhq@hKC5Q{gIrX``=(ri(yPaHM0Nb(0M--o7~l50d@Yu~6D zS$JsTAJChxTc>PZ!Q@}X^hpXHr<%67W_{h7P-18N6k4%FR- zDgwrLU>=!i4vZsi-i13to)$?M$bo2lX9?^|KRIn;xHX6k+m1T~bVTGj?VzMcH-qqC zT{1YIvbl(Jt|{CitvTmP)yXuJpz&_U z#@cF9E!$V6a^^PfIE53o-`X}h&u+Dz>Y1^(`es@D`yf-TFlz0xWXzG*WZkrXW1T*m zDA&#LRau~}<~(+2b^+625*5BXtD2M8YO=YNs>4iU({`9*UbX@=w&`M3dcc|5rOt1e zv7=M=DN$!!)oR7(DmgZiq^dEG>oevodnUrk1v|E^Bb0-g$3d;X_Li!pi)Qo3TDgY# z?2fLpvms~AO|-z=7llSamD6(731`XG$NrRY*wMtUNo;1PU{RgrcPu$@4l_BCa_Cdi zA?pB<>U0NU zkQ!Cb>5QFfwW08kh%SK+b&sevz8Oq8&p$6pjyBB@S={`Qy&#?=ZqF$|-c!$HxhgaI&G<1@LLm(^9sM zlgn1Va-h6t7fSE~_1W$0?!yhWjo#fw#<0sL*V`gFpG1;K6G$c-m&+oTfE7M|jdJgR zaxVmsx9fPXWEoTqq2^c8+(2RlFi7GBFi4U*Ji=$pHy}(x3^J2_3K2PCXsA=Wm`9ya zdbi|XzTH6x0#`ov-lJ$B!!ZvKhk)0He3C`$A&il&=CfovNCtvWa4t1Wqq&6+dZ4gk zsC}|OnWhy7=$-wFqwgVQ3c7_+gf8Ltbqedq!AcWm#pjqpOv?3?OVRTPZ+Gl0(UP!6 z&GLlyBG153bky$P0d)Sp;@xbEsLwjyQ=nAUgf~*c%X7i~jG=l2iPELIghC)i@(4aq zs`tDiZ;Wl>;XUFhIaY`4G#?@5en;u?-*isCyooYM(W}IVO8je*N}}#5Gveb# zlCmOx?xEprt4~-n*>TxrDQA&SyS+kl(Aj5Q!p{gqR-)Q=izsQ~NM(8UyEcgKMU0wO zO8S8yqgOzj3YYxNDOt^=SernMoDE)*~5q+4&; zNp({aW%Au&d=Ig29R4MD{&IbcQvoFw9adx$!Le4qsYP&x_^$O45N z0K|MZI)U9vv98yxuPJJO-s%;_0Y_MB)@Dl$ zlX5(Ynr}p56RWROLJn5miFt^W)UypA^-Ze<+3-IQvLuOG2CA{*#`{Jm7_q`eU)}^S zIg4~kJhF{^s@tM!CA$+8O_T8ag#41qR(K*vRA(iru6YHv&J&Y(!cNpU;ot+v1z+7q zHH$w#-mR-?=PZb`C4*x&H#hMHPjRV<3yV)#RrL%Ih6jhH`=*MKr^F zS>~L1N3aOxG8FOlJixpP4PlT`sz5seBqNtf09<4M0f~8p)7iw0j2*St58DQ84eE9A zQo%%7!#)nPb8~F_+!&w3CZMceI>@&K$2iTRu;MO(pe4-sv8l-c0Y;+q?1Wwu#{5 z7=PF)!o(8u7y{!8`7=2@Ea(OhRIM^~X;PT(HjS3F7>K(e0wu`WaO;7apY=d=3+x@O^WcUY8r(4WMq1Uo zOott{M+$LJaQtuVz~UZ3@LDGNykPf9Vc(^v(LHrKF{6nhkefZ zu-2+s^`&Z!-}u)Uqh`(VCs55>hU2V#NU|=+(>BpLESVo|lPe~4-EEd zL{Ssf{-8x&qMzNO)Ry8BJ4K6CItBYd13{m5*ScX;k%qhlB*$OIWtS034IYAt6Z)f( zl+wYytg!-wCbHvg0ddaYIz?M!U9G==5Y0LJ9h8dI0?+W z@NMQPzWQ7E({0Ld0m8$LtyB{rF}Z%4%?Q<)Mo+9Wei)%%Kqq?-%giG&fknO#I#wmx zEY?PN@d!JuKTwor_swMZe$BBq5fx+&!g40HlS0Cr$|-GRZk#dnJyM!LxAX<=b&Tm;IKFX;)g*-sC$HU&hzLsp z5n~ZH%9^z+1FE$Bk*-Kx2+9(X5F?h{m$?UrDPs$Xsnc&D$Opnu$`?85Cr^dQw3q40 zjm>J5`?b&aAKiz0*1=`Bme+8Tn@R^cctMX_SyY-b*4ud9+#t_c%WWRpoyHZ#MS>9ef{}J z{SQyU!qmor_8&t!3m01((YSe-0S18RF8J5Jum34R_%}XEBBqn3AzI^r_0(dycdtcg zn2=_6@^1DEwUKG>R?)&OVyfC@B+fvGHMLWdy(4e6CnNp7*JoUEg%}*m=X8R)?U+^UkNg^~{8AVMh-CBdN)d|4z@$ zfBCD+4o;RghQ6;6U1YoDeBiI06!xt{~bs^HKms zK)S#7nyihzla)KGkAJ>S?M1Lk`#B#9l3-&{9Nvzz9F@@|q>T1sjgDFEy){T4 zcZvxhBTYID5U%Cv8HukGlkInCq)X9|>qH3Y86^lEqFgTW53|;h_PS>LRZ5Ezit>vH zx;a|57^Bm1Y>$qL)P?m>lIXNYFV1Dsk48njk|f?2x0H6{rBf;bN{M!QWM_W%!N_id z)%0OxP4EECX%*Q_kIZq5J@tE7_I~#+36y7{R+z7TC$_H~FLhJ#Sw_TCuHVp$YhEjYK$O|6 zLVrOPaUsfBB52xfL#3=WcW{I(s!Rs8;!bsx8|6;TYg)v4#f_DjgLg@ICBINYvSPrt zU+yVms$^!(T+zVbbAl zV;6rWTZ<;rXT6DunIC9~FR`$=l&4_XDSzP)${dA%v&cpR;Ra&L*H4RE2LEEDg7CnF zT{cPQahbxs+m1Kt7wM$QBo?yP?%(i5dD`Iev=SFTA|1uh8-@BHzf1yc+7`RO;(8Im zn*X%kH0WI@MKHI-8%y8a$E~@p1=1U~7Qv!3YA)khSDs0EWz$#q@XfQj?l>paA)~B% zg}Wbuw3EC1_CyOI{et&ke{Habl?{oqNnvB?;C%MV4B%Z0)yCc#h=BHNb8v#Jvq`Kl zWcrRI%4C$2OX}T+@X+>_VCP$yl?Q!9j|CNHA_g5OE_vES=jCvD4O5#~!??kHilgUt z3$fD-iLfbtRloldC# z((C^-8diI8M_EPt9N8Q*Vi3D*mn=<2HpVF>lvGYFW^4sQnuIVx4=bIK@N6B$pQdO6 zDwUK@ZIx^bHnWt$$*grLsF4iHFSww8Dfpnjcc07kwP#^#takE*A04&Ndb|F(c26tw z`#dS?1@XZ2lN{)Sm9!aD-{kbBjezw~9F~Ue$`+y4Q@nd5L3a8SLze6!3bUWsUnbET zY*X>(j!^X=47eG4>4f%{pQ)SVwkL5=sqCa+ubbfBRlh4`c}(5vqK(r}t8za)hlK8; zE;&JNCpjUI>L(cA=S539dN;OYRB}_S-ASu@+Pp(~X9>t7y>osS8m3p(QJiC>cn>sB z0bQL;o@fQ3J>VF?sh8cw=m|WYJ1`ubYyLjMf{TPX8V7f!T7RZ-3Z^ERUGGh8ALTph z2+CZq6Q<54K5znXzMI2z!y|9D)w9_&=Ty554s_jC&1pT8!S3j5${z3Dl&7%3a)=V@ zuOysA8Z@M&?@~-cM=cKB3bf2);FZ2U7)&*oC>$RR`;%>@qt8SiJ%dkDZ0?h&i!HP2 zqYBN!m|?Vv9qO(FN*Cnzrjb>nwj@v{zfnq*&Z3EM{;nZsVmXj|*^g#;@1xlqV1}6P z+qwyrbMzMqz(5}(v$xZfT0mN4mL@{nL2lhBIa5q)z84~vJFieDLq9O=7y+I{zrW1} z`G_@HX(rdHBZ=&4gvv9bb`H=?AJ04r=R2lzPC>*xP*k^4a`1an^4d((GE$=(BZ#2b zT+r~P4539yZT?LSZcW2a zp5l2V_z3maezAVgCcBei$0hVGfBjeS8K=nQqTL17B76owH)wt8`LiG)pp0n{aX!zzN zgr>c<%V0x#Z!)-XN`T)1Pm{)au}o}nuw!b>!LIA;x!=aqgN1jjc}6oS7AsgOKtn-+ z2yydtwyAnrNWnJsyCzSg_H}1j>#txNI!^Wnm_jWt>r|M_WZ>0XOb|Fw%%x{A-T|V8 zTbyi@f}QR)v`emRe6uYlZhYU%`R&f9(?S(^v&VrzXx+I`QtzCO{3P$EY6>Z1{XG-~ zZ3fuQc2J9FS~;usNb_`GY~Qd&7`7#hz0bMEsDY|S3MTd{Kc{_NxZY6$)XTTSfowe& zW1!ZHnbTHQVi!YjEQ)


    ^v7e>L((hxz&GsuhDi7w} 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%0A~2TOX$v8!iDM-U5$dQi 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 OfXNR-ju5&-< Date: Mon, 18 Dec 2017 12:02:13 -0500 Subject: [PATCH 0172/1380] Remove unneeded type specifier in lambda --- test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java index fffb220d27..58a51c35b5 100644 --- a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java +++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java @@ -89,7 +89,7 @@ public class LoadDetachedPluginsTest { @LocalData public void upgradeFromJenkins2WithDependency() { VersionNumber since = new VersionNumber("2.0"); - rr.then((JenkinsRule r) -> { + rr.then(r -> { List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); assertThat("Plugins have been detached since the pre-upgrade version", detachedPlugins.size(), greaterThan(1)); -- GitLab From 7fc4815e117eda7e598599fd4bddc97362afaf95 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Mon, 18 Dec 2017 12:40:20 -0500 Subject: [PATCH 0173/1380] Do not downgrade installed plugins when loading detached plugins --- core/src/main/java/hudson/PluginManager.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 14bb1c1e0d..d6b02ad845 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -650,7 +650,17 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas continue; } - String artifactId = dependencyToken.split(":")[0]; + String[] artifactIdVersionPair = dependencyToken.split(":"); + String artifactId = artifactIdVersionPair[0]; + VersionNumber dependencyVersion = new VersionNumber(artifactIdVersionPair[1]); + + PluginManager manager = Jenkins.getActiveInstance().getPluginManager(); + VersionNumber installedVersion = manager.getPluginVersion(manager.rootDir, artifactId); + if (installedVersion != null && installedVersion.isNewerThan(dependencyVersion)) { + // Do not downgrade dependencies that are already installed. + continue; + } + URL dependencyURL = context.getResource(fromPath + "/" + artifactId + ".hpi"); if (dependencyURL == null) { -- GitLab From 01f70d4290859628ded4890fd6ac26aa6271893e Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Mon, 18 Dec 2017 13:14:24 -0500 Subject: [PATCH 0174/1380] Don't copy the bundled dependency if the current version is already installed --- core/src/main/java/hudson/PluginManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index d6b02ad845..28c845a7f7 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -656,7 +656,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas PluginManager manager = Jenkins.getActiveInstance().getPluginManager(); VersionNumber installedVersion = manager.getPluginVersion(manager.rootDir, artifactId); - if (installedVersion != null && installedVersion.isNewerThan(dependencyVersion)) { + if (installedVersion != null && !installedVersion.isOlderThan(dependencyVersion)) { // Do not downgrade dependencies that are already installed. continue; } -- GitLab From eb77e5f20d150c272755435201120dfd8317e180 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Mon, 18 Dec 2017 13:16:20 -0500 Subject: [PATCH 0175/1380] Upgrade installed optional dependencies if necessary --- core/src/main/java/hudson/PluginManager.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 28c845a7f7..f9a7cff84d 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -645,17 +645,23 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas ServletContext context = Jenkins.getActiveInstance().servletContext; for (String dependencyToken : dependencyTokens) { - if (dependencyToken.endsWith(";resolution:=optional")) { - // ignore optional dependencies - continue; + String optionalDepTokenSuffix = ";resolution:=optional"; + boolean isOptional = dependencyToken.endsWith(optionalDepTokenSuffix); + if (isOptional) { + dependencyToken = dependencyToken.substring(0, dependencyToken.length() - optionalDepTokenSuffix.length()); } String[] artifactIdVersionPair = dependencyToken.split(":"); String artifactId = artifactIdVersionPair[0]; VersionNumber dependencyVersion = new VersionNumber(artifactIdVersionPair[1]); - - PluginManager manager = Jenkins.getActiveInstance().getPluginManager(); + PluginManager manager = Jenkins.getInstance().getPluginManager(); VersionNumber installedVersion = manager.getPluginVersion(manager.rootDir, artifactId); + + if (isOptional && installedVersion == null) { + // Ignore uninstalled optional dependencies. + continue; + } + if (installedVersion != null && !installedVersion.isOlderThan(dependencyVersion)) { // Do not downgrade dependencies that are already installed. continue; -- GitLab From 94b1ba978213a0929fc90808c22c0d9b749a8c17 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Mon, 18 Dec 2017 14:21:30 -0500 Subject: [PATCH 0176/1380] Add test asserting that outdated dependencies of detached plugins are updated --- .../install/LoadDetachedPluginsTest.java | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java index 58a51c35b5..856e5263ba 100644 --- a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java +++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java @@ -31,7 +31,6 @@ import hudson.PluginManager; import hudson.PluginManagerUtil; import hudson.PluginWrapper; import hudson.util.VersionNumber; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -87,7 +86,7 @@ public class LoadDetachedPluginsTest { @Issue("JENKINS-48604") @Test @LocalData - public void upgradeFromJenkins2WithDependency() { + public void upgradeFromJenkins2WithNewerDependency() { VersionNumber since = new VersionNumber("2.0"); rr.then(r -> { List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); @@ -98,7 +97,25 @@ public class LoadDetachedPluginsTest { Plugin scriptSecurity = r.jenkins.getPlugin("script-security"); assertThat("Script-security should be installed", scriptSecurity, notNullValue()); assertThat("Dependencies of detached plugins should not be downgraded", - scriptSecurity.getWrapper().getVersionNumber(), greaterThan(new VersionNumber("1.18.1"))); + scriptSecurity.getWrapper().getVersionNumber(), equalTo(new VersionNumber("1.34"))); + assertNoFailedPlugins(r); + }); + } + + @Test + @LocalData + public void upgradeFromJenkins2WithOlderDependency() { + VersionNumber since = new VersionNumber("2.0"); + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + assertThat("Plugins have been detached since the pre-upgrade version", + detachedPlugins.size(), greaterThan(1)); + assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", + getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); + Plugin scriptSecurity = r.jenkins.getPlugin("script-security"); + assertThat("Script-security should be installed", scriptSecurity, notNullValue()); + assertThat("Dependencies of detached plugins should be upgraded to the required version", + scriptSecurity.getWrapper().getVersionNumber(), equalTo(new VersionNumber("1.18.1"))); assertNoFailedPlugins(r); }); } -- GitLab From b98ba8c8ffde0c792da0dfda1eb308cefce9599b Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Mon, 18 Dec 2017 16:12:29 -0500 Subject: [PATCH 0177/1380] Update local data for tests --- ...upgradeFromJenkins2WithNewerDependency.zip} | Bin .../upgradeFromJenkins2WithOlderDependency.zip | Bin 0 -> 168835 bytes 2 files changed, 0 insertions(+), 0 deletions(-) rename test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/{upgradeFromJenkins2WithDependency.zip => upgradeFromJenkins2WithNewerDependency.zip} (100%) create mode 100644 test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithOlderDependency.zip diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithDependency.zip b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithNewerDependency.zip similarity index 100% rename from test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithDependency.zip rename to test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithNewerDependency.zip diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithOlderDependency.zip b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithOlderDependency.zip new file mode 100644 index 0000000000000000000000000000000000000000..b85561cf0dd5f0bed6182727cd246cc1404263bb GIT binary patch literal 168835 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 Date: Mon, 18 Dec 2017 16:35:23 -0500 Subject: [PATCH 0178/1380] Revert "Upgrade installed optional dependencies if necessary" This reverts commit eb77e5f20d150c272755435201120dfd8317e180. --- core/src/main/java/hudson/PluginManager.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index f9a7cff84d..28c845a7f7 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -645,23 +645,17 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas ServletContext context = Jenkins.getActiveInstance().servletContext; for (String dependencyToken : dependencyTokens) { - String optionalDepTokenSuffix = ";resolution:=optional"; - boolean isOptional = dependencyToken.endsWith(optionalDepTokenSuffix); - if (isOptional) { - dependencyToken = dependencyToken.substring(0, dependencyToken.length() - optionalDepTokenSuffix.length()); + if (dependencyToken.endsWith(";resolution:=optional")) { + // ignore optional dependencies + continue; } String[] artifactIdVersionPair = dependencyToken.split(":"); String artifactId = artifactIdVersionPair[0]; VersionNumber dependencyVersion = new VersionNumber(artifactIdVersionPair[1]); - PluginManager manager = Jenkins.getInstance().getPluginManager(); - VersionNumber installedVersion = manager.getPluginVersion(manager.rootDir, artifactId); - - if (isOptional && installedVersion == null) { - // Ignore uninstalled optional dependencies. - continue; - } + PluginManager manager = Jenkins.getActiveInstance().getPluginManager(); + VersionNumber installedVersion = manager.getPluginVersion(manager.rootDir, artifactId); if (installedVersion != null && !installedVersion.isOlderThan(dependencyVersion)) { // Do not downgrade dependencies that are already installed. continue; -- GitLab From db7a19810d77caf303b0b07ad911ae575040c088 Mon Sep 17 00:00:00 2001 From: Josh Soref Date: Mon, 18 Dec 2017 21:19:06 -0500 Subject: [PATCH 0179/1380] MessageFormat treats ' as a special character https://docs.oracle.com/javase/8/docs/api/java/text/MessageFormat.html Test case is jenkins/computer/node/configure Help for "Launch method" Without this fix, you will see: Launch agent via Java Web Start ... By default, the JNLP agent will launch a GUI, but its also possible to run a JNLP agent without a GUI, e.g. as a Window service. You should see: ... By default, the JNLP agent will launch a GUI, but it's also possible to run a JNLP agent without a GUI, e.g. as a Window service. --- .../diagnosis/TooManyJobsButNoView/message.properties | 2 +- .../resources/hudson/model/LoadStatistics/main.properties | 2 +- core/src/main/resources/hudson/model/Messages.properties | 4 ++-- .../hudson/model/MyViewsProperty/config.properties | 2 +- .../os/solaris/ZFSInstaller/askRootPassword.properties | 2 +- core/src/main/resources/hudson/security/Messages.properties | 4 ++-- .../hudson/security/csrf/CrumbFilter/retry.properties | 4 ++-- .../resources/hudson/slaves/JNLPLauncher/help.properties | 2 +- core/src/main/resources/hudson/tasks/Maven/help.properties | 2 +- .../resources/jenkins/install/pluginSetupWizard.properties | 6 +++--- .../model/CoreEnvironmentContributor/buildEnv.properties | 2 +- .../resources/jenkins/model/Jenkins/noPrincipal.properties | 2 +- core/src/main/resources/jenkins/model/Messages.properties | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/core/src/main/resources/hudson/diagnosis/TooManyJobsButNoView/message.properties b/core/src/main/resources/hudson/diagnosis/TooManyJobsButNoView/message.properties index 5416b7015e..b612682616 100644 --- a/core/src/main/resources/hudson/diagnosis/TooManyJobsButNoView/message.properties +++ b/core/src/main/resources/hudson/diagnosis/TooManyJobsButNoView/message.properties @@ -1,2 +1,2 @@ blurb=There appears to be a large number of jobs. Did you know that you can organize your jobs to different views? \ - You can click '+' in the top page to create a new view any time. \ No newline at end of file + You can click ''+'' in the top page to create a new view any time. diff --git a/core/src/main/resources/hudson/model/LoadStatistics/main.properties b/core/src/main/resources/hudson/model/LoadStatistics/main.properties index f8e8787faf..62d0348c77 100644 --- a/core/src/main/resources/hudson/model/LoadStatistics/main.properties +++ b/core/src/main/resources/hudson/model/LoadStatistics/main.properties @@ -51,7 +51,7 @@ blurb=\
    \ This is the number of jobs that are in the build queue, waiting for an \ available executor (of this computer, of this label, or in this Jenkins, respectively.) \ - This doesn't include jobs that are in the quiet period, nor does it include \ + This doesn''t include jobs that are in the quiet period, nor does it include \ jobs that are in the queue because earlier builds are still in progress. \ If this line ever goes above 0, that means your Jenkins will run more builds by \ adding more computers.\ diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties index 585d1786fd..35d5c33cc8 100644 --- a/core/src/main/resources/hudson/model/Messages.properties +++ b/core/src/main/resources/hudson/model/Messages.properties @@ -67,7 +67,7 @@ AbstractProject.ExtendedReadPermission.Description=\ AbstractProject.DiscoverPermission.Description=\ This permission grants discover access to jobs. Lower than read permissions, it allows you to \ redirect anonymous users to the login page when they try to access a job url. \ - Without it they would get a 404 error and wouldn't be able to discover project names. + Without it they would get a 404 error and wouldn''t be able to discover project names. # WipeOutPermission is only visible in the security configuration if the system property # hudson.security.WipeOutPermission is set to true AbstractProject.WipeOutPermission.Description=\ @@ -77,7 +77,7 @@ AbstractProject.CancelPermission.Description=\ AbstractProject.AssignedLabelString.InvalidBooleanExpression=\ Invalid boolean expression: {0} AbstractProject.AssignedLabelString.NoMatch=\ - There's no agent/cloud that matches this assignment + There''s no agent/cloud that matches this assignment AbstractProject.CustomWorkspaceEmpty=Custom workspace is empty. AbstractProject.LabelLink=Label {1} is serviced by {3,choice,0#no nodes|1#1 node|1<{3} nodes}{4,choice,0#|1# and 1 cloud|1< and {4} clouds}. \ Permissions or other restrictions provided by plugins may prevent this job from running on those nodes. diff --git a/core/src/main/resources/hudson/model/MyViewsProperty/config.properties b/core/src/main/resources/hudson/model/MyViewsProperty/config.properties index 1faeee49f4..659a36b409 100644 --- a/core/src/main/resources/hudson/model/MyViewsProperty/config.properties +++ b/core/src/main/resources/hudson/model/MyViewsProperty/config.properties @@ -20,4 +20,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -description=The view selected by default when navigating to the users' private views \ No newline at end of file +description=The view selected by default when navigating to the users'' private views diff --git a/core/src/main/resources/hudson/os/solaris/ZFSInstaller/askRootPassword.properties b/core/src/main/resources/hudson/os/solaris/ZFSInstaller/askRootPassword.properties index c8da85692e..d5dece06e4 100644 --- a/core/src/main/resources/hudson/os/solaris/ZFSInstaller/askRootPassword.properties +++ b/core/src/main/resources/hudson/os/solaris/ZFSInstaller/askRootPassword.properties @@ -21,4 +21,4 @@ # THE SOFTWARE. blurb=It appears that the current user account lacks necessary permissions to create a ZFS file system. \ - Please provide the username and the password that's capable of doing this, such as root. \ No newline at end of file + Please provide the username and the password that''s capable of doing this, such as root. diff --git a/core/src/main/resources/hudson/security/Messages.properties b/core/src/main/resources/hudson/security/Messages.properties index 086cc59afb..a1de6958a1 100644 --- a/core/src/main/resources/hudson/security/Messages.properties +++ b/core/src/main/resources/hudson/security/Messages.properties @@ -33,8 +33,8 @@ HudsonPrivateSecurityRealm.Details.PasswordError=\ HudsonPrivateSecurityRealm.ManageUserLinks.DisplayName=Manage Users HudsonPrivateSecurityRealm.ManageUserLinks.Description=Create/delete/modify users that can log in to this Jenkins -HudsonPrivateSecurityRealm.CreateAccount.TextNotMatchWordInImage=Text didn't match the word shown in the image -HudsonPrivateSecurityRealm.CreateAccount.PasswordNotMatch=Password didn't match +HudsonPrivateSecurityRealm.CreateAccount.TextNotMatchWordInImage=Text didn''t match the word shown in the image +HudsonPrivateSecurityRealm.CreateAccount.PasswordNotMatch=Password didn''t match HudsonPrivateSecurityRealm.CreateAccount.PasswordRequired=Password is required HudsonPrivateSecurityRealm.CreateAccount.UserNameRequired=User name is required HudsonPrivateSecurityRealm.CreateAccount.InvalidEmailAddress=Invalid e-mail address diff --git a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties index 0e47fdc8d5..5fdc2053ae 100644 --- a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties +++ b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry.properties @@ -1,4 +1,4 @@ -blurb = The URL you're trying to access requires that requests be sent using POST (like a form submission). \ +blurb = The URL you''re trying to access requires that requests be sent using POST (like a form submission). \ The button below allows you to retry accessing this URL using POST. \ URL being accessed: -warning = If you were sent here from an untrusted source, please proceed with caution. \ No newline at end of file +warning = If you were sent here from an untrusted source, please proceed with caution. diff --git a/core/src/main/resources/hudson/slaves/JNLPLauncher/help.properties b/core/src/main/resources/hudson/slaves/JNLPLauncher/help.properties index 2b2bb03e04..2b30c4da21 100644 --- a/core/src/main/resources/hudson/slaves/JNLPLauncher/help.properties +++ b/core/src/main/resources/hudson/slaves/JNLPLauncher/help.properties @@ -27,5 +27,5 @@ blurb=Allows an agent to be launched using various environment \ variables to Maven, which you can access from Maven as "${env.VARIABLENAME}". diff --git a/core/src/main/resources/jenkins/install/pluginSetupWizard.properties b/core/src/main/resources/jenkins/install/pluginSetupWizard.properties index 1c2e709180..01ab65a992 100644 --- a/core/src/main/resources/jenkins/install/pluginSetupWizard.properties +++ b/core/src/main/resources/jenkins/install/pluginSetupWizard.properties @@ -39,12 +39,12 @@ installWizard_installComplete_installComplete_restartRequiredNotSupportedMessage installWizard_installComplete_restartLabel=Restart installWizard_installIncomplete_title=Resume Installation installWizard_installIncomplete_banner=Resume Installation -installWizard_installIncomplete_message=Jenkins was restarted during installation and some plugins didn't seem to get installed. +installWizard_installIncomplete_message=Jenkins was restarted during installation and some plugins didn''t seem to get installed. installWizard_installIncomplete_resumeInstallationButtonLabel=Resume installWizard_saveFirstUser=Save and Finish installWizard_skipFirstUser=Continue as admin installWizard_firstUserSkippedMessage=
    \ -You've skipped creating an admin user. To log in, use the username: 'admin' and \ +You''ve skipped creating an admin user. To log in, use the username: "admin" and \ the administrator password you used to access the setup wizard.\
    installWizard_addFirstUser_title=Getting Started @@ -63,7 +63,7 @@ installWizard_continue=Continue installWizard_retry=Retry installWizard_upgradePanel_title=Upgrade installWizard_upgradePanel_banner=Welcome to Jenkins {0}! -installWizard_upgradePanel_message=Jenkins {0} includes some great new features that we think you'll love, install these additional plugins to take advantage of them! +installWizard_upgradePanel_message=Jenkins {0} includes some great new features that we think you''ll love, install these additional plugins to take advantage of them! installWizard_upgradePanel_skipRecommendedPlugins=No thanks installWizard_upgradeComplete_title=Upgrade installWizard_pluginsInstalled_banner=Welcome to Jenkins {0}! diff --git a/core/src/main/resources/jenkins/model/CoreEnvironmentContributor/buildEnv.properties b/core/src/main/resources/jenkins/model/CoreEnvironmentContributor/buildEnv.properties index 72c8c0ae3b..b244378815 100644 --- a/core/src/main/resources/jenkins/model/CoreEnvironmentContributor/buildEnv.properties +++ b/core/src/main/resources/jenkins/model/CoreEnvironmentContributor/buildEnv.properties @@ -3,7 +3,7 @@ BUILD_ID.blurb=The current build ID, identical to BUILD_NUMBER for builds create BUILD_DISPLAY_NAME.blurb=The display name of the current build, which is something like "#153" by default. JOB_NAME.blurb=Name of the project of this build, such as "foo" or "foo/bar". JOB_BASE_NAME.blurb=Short Name of the project of this build stripping off folder paths, such as "foo" for "bar/foo". -BUILD_TAG.blurb=String of "jenkins-$'{'JOB_NAME}-$'{'BUILD_NUMBER}". All forward slashes ('/') in the JOB_NAME are replaced with dashes ('-'). Convenient to put into a resource file, a jar file, etc for easier identification. +BUILD_TAG.blurb=String of "jenkins-$'{'JOB_NAME}-$'{'BUILD_NUMBER}". All forward slashes ("/") in the JOB_NAME are replaced with dashes ("-"). Convenient to put into a resource file, a jar file, etc for easier identification. EXECUTOR_NUMBER.blurb=\ The unique number that identifies the current executor \ (among executors of the same machine) that\u2019s \ diff --git a/core/src/main/resources/jenkins/model/Jenkins/noPrincipal.properties b/core/src/main/resources/jenkins/model/Jenkins/noPrincipal.properties index 52f352a75f..78958278e5 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/noPrincipal.properties +++ b/core/src/main/resources/jenkins/model/Jenkins/noPrincipal.properties @@ -21,5 +21,5 @@ # THE SOFTWARE. blurb=\ - The web container doesn't seem to be configured to do authentication. \ + The web container doesn''t seem to be configured to do authentication. \ Check the container documentation and/or also consult jenkins-users@googlegroups.com diff --git a/core/src/main/resources/jenkins/model/Messages.properties b/core/src/main/resources/jenkins/model/Messages.properties index b76ef619d7..adf5c9da94 100644 --- a/core/src/main/resources/jenkins/model/Messages.properties +++ b/core/src/main/resources/jenkins/model/Messages.properties @@ -62,7 +62,7 @@ NewViewLink.NewView=New View PatternProjectNamingStrategy.DisplayName=Pattern PatternProjectNamingStrategy.NamePatternRequired=Name Pattern is required -PatternProjectNamingStrategy.NamePatternInvalidSyntax=regular expression's syntax is invalid. +PatternProjectNamingStrategy.NamePatternInvalidSyntax=regular expression''s syntax is invalid. ParameterizedJobMixIn.build_with_parameters=Build with Parameters ParameterizedJobMixIn.build_now=Build Now BlockedBecauseOfBuildInProgress.shortDescription=Build #{0} is already in progress{1} -- GitLab From 14ff05fdb6b706d19a5a20853024a091f3316d94 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 19 Dec 2017 14:42:23 -0500 Subject: [PATCH 0180/1380] Bump. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6d88780f12..4fb4d74363 100644 --- a/pom.xml +++ b/pom.xml @@ -175,7 +175,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.15-20171201.211705-5 + 3.15-20171219.194157-7 -- GitLab From 5f2a40ba96209bcd866275fe7e86105553f5817c Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 19 Dec 2017 11:48:27 -0800 Subject: [PATCH 0181/1380] [maven-release-plugin] prepare release jenkins-2.97 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 6042680574..315e5649c9 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.97-SNAPSHOT + 2.97 cli diff --git a/core/pom.xml b/core/pom.xml index 4517bab996..859ed172a7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.97-SNAPSHOT + 2.97 jenkins-core diff --git a/pom.xml b/pom.xml index 4ce5365476..fe4b8253bc 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.97-SNAPSHOT + 2.97 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.97 diff --git a/test/pom.xml b/test/pom.xml index da6245605c..268a3b2c86 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.97-SNAPSHOT + 2.97 test diff --git a/war/pom.xml b/war/pom.xml index 9147f0ecc3..ff68962ee3 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.97-SNAPSHOT + 2.97 jenkins-war -- GitLab From 90f778d2819cfd223f8815a3d84ce3e904d91fb0 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Tue, 19 Dec 2017 11:48:27 -0800 Subject: [PATCH 0182/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 315e5649c9..3366e9d2a8 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.97 + 2.98-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 859ed172a7..ee9de7d7aa 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.97 + 2.98-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index fe4b8253bc..45dfa6d665 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.97 + 2.98-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.97 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 268a3b2c86..c3a41f4cab 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.97 + 2.98-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index ff68962ee3..fff73572dc 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.97 + 2.98-SNAPSHOT jenkins-war -- GitLab From 837d0c90ac98a692118ee21a1b3f6cb487a08f57 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 19 Dec 2017 15:05:11 -0500 Subject: [PATCH 0183/1380] Allow whitelists to contain blank lines and comments. --- .../jenkins/security/ClassFilterImpl.java | 3 ++- .../jenkins/security/CustomClassFilter.java | 4 +++- .../jenkins/security/whitelisted-classes.txt | 11 +++++++++++ .../jenkins/security/ClassFilterImplTest.java | 4 +++- .../resources/plugins/custom-class-filter.jpi | Bin 3955 -> 4623 bytes 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/jenkins/security/ClassFilterImpl.java b/core/src/main/java/jenkins/security/ClassFilterImpl.java index ec7737383a..1cb6238d67 100644 --- a/core/src/main/java/jenkins/security/ClassFilterImpl.java +++ b/core/src/main/java/jenkins/security/ClassFilterImpl.java @@ -48,6 +48,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; import jenkins.model.Jenkins; import org.apache.commons.io.IOUtils; import org.kohsuke.accmod.Restricted; @@ -99,7 +100,7 @@ public class ClassFilterImpl extends ClassFilter { static final Set WHITELISTED_CLASSES; static { try (InputStream is = ClassFilterImpl.class.getResourceAsStream("whitelisted-classes.txt")) { - WHITELISTED_CLASSES = ImmutableSet.copyOf(IOUtils.readLines(is, StandardCharsets.UTF_8)); + WHITELISTED_CLASSES = ImmutableSet.copyOf(IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*")).collect(Collectors.toSet())); } catch (IOException x) { throw new ExceptionInInitializerError(x); } diff --git a/core/src/main/java/jenkins/security/CustomClassFilter.java b/core/src/main/java/jenkins/security/CustomClassFilter.java index a417b82017..960dee5835 100644 --- a/core/src/main/java/jenkins/security/CustomClassFilter.java +++ b/core/src/main/java/jenkins/security/CustomClassFilter.java @@ -159,7 +159,9 @@ public interface CustomClassFilter extends ExtensionPoint { while (resources.hasMoreElements()) { try (InputStream is = resources.nextElement().openStream()) { for (String entry : IOUtils.readLines(is, StandardCharsets.UTF_8)) { - if (entry.startsWith("!")) { + if (entry.matches("#.*|\\s*")) { + // skip + } else if (entry.startsWith("!")) { overrides.put(entry.substring(1), false); } else { overrides.put(entry, true); diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index fd2f6443ec..49734ac875 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -10,7 +10,10 @@ com.google.common.collect.RegularImmutableSet com.google.common.collect.RegularImmutableSortedSet com.google.common.collect.SingletonImmutableList com.google.common.collect.SingletonImmutableSet + +# TODO move to maven-plugin hudson.maven.MavenInformation + java.io.File java.lang.Boolean java.lang.Class @@ -67,10 +70,15 @@ java.util.concurrent.atomic.AtomicBoolean java.util.logging.Level java.util.logging.LogRecord java.util.regex.Pattern + +# TODO move to job-dsl, or mark job-dsl-core with Jenkins-ClassFilter-Whitelisted: true javaposse.jobdsl.dsl.GeneratedJob + org.acegisecurity.userdetails.User org.apache.commons.fileupload.disk.DiskFileItem org.apache.commons.fileupload.util.FileItemHeadersImpl + +# TODO move to git-client org.eclipse.jgit.lib.ObjectId org.eclipse.jgit.lib.ObjectIdOwnerMap$Entry org.eclipse.jgit.lib.PersonIdent @@ -78,8 +86,11 @@ org.eclipse.jgit.revwalk.RevCommit org.eclipse.jgit.revwalk.RevObject org.eclipse.jgit.revwalk.RevTree org.eclipse.jgit.transport.URIish + +# TODO move to workflow-support org.jboss.marshalling.TraceInformation$FieldInfo org.jboss.marshalling.TraceInformation$ObjectInfo + org.jvnet.hudson.MemoryUsage org.jvnet.localizer.Localizable org.jvnet.localizer.ResourceBundleHolder diff --git a/test/src/test/java/jenkins/security/ClassFilterImplTest.java b/test/src/test/java/jenkins/security/ClassFilterImplTest.java index 752c3634ed..c3ca2c3bac 100644 --- a/test/src/test/java/jenkins/security/ClassFilterImplTest.java +++ b/test/src/test/java/jenkins/security/ClassFilterImplTest.java @@ -36,9 +36,11 @@ import hudson.tasks.Builder; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Iterator; import java.util.List; import java.util.TreeSet; import java.util.logging.Level; +import java.util.stream.Collectors; import jenkins.model.GlobalConfiguration; import org.apache.commons.io.IOUtils; import org.junit.Test; @@ -68,7 +70,7 @@ public class ClassFilterImplTest { @Test public void whitelistSanity() throws Exception { try (InputStream is = ClassFilterImpl.class.getResourceAsStream("whitelisted-classes.txt")) { - List lines = IOUtils.readLines(is, StandardCharsets.UTF_8); + List lines = IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*")).collect(Collectors.toList()); assertThat("whitelist is ordered", new TreeSet<>(lines), contains(lines.toArray(new String[0]))); for (String line : lines) { try { diff --git a/test/src/test/resources/plugins/custom-class-filter.jpi b/test/src/test/resources/plugins/custom-class-filter.jpi index c7ae4a9330ab5fcb427f1d13f7d6b58ce18e430f..e7cf29fa288daab44fef480fe62ba5c3ae823de2 100644 GIT binary patch delta 2371 zcmaKu3p7-D9LF!iGTzS4@g^M%(O^xCA%x5lBQjBDJ;IP0d5el!X%&%a@0LeOv9v_; zejJZ%$$KgqjZ8&pQX-G?*i?JRNHdOR=G-}R&-s78zwht&zvutGb7~oyRF$nN0N##Z zjdF0t8SEv%0hqhStNl}(590mX@KQ4|iAp~eGA#cP5;H{kMR>-i3&@fU&R4^%Wtpo_n;{N#B-1&S!tsE3^m z??i9^V4?s3hypXIE{;@ZyOjIT-FX23KyWFLgZ)WIB1w_@u_0uB)oZA2&UXzZcD@Id z1ZhM7kcQ}uUrMUhV; zhk7)l9$}U&m$CGcsK>-wMTe3>3|2$RDAWt#*+#*l=5du7QBP01gSYTli)NQVm^t#zdo`x?a=D1EYwz+88_{W7qA! zJCVYQNze-}7iLv}z0^hR=ggDKzcr^abDP_)Co1moy&v@O_EBYN^V7BSLT^tWGB?xr zBED+UZ*T}NmMIK>=95aoqam6R6x8L0&J6pbtu`&!L`4FnF1vQSQ59$8O zQu~LVMT>hfb>aAId{Gkfa8a;+PQ&PgE9=65=Z$5PCYJGBS4#Y2*J@vIPqDe<*SyOV zyyEpc#C%41aD~0knXRpD(>G`G$ZjR!!;u36N1d8_AN2%Cn+`u6X{aE%PT&f@NYq`S z8D8qMoN0YM#B8o!9Dj4icoV*)O7Ez+_X8ADp-G#ZaUP!=ef(4=;aPuBkl#gyT76!E zHG_3^JUTnBYVReo5w_vAWv^#EdC~(z8Kf4Bc5L%95$&76H#JvlQAOJpNG-=BPvu zCzy01ef3Obksr|chywNWh~9etR2%p$B<|L|&@_pia%rX1ld@$lcjZl%bq++Hfjd_G zL99|rWi4dogvhOaI48!caQ^AP#-~ga9B@l706-ro1xU delta 1669 zcmeBI`7Ad7!e9Y1O2^mWhYOpb_o zGi_$@q^Ocx-p8xYvgX?F4YraJ-+8m?dgr{DldG&gr>FXaKRI=3wy}19m~QCY%*drL zb`?E!481;cs{Y?;=eAslc`3!bUB>w2N9E{wvm>U>THYVMQEJ(vKZ{Bd>R&ubO@Ai( zS}pyR=#weSm}gd;*`Rc*=gFl=Z*}WMWBFpE0HZiriuika|=f3S{_iy^GcYk(#;q~3} zum8?`tiJ7>Lu|mG8516#x$0&rX(lGT+~vgnPxt>E*fX^@@M7D%mu=>0t7T?ZF9=Jv zdY-vEy#C$E?=$&|*3VqFGvis?diR;(e%Wh3-TpA^`WbcgZ?Wzdn|!^NzSH?zUuKp% zhcCY~Y5%gLO0|lc{;(b1eXM?y@5*Dj=Y9R}8-6qj)}L(qAg8?f`LSuPYo&XC8Q-b0 zI-Fzn`^f)!>BZkKJzBh+|9Xwd%5&@P=vFvxFG&6{Z-cFyS$*NPw}Np&mEQB`c19*u zKdAe~nmD_8(e>14XTx$goqY0S-fx5KuN$|<-;HqYYm>iRa#O)%z4^y)=Z;x=b+2l< zo_~MOUzggy2k-p9RetNV(Z6H%R>%80q621iZk+LNDdUN3)+GL}$J-WY>{r;jLdAIN zjPFwv!#6i_9A>@!)L!m{ME$1B!Vg=k?%GL|Hddc>f9ZYN>i-Ihr^oZn?|)w5eJ~+$G7V%@kag-|E>8$^UN)?+_zJ>KW1ug(a)Q3b*|G(A7(Can7B9Es^nLEc%`*6nhSy#OI_UuQ$vR7%{zrnmma;D3S!usj| z1r!c^5Sy}KVm9*(vo~KEd2}*Ejt5#R%x+k_#CgVr^BqC&7G}HX?-r2SZzvQ#=^;0B z-4p|jv_nBQ53j9hTU5!oEN#wF(LCd|0cUEQw#>ZIt?v?$qm#0LP1JR(e3j#p8wj$|*lw@F=feNsdWiXu}x7=rBm@FtL zs*A7-YXJw>vZV0=6S%m`MCf1umzQuQKn5rZAOPgL3Sc1#4-`m*;M1%I)5`$Typ#hJ z=sNf`SfOZm3Dz(f7{f@KI2bVF7_JK#433jXRyL3n NHxNbvlMFLBLI5qbZ~OoN -- GitLab From dfda4e18df8b79690b9319b2aa170f2e8105dc27 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Wed, 20 Dec 2017 16:11:23 -0500 Subject: [PATCH 0184/1380] https://github.com/jenkinsci/lib-jenkins-maven-embedder/pull/15 --- .../src/main/resources/jenkins/security/whitelisted-classes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 49734ac875..3ceb29c07c 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -11,7 +11,7 @@ com.google.common.collect.RegularImmutableSortedSet com.google.common.collect.SingletonImmutableList com.google.common.collect.SingletonImmutableSet -# TODO move to maven-plugin +# TODO remove when https://github.com/jenkinsci/lib-jenkins-maven-embedder/pull/15 is widely adopted in maven-plugin hudson.maven.MavenInformation java.io.File -- GitLab From d23ae596adeb55612f5c9e6572fcfc9e53c6ff04 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 21 Dec 2017 12:26:10 -0500 Subject: [PATCH 0185/1380] https://github.com/jenkinsci/job-dsl-plugin/pull/1092 --- .../src/main/resources/jenkins/security/whitelisted-classes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 3ceb29c07c..72af1bcb33 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -71,7 +71,7 @@ java.util.logging.Level java.util.logging.LogRecord java.util.regex.Pattern -# TODO move to job-dsl, or mark job-dsl-core with Jenkins-ClassFilter-Whitelisted: true +# TODO remove when https://github.com/jenkinsci/job-dsl-plugin/pull/1092 is widely adopted javaposse.jobdsl.dsl.GeneratedJob org.acegisecurity.userdetails.User -- GitLab From c8a98cb6e1a423efe9b4a5be3023c48c27bfa644 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 21 Dec 2017 12:32:14 -0500 Subject: [PATCH 0186/1380] https://github.com/jenkinsci/git-client-plugin/pull/290 --- .../src/main/resources/jenkins/security/whitelisted-classes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 72af1bcb33..707210221f 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -78,7 +78,7 @@ org.acegisecurity.userdetails.User org.apache.commons.fileupload.disk.DiskFileItem org.apache.commons.fileupload.util.FileItemHeadersImpl -# TODO move to git-client +# TODO remove when https://github.com/jenkinsci/git-client-plugin/pull/290 is widely adopted org.eclipse.jgit.lib.ObjectId org.eclipse.jgit.lib.ObjectIdOwnerMap$Entry org.eclipse.jgit.lib.PersonIdent -- GitLab From 353e6adeddcee7b26a1a0e5f2e7fefc190461e89 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 21 Dec 2017 12:35:22 -0500 Subject: [PATCH 0187/1380] https://github.com/jenkinsci/workflow-support-plugin/pull/50 --- .../src/main/resources/jenkins/security/whitelisted-classes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 707210221f..3356a8ac22 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -87,7 +87,7 @@ org.eclipse.jgit.revwalk.RevObject org.eclipse.jgit.revwalk.RevTree org.eclipse.jgit.transport.URIish -# TODO move to workflow-support +# TODO remove when https://github.com/jenkinsci/workflow-support-plugin/pull/50 is widely adopted org.jboss.marshalling.TraceInformation$FieldInfo org.jboss.marshalling.TraceInformation$ObjectInfo -- GitLab From e575f7581991fc4728a6e23477822b869d8ad7fc Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 21 Dec 2017 12:59:18 -0500 Subject: [PATCH 0188/1380] Snapshot bump, to work around apparent maven-compiler-plugin (or Maven core) bug. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 35f74bcaac..e9459a85bd 100644 --- a/pom.xml +++ b/pom.xml @@ -175,7 +175,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.15-20171219.194157-7 + 3.15-20171221.175805-9 -- GitLab From 006c51256e58d9b691956962d9fd624fb0c1e08b Mon Sep 17 00:00:00 2001 From: Jan Zuchhold Date: Fri, 22 Dec 2017 14:36:34 +0100 Subject: [PATCH 0189/1380] [JENKINS-48593] - Add getSystemProperty(key) to fix hudson.consoleTailKB system property (#3200) * Add getSystemProperty(key) to fix hudson.consoleTailKB system property * Add Javadoc, @Restricted and delegate to SystemProperties These changes were requested by @daniel-beck. * PR was not merged in time for 2.96 * Changes as requested by @oleg-nenashev --- core/src/main/java/hudson/Functions.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index bf8aca4e47..8462b88994 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -469,6 +469,16 @@ public class Functions { return new TreeMap(System.getProperties()); } + /** + * Gets the system property indicated by the specified key. + * + * Delegates to {@link SystemProperties#getString(java.lang.String)}. + */ + @Restricted(DoNotUse.class) + public static String getSystemProperty(String key) { + return SystemProperties.getString(key); + } + public static Map getEnvVars() { return new TreeMap(EnvVars.masterEnvVars); } -- GitLab From 19b26ac7b2f928a5c8f4a42d7a84d416348f4ae1 Mon Sep 17 00:00:00 2001 From: Gentle Yang Date: Fri, 22 Dec 2017 05:38:54 -0800 Subject: [PATCH 0190/1380] Create messages_zh_CN.properties to translate into Chinese (simplified) (#3174) * Create messages_zh_CN.properties * Update messages_zh_CN.properties * Rename messages_zh_CN.properties to message_zh_CN.properties * Update message_zh_CN.properties --- .../WarnWhenEnabled/message_zh_CN.properties | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 core/src/main/resources/jenkins/CLI/WarnWhenEnabled/message_zh_CN.properties diff --git a/core/src/main/resources/jenkins/CLI/WarnWhenEnabled/message_zh_CN.properties b/core/src/main/resources/jenkins/CLI/WarnWhenEnabled/message_zh_CN.properties new file mode 100644 index 0000000000..c4963491f5 --- /dev/null +++ b/core/src/main/resources/jenkins/CLI/WarnWhenEnabled/message_zh_CN.properties @@ -0,0 +1,26 @@ +# The MIT License +# +# Copyright 2017 Gentle Yang, vivo.com +# +# 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. + +blurb=\ + \u5f00\u542fjenkins cli\u7684 -remoting \u5de5\u4f5c\u6a21\u5f0f\u662f\u6bd4\u8f83\u5371\u9669\u7684\uff0c\u901a\u5e38\u6ca1\u6709\u5fc5\u8981\u3002\ + \u5efa\u8bae\u7981\u6b62\u8be5\u6a21\u5f0f\u3002 \ + \u8bf7\u53c2\u8003 cli\u6587\u6863 \u4e86\u89e3\u8be6\u60c5\u3002 -- GitLab From 02035017b6ea222b8cd8b13b02bf576db3bdd2b6 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Fri, 22 Dec 2017 17:27:13 +0100 Subject: [PATCH 0191/1380] Ensure all Jelly view have escape-by-default. (#3180) * Ensure all Jelly view have escape-by-default. - all those views escape the content theirself, so no vulnerability corrected here (for TextParameterDefinition, it's done by textarea.jelly) * - just a line break at end * - just a line break at end (part 2) * - change from st:out to j:out because their content is expected to be html tags * - st => j for all finally --- .../resources/hudson/model/Computer/_scriptText.jelly | 4 ++-- .../resources/hudson/model/DownloadService/footer.jelly | 6 +++--- .../hudson/model/TextParameterDefinition/config.jelly | 2 +- .../resources/jenkins/model/Jenkins/_scriptText.jelly | 4 ++-- core/src/main/resources/lib/hudson/artifactList.jelly | 1 - core/src/main/resources/lib/layout/breakable.jelly | 8 ++++---- .../main/resources/lib/layout/progressiveRendering.jelly | 2 +- .../lib/layout/RenderOnDemandTest/externalScript.jelly | 3 ++- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/core/src/main/resources/hudson/model/Computer/_scriptText.jelly b/core/src/main/resources/hudson/model/Computer/_scriptText.jelly index 60bc10e47f..0867147550 100644 --- a/core/src/main/resources/hudson/model/Computer/_scriptText.jelly +++ b/core/src/main/resources/hudson/model/Computer/_scriptText.jelly @@ -25,6 +25,6 @@ THE SOFTWARE. - + -${output} \ No newline at end of file + diff --git a/core/src/main/resources/hudson/model/DownloadService/footer.jelly b/core/src/main/resources/hudson/model/DownloadService/footer.jelly index e48f72d836..952945b014 100644 --- a/core/src/main/resources/hudson/model/DownloadService/footer.jelly +++ b/core/src/main/resources/hudson/model/DownloadService/footer.jelly @@ -29,7 +29,7 @@ THE SOFTWARE. This file is pulled into the layout.jelly --> - - - ${it.generateFragment()} + + + diff --git a/core/src/main/resources/hudson/model/TextParameterDefinition/config.jelly b/core/src/main/resources/hudson/model/TextParameterDefinition/config.jelly index 96489ca1b9..592e242a03 100644 --- a/core/src/main/resources/hudson/model/TextParameterDefinition/config.jelly +++ b/core/src/main/resources/hudson/model/TextParameterDefinition/config.jelly @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - + diff --git a/core/src/main/resources/jenkins/model/Jenkins/_scriptText.jelly b/core/src/main/resources/jenkins/model/Jenkins/_scriptText.jelly index 60bc10e47f..0867147550 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/_scriptText.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/_scriptText.jelly @@ -25,6 +25,6 @@ THE SOFTWARE. - + -${output} \ No newline at end of file + diff --git a/core/src/main/resources/lib/hudson/artifactList.jelly b/core/src/main/resources/lib/hudson/artifactList.jelly index c061062bde..d0c593c0c9 100644 --- a/core/src/main/resources/lib/hudson/artifactList.jelly +++ b/core/src/main/resources/lib/hudson/artifactList.jelly @@ -22,7 +22,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - diff --git a/core/src/main/resources/lib/layout/breakable.jelly b/core/src/main/resources/lib/layout/breakable.jelly index 482abe63ae..397d82f871 100644 --- a/core/src/main/resources/lib/layout/breakable.jelly +++ b/core/src/main/resources/lib/layout/breakable.jelly @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> - + Send escaped value to output decorated to be safely broken into lines when necessary @@ -30,6 +30,6 @@ THE SOFTWARE. Unescaped value to output - ${ - h.breakableString(h.escape(value)) -} + + + diff --git a/core/src/main/resources/lib/layout/progressiveRendering.jelly b/core/src/main/resources/lib/layout/progressiveRendering.jelly index 13fe761ce6..ba106061c2 100644 --- a/core/src/main/resources/lib/layout/progressiveRendering.jelly +++ b/core/src/main/resources/lib/layout/progressiveRendering.jelly @@ -21,7 +21,7 @@ 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. --> - + diff --git a/test/src/test/resources/lib/layout/RenderOnDemandTest/externalScript.jelly b/test/src/test/resources/lib/layout/RenderOnDemandTest/externalScript.jelly index 90fbbc8e86..4d0df4b8b0 100644 --- a/test/src/test/resources/lib/layout/RenderOnDemandTest/externalScript.jelly +++ b/test/src/test/resources/lib/layout/RenderOnDemandTest/externalScript.jelly @@ -1,3 +1,4 @@ + y = "yyy"; - \ No newline at end of file + -- GitLab From 001a33c747feb437a1bf5f0349b6988f517ec9a8 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 22 Dec 2017 11:29:49 -0500 Subject: [PATCH 0192/1380] [JENKINS-48638] Deprecate Jenkins.getInstance in favor of (usually) get (#3195) * Deprecate Jenkins.getInstance in favor of (usually) getActiveInstance. * Introduced get() as a concise non-null accessor. * May as well suggest rewrites of getActiveInstance too. --- core/src/main/java/hudson/model/Hudson.java | 7 +-- core/src/main/java/jenkins/model/Jenkins.java | 62 +++++++++---------- .../resources/META-INF/upgrade/Hudson.hint | 2 +- .../resources/META-INF/upgrade/Jenkins.hint | 2 + 4 files changed, 37 insertions(+), 36 deletions(-) create mode 100644 core/src/main/resources/META-INF/upgrade/Jenkins.hint diff --git a/core/src/main/java/hudson/model/Hudson.java b/core/src/main/java/hudson/model/Hudson.java index db063b952c..cf72f3b336 100644 --- a/core/src/main/java/hudson/model/Hudson.java +++ b/core/src/main/java/hudson/model/Hudson.java @@ -34,7 +34,6 @@ import hudson.model.listeners.ItemListener; import hudson.slaves.ComputerListener; import hudson.util.CopyOnWriteList; import hudson.util.FormValidation; -import javax.annotation.Nonnull; import jenkins.model.Jenkins; import org.jvnet.hudson.reactor.ReactorException; import org.kohsuke.stapler.QueryParameter; @@ -52,7 +51,7 @@ import java.text.ParseException; import java.util.List; import static hudson.Util.fixEmpty; -import javax.annotation.CheckForNull; +import javax.annotation.Nullable; public class Hudson extends Jenkins { @@ -70,10 +69,10 @@ public class Hudson extends Jenkins { @Deprecated private transient final CopyOnWriteList computerListeners = ExtensionListView.createCopyOnWriteList(ComputerListener.class); - /** @deprecated Here only for compatibility. Use {@link Jenkins#getInstance} instead. */ + /** @deprecated Here only for compatibility. Use {@link Jenkins#get} instead. */ @Deprecated @CLIResolver - @Nonnull + @Nullable public static Hudson getInstance() { return (Hudson)Jenkins.getInstance(); } diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index b69d04b516..4a9410e7bc 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -179,7 +179,6 @@ import jenkins.ExtensionComponentSet; import jenkins.ExtensionRefreshException; import jenkins.InitReactorRunner; import jenkins.install.InstallState; -import jenkins.install.InstallUtil; import jenkins.install.SetupWizard; import jenkins.model.ProjectNamingStrategy.DefaultProjectNamingStrategy; import jenkins.security.ConfidentialKey; @@ -739,56 +738,57 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve /** * Gets the {@link Jenkins} singleton. - * {@link #getInstanceOrNull()} provides the unchecked versions of the method. * @return {@link Jenkins} instance - * @throws IllegalStateException {@link Jenkins} has not been started, or was already shut down - * @since 1.590 - * @deprecated use {@link #getInstance()} + * @throws IllegalStateException for the reasons that {@link #getInstanceOrNull} might return null + * @since FIXME */ - @Deprecated @Nonnull - public static Jenkins getActiveInstance() throws IllegalStateException { - Jenkins instance = HOLDER.getInstance(); + public static Jenkins get() throws IllegalStateException { + Jenkins instance = getInstanceOrNull(); if (instance == null) { - throw new IllegalStateException("Jenkins has not been started, or was already shut down"); + throw new IllegalStateException("Jenkins.instance is missing. Read the documentation of Jenkins.getInstanceOrNull to see what you are doing wrong."); } return instance; } + /** + * @deprecated This is a verbose historical alias for {@link #get}. + * @since 1.590 + */ + @Deprecated + @Nonnull + public static Jenkins getActiveInstance() throws IllegalStateException { + return get(); + } + /** * Gets the {@link Jenkins} singleton. - * {@link #getActiveInstance()} provides the checked versions of the method. - * @return The instance. Null if the {@link Jenkins} instance has not been started, - * or was already shut down + * {@link #get} is what you normally want. + *

    In certain rare cases you may have code that is intended to run before Jenkins starts or while Jenkins is being shut down. + * For those rare cases use this method. + *

    In other cases you may have code that might end up running on a remote JVM and not on the Jenkins master. + * For those cases you really should rewrite your code so that when the {@link Callable} is sent over the remoting channel + * it can do whatever it needs without ever referring to {@link Jenkins}; + * for example, gather any information you need on the master side before constructing the callable. + * If you must do a runtime check whether you are in the master or agent, use {@link JenkinsJVM} rather than this method, + * as merely loading the {@link Jenkins} class file into an agent JVM can cause linkage errors under some conditions. + * @return The instance. Null if the {@link Jenkins} service has not been started, or was already shut down, + * or we are running on an unrelated JVM, typically an agent. * @since 1.653 */ + @CLIResolver @CheckForNull public static Jenkins getInstanceOrNull() { return HOLDER.getInstance(); } /** - * Gets the {@link Jenkins} singleton. In certain rare cases you may have code that is intended to run before - * Jenkins starts or while Jenkins is being shut-down. For those rare cases use {@link #getInstanceOrNull()}. - * In other cases you may have code that might end up running on a remote JVM and not on the Jenkins master, - * for those cases you really should rewrite your code so that when the {@link Callable} is sent over the remoting - * channel it uses a {@code writeReplace} method or similar to ensure that the {@link Jenkins} class is not being - * loaded into the remote class loader - * @return The instance. - * @throws IllegalStateException {@link Jenkins} has not been started, or was already shut down + * @deprecated This is a historical alias for {@link #getInstanceOrNull} but with ambiguous nullability. Use {@link #get} in typical cases. */ - @CLIResolver - @Nonnull + @Nullable + @Deprecated public static Jenkins getInstance() { - Jenkins instance = HOLDER.getInstance(); - if (instance == null) { - if(SystemProperties.getBoolean(Jenkins.class.getName()+".enableExceptionOnNullInstance")) { - // TODO: remove that second block around 2.20 (that is: ~20 versions to battle test it) - // See https://github.com/jenkinsci/jenkins/pull/2297#issuecomment-216710150 - throw new IllegalStateException("Jenkins has not been started, or was already shut down"); - } - } - return instance; + return getInstanceOrNull(); } /** diff --git a/core/src/main/resources/META-INF/upgrade/Hudson.hint b/core/src/main/resources/META-INF/upgrade/Hudson.hint index 4ff2df23fd..5eb67cffce 100644 --- a/core/src/main/resources/META-INF/upgrade/Hudson.hint +++ b/core/src/main/resources/META-INF/upgrade/Hudson.hint @@ -1 +1 @@ -hudson.model.Hudson.getInstance() => jenkins.model.Jenkins.getInstance();; +hudson.model.Hudson.getInstance() => jenkins.model.Jenkins.get();; diff --git a/core/src/main/resources/META-INF/upgrade/Jenkins.hint b/core/src/main/resources/META-INF/upgrade/Jenkins.hint new file mode 100644 index 0000000000..4e5821c547 --- /dev/null +++ b/core/src/main/resources/META-INF/upgrade/Jenkins.hint @@ -0,0 +1,2 @@ +jenkins.model.Jenkins.getInstance() => jenkins.model.Jenkins.get();; +jenkins.model.Jenkins.getActiveInstance() => jenkins.model.Jenkins.get();; -- GitLab From 8e78ab1c660de81f48beecedced25d9b2cbbf64a Mon Sep 17 00:00:00 2001 From: Larry Singleton Date: Fri, 22 Dec 2017 10:38:57 -0600 Subject: [PATCH 0193/1380] [JENKINS-48227] Use "Files.createTempDirectory" to create temp directory (#3161) * Use "Files.createTempDirectory" to create temp directory instead See SonarQube critical vulnerability squid:S2976 (tag: owasp-a9) https://next.sonarqube.com/sonarqube/coding_rules#rule_key=squid%3AS2976 * [JENKINS-48227] Creating a utility "static Path toPath(File file) throws IOException" method, which wraps InvalidPathException to IOException so that it will be checked. - also fixed public static final reference - fixed broken test cases - added new test cases for toPath() and createTempDir() * Revert back to public static int * adjustments due to merges * Add posix check to determine if Posix FileAttributes should be included in call to Files.createTempDirectory() * Remove reference to private element * Updated to use explicit imports --- core/src/main/java/hudson/FilePath.java | 36 +++++++++++++++++--- core/src/test/java/hudson/FilePathTest.java | 37 ++++++++++++++++----- 2 files changed, 60 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index 8d1233ab04..b7b5b2d79d 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -77,13 +77,20 @@ import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLConnection; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.nio.file.LinkOption; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; +import java.util.EnumSet; import java.util.Enumeration; import java.util.List; import java.util.Map; @@ -113,6 +120,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.fileupload.FileItem; import org.apache.commons.io.input.CountingInputStream; +import org.apache.commons.lang.StringUtils; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.types.FileSet; @@ -1403,17 +1411,35 @@ public final class FilePath implements Serializable { * @return * The new FilePath pointing to the temporary directory * @since 1.311 - * @see File#createTempFile(String, String) + * @see Files#createTempDirectory(Path, String, FileAttribute[]) */ public FilePath createTempDir(final String prefix, final String suffix) throws IOException, InterruptedException { try { + String[] s; + if (StringUtils.isBlank(suffix)) { + s = new String[]{prefix, "tmp"}; // see File.createTempFile - tmp is used if suffix is null + } else { + s = new String[]{prefix, suffix}; + } + String name = StringUtils.join(s, "."); return new FilePath(this,act(new SecureFileCallable() { private static final long serialVersionUID = 1L; public String invoke(File dir, VirtualChannel channel) throws IOException { - File f = File.createTempFile(prefix, suffix, dir); - f.delete(); - f.mkdir(); - return f.getName(); + + Path tempPath; + final boolean isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix"); + + if (isPosix) { + tempPath = Files.createTempDirectory(Util.fileToPath(dir), name, + PosixFilePermissions.asFileAttribute(EnumSet.allOf(PosixFilePermission.class))); + } else { + tempPath = Files.createTempDirectory(Util.fileToPath(dir), name, new FileAttribute[] {}); + } + + if (tempPath.toFile() == null) { + throw new IOException("Failed to obtain file from path " + dir + " on " + remote); + } + return tempPath.toFile().getName(); } })); } catch (IOException e) { diff --git a/core/src/test/java/hudson/FilePathTest.java b/core/src/test/java/hudson/FilePathTest.java index 846900424e..57815f6618 100644 --- a/core/src/test/java/hudson/FilePathTest.java +++ b/core/src/test/java/hudson/FilePathTest.java @@ -32,18 +32,15 @@ import hudson.util.StreamTaskListener; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.ConnectException; import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; -import java.net.URLStreamHandlerFactory; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -62,10 +59,16 @@ import org.apache.commons.io.output.NullOutputStream; import org.apache.tools.ant.Project; import org.apache.tools.ant.taskdefs.Chmod; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.junit.Assume.assumeThat; import static org.junit.Assume.assumeTrue; import static org.junit.Assume.assumeFalse; + import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @@ -622,7 +625,7 @@ public class FilePathTest { when(con.getResponseCode()) .thenReturn(HttpURLConnection.HTTP_NOT_MODIFIED); - assertFalse(d.installIfNecessaryFrom(url, null, null)); + assertFalse(d.installIfNecessaryFrom(url, null, "")); verify(con).setIfModifiedSince(123000); } @@ -641,7 +644,7 @@ public class FilePathTest { when(con.getInputStream()) .thenReturn(someZippedContent()); - assertTrue(d.installIfNecessaryFrom(url, null, null)); + assertTrue(d.installIfNecessaryFrom(url, null, "")); } @Issue("JENKINS-26196") @@ -759,7 +762,7 @@ public class FilePathTest { // and now fail when flush is bad! tmpDirPath.child("../" + archive.getName()).untar(outDir, TarCompression.NONE); } - + @Test public void chmod() throws Exception { assumeFalse(Functions.isWindows()); @@ -790,7 +793,25 @@ public class FilePathTest { path.chmod(mode); return path.mode(); } - + + @Issue("JENKINS-48227") + @Test + public void testCreateTempDir() throws IOException, InterruptedException { + final File srcFolder = temp.newFolder("src"); + final FilePath filePath = new FilePath(srcFolder); + FilePath x = filePath.createTempDir("jdk", "dmg"); + FilePath y = filePath.createTempDir("jdk", "pkg"); + FilePath z = filePath.createTempDir("jdk", null); + + assertNotNull("FilePath x should not be null", x); + assertNotNull("FilePath y should not be null", y); + assertNotNull("FilePath z should not be null", z); + + assertTrue(x.getName().contains("jdk.dmg")); + assertTrue(y.getName().contains("jdk.pkg")); + assertTrue(z.getName().contains("jdk.tmp")); + } + @Test public void deleteRecursiveOnUnix() throws Exception { assumeFalse("Uses Unix-specific features", Functions.isWindows()); Path targetDir = temp.newFolder("target").toPath(); -- GitLab From cb3990a4d6094260bea4571e7079fd0e3949047f Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 22 Dec 2017 18:02:44 +0100 Subject: [PATCH 0194/1380] Update to Remoting 3.15 and Cleanup issues in Channel#current() usages (#3145) Pulls in fixes for: JENKINS-48133, JENKINS-48055, JENKINS-37566, JENKINS-48309, JENKINS-47965, JENKINS-48130, JENKINS-37670, JENKINS-37566, JENKINS-46724 This change also adds some missing null/closing channel checks in the core. In some cases the change prevents spawning threads if the channel is in the invalid state. --- core/src/main/java/hudson/Launcher.java | 9 +++++++-- core/src/main/java/hudson/model/Computer.java | 7 ++++--- core/src/main/java/hudson/slaves/ChannelPinger.java | 3 ++- core/src/main/java/hudson/slaves/SlaveComputer.java | 2 +- core/src/main/java/jenkins/FilePathFilter.java | 2 +- .../main/java/jenkins/slaves/StandardOutputSwapper.java | 4 +--- pom.xml | 2 +- .../hudson/bugs/JnlpAccessWithSecuredHudsonTest.java | 2 +- .../test/java/jenkins/security/Security218CliTest.java | 2 +- 9 files changed, 19 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java index 1abbc6f949..0e6d8c607f 100644 --- a/core/src/main/java/hudson/Launcher.java +++ b/core/src/main/java/hudson/Launcher.java @@ -1289,6 +1289,7 @@ public abstract class Launcher { } public RemoteProcess call() throws IOException { + final Channel channel = getOpenChannelOrFail(); Launcher.ProcStarter ps = new LocalLauncher(listener).launch(); ps.cmds(cmd).masks(masks).envs(env).stdin(in).stdout(out).stderr(err).quiet(quiet); if(workDir!=null) ps.pwd(workDir); @@ -1298,16 +1299,20 @@ public abstract class Launcher { final Proc p = ps.start(); - return Channel.current().export(RemoteProcess.class,new RemoteProcess() { + return channel.export(RemoteProcess.class,new RemoteProcess() { public int join() throws InterruptedException, IOException { try { return p.join(); } finally { // make sure I/O is delivered to the remote before we return + Channel taskChannel = null; try { - Channel.current().syncIO(); + // Sync IO will fail automatically if the channel is being closed, no need to use getOpenChannelOrFail() + taskChannel = Channel.currentOrFail(); + taskChannel.syncIO(); } catch (Throwable t) { // this includes a failure to sync, agent.jar too old, etc + LOGGER.log(Level.INFO, "Failed to synchronize IO streams on the channel " + taskChannel, t); } } } diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 3a968f54a9..8e35bf5b1b 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -1426,10 +1426,11 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces private static final class DumpExportTableTask extends MasterToSlaveCallable { public String call() throws IOException { + final Channel ch = getChannelOrFail(); StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - Channel.current().dumpExportTable(pw); - pw.close(); + try (PrintWriter pw = new PrintWriter(sw)) { + ch.dumpExportTable(pw); + } return sw.toString(); } } diff --git a/core/src/main/java/hudson/slaves/ChannelPinger.java b/core/src/main/java/hudson/slaves/ChannelPinger.java index 14ac43dc35..85d6d78675 100644 --- a/core/src/main/java/hudson/slaves/ChannelPinger.java +++ b/core/src/main/java/hudson/slaves/ChannelPinger.java @@ -133,7 +133,8 @@ public class ChannelPinger extends ComputerListener { @Override public Void call() throws IOException { - setUpPingForChannel(Channel.current(), null, pingTimeoutSeconds, pingIntervalSeconds, false); + // No sense in setting up channel pinger if the channel is being closed + setUpPingForChannel(getOpenChannelOrFail(), null, pingTimeoutSeconds, pingIntervalSeconds, false); return null; } diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java index f1e66ac33d..650667520f 100644 --- a/core/src/main/java/hudson/slaves/SlaveComputer.java +++ b/core/src/main/java/hudson/slaves/SlaveComputer.java @@ -867,7 +867,7 @@ public class SlaveComputer extends Computer { // ignore this error. } - Channel.current().setProperty("slave",Boolean.TRUE); // indicate that this side of the channel is the slave side. + Channel.currentOrFail().setProperty("slave",Boolean.TRUE); // indicate that this side of the channel is the slave side. return null; } diff --git a/core/src/main/java/jenkins/FilePathFilter.java b/core/src/main/java/jenkins/FilePathFilter.java index 501a005a5d..4d4eb4f2be 100644 --- a/core/src/main/java/jenkins/FilePathFilter.java +++ b/core/src/main/java/jenkins/FilePathFilter.java @@ -102,7 +102,7 @@ public abstract class FilePathFilter { /** * Returns an {@link FilePathFilter} object that represents all the in-scope filters, - * or null if none is needed. + * or {@code null} if none is needed. */ public static @CheckForNull FilePathFilter current() { Channel ch = Channel.current(); diff --git a/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java b/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java index 89fedfd04f..9a8d10a12f 100644 --- a/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java +++ b/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java @@ -39,9 +39,7 @@ public class StandardOutputSwapper extends ComputerListener { private static final class ChannelSwapper extends MasterToSlaveCallable { public Boolean call() throws Exception { if (File.pathSeparatorChar==';') return false; // Windows - - Channel c = Channel.current(); - + Channel c = getOpenChannelOrFail(); StandardOutputStream sos = (StandardOutputStream) c.getProperty(StandardOutputStream.class); if (sos!=null) { swap(sos); diff --git a/pom.xml b/pom.xml index 45dfa6d665..37d28ea832 100644 --- a/pom.xml +++ b/pom.xml @@ -175,7 +175,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.14 + 3.15 diff --git a/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java b/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java index 906adcf6e7..dcd8989846 100644 --- a/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java +++ b/test/src/test/java/hudson/bugs/JnlpAccessWithSecuredHudsonTest.java @@ -154,7 +154,7 @@ public class JnlpAccessWithSecuredHudsonTest { } @Override public String call() throws Exception { - return Channel.current().call(new ScriptLoader(path)); + return getChannelOrFail().call(new ScriptLoader(path)); } } diff --git a/test/src/test/java/jenkins/security/Security218CliTest.java b/test/src/test/java/jenkins/security/Security218CliTest.java index 40c5acc8ff..2acf0b18e5 100644 --- a/test/src/test/java/jenkins/security/Security218CliTest.java +++ b/test/src/test/java/jenkins/security/Security218CliTest.java @@ -239,7 +239,7 @@ public class Security218CliTest { // Invoke backward call try { - Channel.current().call(new Callable() { + getChannelOrFail().call(new Callable() { private static final long serialVersionUID = 1L; @Override -- GitLab From 23677fee49730679b641dea95f9034c87513c4d3 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Sun, 24 Dec 2017 14:39:05 +0100 Subject: [PATCH 0195/1380] Critical favicon change :-) --- war/src/main/webapp/favicon.ico | Bin 17542 -> 9662 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/war/src/main/webapp/favicon.ico b/war/src/main/webapp/favicon.ico index e6d7748d5576066255e271a3c16ebff7849637d6..20ef7451138299b72d3f89b9b4dd5dffb7ab9efe 100644 GIT binary patch literal 9662 zcmc&)30zKT+Yf_CB}=76Pid7XTD0%`_OvfrF|uXCFqQ^m$dc`~=C!?KdkrH>WGfUZ zq%PP56SXz$C>W6soq75HEeuUuKxN##69Xtr( z0TOWXxYy;~_HH?llb-|~yRn$G!5@CVdc%3DHJqnd!*!Z1Mvdbke$67(<=#Z%90!CN zs3CM`2!}uHpP|suyef!bNf^X3-cf)ZBEZm!oA#l6qi<^ zu%sM?#Sc(Ibi94{P5>K?jSV<@^eALyWMN`#f`0wQ;Ov)$7+CxY^+S`}b&W zZbocuEUc_;@TjR3hDN3sC?XFNi+I=)KLq@-bH9(i{rbXnz7u?Z_4>;H?`@%^r2zgf z?y&JQfK8|=d}Dn(bSG?2D8|~40qGrv51#;g$BS_HDj{0(;6(3ax}>5p8+yhE;OQBN z{Gt-PMJxv}ydSnRjwwYi|cl%?Z#o+6{Ymnv> zV&=@5@bmXWM8tH=m@yMN<8{%0z(Cx*c@v+=9-fn393c7|np>cvYKpNsF+@W)_&yIH zC20js6IBGR_2#gT8mox@{rc5=d3eh6ba{{)EsIfFJlF)8!TkqEcrWL3a$xvFc1(g> zggq2T%X6}jmv@AN^F^}ha76#S47W0~@aFA1eE9Hz!~NmIdmK3&kLlB=BY1KIyu)W< z!J1vzdFloZUoOD>m>me66V0tHiQNYS3kT@y>vOj7`HP?q9X>+#+y_QRYoVpJg!`_p z(|AmZ^@l&{&^Yta7%DlmLQO?|g4FeNohna+knJ750RALg7bw>kW%~{ z(f^2tv>Xq%&Rz)oK9cm$7vh75qIa)e9KPPYd&A$~pL3dY%2MEIDZ+}dh)amQowSZ zB!~u6xJ|dmU`a9TI}lHL+roV>@$4Cri7VzU-+*hCZ*aN%bvy2&XIQcQ7~K5B5w(0H zu9m+-TGe}8t$4@v=?_0*+li~N_nZU~kwF|CMn*+8Gl@T0PaVc^5tn@;_IzQOYw!?ry z1K>5=8Q+m?OUtX#*xZV&%uEDLorN2vO}J9_f^a|Ou%}kOgLl|$Or0BzGk5Dr2G6*4 zCXbF*^aK}(#tYQPU&@2JmM%v_)DJ&!eBpcZ;l9{~aQQ>oa1`$BU5V^Nu}Gflim8@k zVI61&U6%>Odw&i)qk%Q&d5#bk5r&dBk9018TMOAd3B0i*p*c~F`$hrz^tP98;p*m& zbD5<`uY8H~1?|`_mOMkmq8PaOh2wG=t#`tFq39_tmb~C-I-OaK(|76#|5HxKl5!d# zC98mm1_mfAD}#p4X!!in&cD$PqjBocUKC$Cg_AQJVXmzNyHHCQdK$o+Y{G4k6M{%i zfn+1h=Zw-*g1);x$9sWn+!wfT*eAN{!EmAxB7ca2rM)xqnm!_hcqqWRe8*9Q&s^{a z?Ck!@tXl9VO@pq1Ir$g^=;#{5m~41c{5f2H@Cxyl^U2rsfrhpk?7}VKwa5(~3tizx z@m1SM1BMzzA7MB1GKLGq0I!vvP&HP8tfVYtN6JCpMGyV@_QN>KaR^xL!|`4q7vDAB zkW-N%9rJXz7oIN3=TvNT-4~O@h}757^BnzeM_G;H8Vf5)vqV z23&jayaRt?ZWFejxWeg#KpvM$p5jXB3rvYxfei-|aJvzRzgmdZyMM#Nm|gfOCJu*F zi?HX+Eu>by#mUSXicLcyB_|1<=17dU84KG$$^)j^k#DpmKWGFE0~JWgNH!IfR)94Y zgU(;P%8gH*PDFN2KH}o`z$(%jfm{3`sVW6!A8ZO5)@<=F-~g&rY%|n>0#2abl~Gs$#aDM z@H2$__02LhHFcJekx3L08Tgj@Sk@#YB-#`e71!I^+74v-m4F6jlWd$@P(pdv10*CS zL)AzX6HSb8=Il9?k=PB@5I?Y|=l=`qH zn6tjHxII7YOoxD;#N-PwHZ|dBs%vPWu^D)y)Nt%tG0x;PV%@&e2#T0Te!Z)Y>5gA8 zPcCT1IT9Gt@k3V&pfkY;Hui2%Q`d%>tsC|w-a<0@uzws)!R~|`uyXUqpaK2$A|fI> zL+&ybop%Jp1sDZxD%LYY6pLc87FR(a=vT9nmCM*lA6^8aJLB;DCp#w((u^-p!@TeO_H z_Xz0~FG%;=;~L8`&XatWkUlx`1CUI)$F|>6p`f66yc=Y0`XK)j*9-7>?h_Lep{lBi zfWRP54lJ)!RZ+%{<7bgk`;PWWK}f59j|G&AT_ygfP`vsU&OcjccAZxC)%Guu|6IBy z9(uZlNT9fOAmu*UnMOf({M8R`_h)P6_md1e;}(qBo^HdYe-O_dIo-+0E#!R9(epQO zz5E6AO>7Vu9ZNaQ1_aGqjNn;6A@P1A(a@ed{2AuX_n2=I%vlWHeB=@oMk-V842kHNGocD1^H^xK_bx982u%8>ls^8UNh#e+Ch33 z3>WVZtfM$^iOcocpJ#r+#Fmdai(_cL4SUaJBk6t<(b@_HWeo;NoDoow8?Rq`Yt`0< zjJeg13wC^dTyzAYqM`^43_ug@;hLJ7U~O%IL84;ZKJYs6o;Bu=im7GX&V&j1(7yeI zAu(hqw(ZynBV$ucG_xjKZtIb+jE`r@R>LEg;K!9)@%_ACAU9$p*-iWN4146V%`mfc zz;&{zDf3rDUU~!+d8$~s?J#&Mnt1Z0buhzUo4add&5ebLt%bXuzI)YxN7v`T!O0N^ z4;_Moha>t54}pQnQHpVcafWR8?)?JX%)E=L+IqCLwV|w{1~qk!C@3zY{J9Ecl2tT%o-HqwUPyN1X3Zq8yv$(r0rX;ppWY zc!tb^U+7H8Q@o)|g5j^ex7)t{T4Yi4y;biY7ac&uML&4Oc*1R&8@$#~9yiXF_D2Pr zPKQsPii+xbZjZ&{0`sl!-hZIIc`I626PXel`ceQ!N@ulB^G?Ez3$_k*od9)=D#z`%hDq%-BTM|q3~71gM% zd(>ecj3I)nh0YqPYwB_Qx8Ld9!<%%af#T~kP8Ndvll5#*aJ~E)ex^lkOf0R_zAt@=Th0o6^d2=GEv%M-92?yFa?JND=T?1G-1r?EZ!@25^-tgaDG-m0WH zv=^xrZ#!@@yv!bxDDMzp7xbO!VQVb5F`CyOI**u`HHeIi{5)X50B453BJZqBU0ISr zUFNS>9;HX+vD`k0a$`+%4JfP3p!2^1C@F`)$}tD3YCdEK=Q!*Ftbf&)l$2o1xCxLM zAy2tR<~MCZfW4=lVQ2d`2fjaQYHHBh+KRDb$99Rs_4S34wM8dfS#HnH<-C4&BmDD7 zfATS2FAjPJ+eim*L3_L%?&RbWf8YKU^p5-Nn|{8&7&1%>E4LrT@$_<>$S5bif7P3w za5Kzo4|cWu4fw&c5wm&~_fD4E@cH~|#(#D_C`#vgx+xU0BZ&4RezfOxAb$S@8^Ukr zQUbY=9+)sO4${)Lq<_PadM%x^*T-}&_~FCX^Vbg0-3IJjCH=+??tD{Xums$_gOF1C z5*KKH#_XBx*_o}U5KT;fSPnAxmkpRrab3^c9vkRvQE+YDx|QMY62}^A+S7{geW3{5 z9RlaM4wT0(=J;>pQ~+y-9LUmHm6D<+P9-EFYVMEt_sInEsfAoTYoR>v?_Q_KE> z5f~DV@L3BHvEWzCSiBB1m&79Crx;9)T85B_C_2~kL1;)YZr!@s<<*Xkj-UGV>(|IJ zz1xqvttPj>;;(e4ob5-B|4asq1{14uq|0mJU~h%@A3x#Av*+9ya_95!E+6`fNn6_s zijj?|CH<Z&T#*VmJdKgOFkZ+d(~N=gdjeP}o6+;n${C3Mx~5D@DJMdj(8 z@aMzA?mF3XF4Q!lu#IH+-@cd*vUAD0x;lu7iADE>KRL=3%bg~`IM@^_V*&_&9!G<< zV=k;5GGR?~TgQAloAAZq_=Ep#5|Emj%H?Ou%F3I2!k@Ij2N6_vahUH2Rb5NMo7+VO z?Yfi?N7pjSc?_|0=Z8zi%5Ng(uq(iEq zbT(YpBYuG_m~9&v{|hoQ7F1gpjJb2?A~!de`|h`FunVZaIk|T28r0R**C;3`WY9T^ z#MP@;|3HGER>%7AyEoP^DOaN)>ORaPP`^$MD4AMwAr5)u+X=TlYE($WJufPNWNl-%M*Pg{%C8*9?c zZD-gZbWiBlx);%4GT8uQw5Gt>JrDMj4}A+ivlCnAA~?EN!@@QlqqWv!gj^V%{h5)i z>yVxJW8J!q$jG=(KCtFbNqS8+zB_mBuw3mm0X!tYZD?pH7z$OFCUN8P!c^V5`+F`v z%Gp*cB`ek99pi=2II0zO$RQZ!Q%xZ?+!*fsdvM_w63%?`O({TxfddDyfB$}xSq#1#Fzo27N|T4w79WpjE{R9WgG7u{RQA;K)I%8YU4TD$XAu0i1dvaq+(%v! zmX?Pp|F}mr)FQaJQ61GKm-LI|LNu|)#!ODG`~t4d%4)5A&qDC2cX7Q9JBJi#YcF~y zDmtQGN?K|&kH?E3f4G!<$|5BtrO8vLPSxxH_!omm&mmruwLYu0>h#C@o6+FO^M*;u zOFf*tKa9g4{!gk~Q!X81uS2!rF>qW+^)=rKv=1`G7>)U~-@F1J|7v)9SHi=q9DHx$ zx7P!BQJv7sw-UZ|&(7fzbamH*r|3cUZB;&Egv_duG6OA69@+W_ef|r$J>IS@JQ>+k zu=hiASpw6^KD~wdq)yrtfXVyAG0trqVtq_-p6YrhraQpMP=nUP5WXiI=95h^Y~&D# ziphhg=prPuArKKC0CO|T;2r_cVq6c% zD?&A;*Lv5MrjDx5JJx3l<;Co(D^BM6?mu_NEesnxG`{<{JI`rr*SqHaN%e-pe@&>lvvYD?*2*Q1a<*@)xw~7duH=ez z=Vt}ux~gn$n0_1IJ!&c7v5Djm$zSxn^)dNCI9wJxBh1YSS65EPdM_iGu=*I)@7Nx} zWxfN%hKfN^NpYXEle5Zz{sTmGb#;enX=(i^CNBDM%tUpnnNaPOY9l86iJ-hcjlI98 z>*2iv+;a8JwV`!cv03%0lixO_&-+++IrvkXr|xT22NG}IP#jM+eX z*ogV#opr$e&qFBj+H$4^R1H<27cY;#%_F2u9) zV`yv4eETqCaawE1PX4$4{{MAdP>xvs1x5FM$(Qx0ZaweIyn7$^Wt}^vqp_fu`?9X^ zf7AZMXHEJlpsNZAOZ@K6_p?$YTGD?2@!M7 zIp>5~5Jk*wE81eh*mfHbNfL^>d+&q4EXpcGdg`24?|<)q@80j_`|iEJfk7XGuMH#; z1B?v~wwoCkcpDfP7#en_zcVnX!MwlzwIf~duLcH7Z4C^*!+a{A{I!9>Kimuq_*e%? z7{p?reD*W#i1hs%_B9!g0fq+H!|Y67NE_-1alx`)1y4so9iJapXDRsMm8pr_TcoS< zc6TT%*){pY`UR;~CdTIN$BvzZ#>OW2_19mawY3$T9UUK>-#-N?teiC*oaB~j-ZCK{ zOpQ&K=)+F=$m5E!p(*5#_Gb8p53*6$|I&8w%!GAP+q3mD`&&RkYFk!sW*%C@GqZRo zuoP6KrDe1~dh`VD-mQYYd-uWHx9`Bw-r+Y6|I!%=U~Xy#Q3I?Q1|Mf3m>8M9(uW_h z8&co*a}YD^Gt>L`8v77`DVUkMHp?A;X4ttlP;4of^48eMynXAoop8760X%&47=F5W z8|Ken1Uz%gW`(Wffv2+!OpHyz+2&*aQ#*|MnzSQExvn-~{#e34I?{#V&rSB|HGV%= zAqa%|N}0p$Pw-P4tZnx+nws!c4)#v%(b2I@7JQkK$8&5lGY@=gVVP7Xmiz#Go)7rD zin?GBS@PZ^#sWR~%W!_d%ygd){3AUeF|>E{$427PCbvDM((HHcS#E!`MPj|J+1S{o zSs-3qV`Ac5Yh^V76^{~zhWe}ej7CbUpZO!8oDD9rGIiAipd z9A?+0AM~w-SmfdFCI(XzLA%WM>?io;4);`2*>2$T6M)By0vpGNU}2g0-pnijWHu*M zHjekyb)>SzAQgO^Kg2WCOALe&gHXE-m}iCIF1^Qf(jGU^k+p&LAqq5~QX(Dnwj(~m zzs*^J{Vuap85{8t&nb{QRAIkXvVhpYPVoY4>@I+X#RxDqlBmpi5e-I$yk@CvCpbF4 z13Sm3>iH&?Oa)spU(GKu#0DlLxN-PLY2&9fV*eV$2n?^O9q#rb)wGn3y@+ea)D$lm z6Rm*wfpSKBkhcVU`unt|r)R28oi2gbZ{C8vonM>8dIQ5mxa<{=K`dPf=4L@EqHE3* zJq(RXE=kPHJu8yf-!L;9&}L(I2^^f?f{nui3{@Zyj0Q)U1>-R?+zw`>c|&@XD~ErU zF8o{`^7u~-t1+l}re@0MKnExo)3GkNcF_J543)%JXu*TTK?*3$=$KEj!Hyg) zP&m1_4p0BSW!#KqFnj%BSatX^3>!5bEUlzCkDS%z%*-rQHV$r${d@+#klVTZVrt6& zK(uuQb5kBn8sX07?#OUwn$ujlZqG37_8{#)VwixTiTKDwmN2Nl4Hy~XJTSLZF57w- zt~_gmQ&sQb=$+awVNLNhaPtntdEo`Qi#EfNTW`C(r}E49mqTDUj*&(?6f!Hw9OlNb zQ=bv9&i4fv_tD1KlQh;d#&Eim4x_j6&^fsF`~#f1SN937!gYu643I(olI>7@``st+ zwcEPt@FfV2Ohl}vbnYt0#=1ky$1zqS-w{2*`MEMkXDYQJJSJI1*v~y|_yk+g#$r&o z`M|p4KYsE~8@G0P>E3g=r;1fLU$`|x*av(LS5My~tqdb0Gu6TJJFM@I|J-%n(44V% z@`PO#)nAOAYs=Do=V_hOSw-WV)KhGqD~GiH1=nD8SX2_jUS8eU1>^2ZPr<>(8&({s z=#tOjqWIvCzro1yGaz7a9N5^q;yGpoE?xmJBqepQEeEouEm7k? zQdvXmzTMc!?0ir0eXbm0Ux)qQUVi^AT*bM6^yjYo&G)lctNCl#PcX?RnI}DeJo+0ha=2n1v5754U_CbA*@E^Nd3#V}mj^i9!aquF9 zC!|5n-1V^j#0{WvD8_ltwVTofYxc7>L2Zl~Jps>-8sJ-q8WBTOcUX13!#eD|%9t{b z?T7oX{Q_B27eUtK*>L=B*L{=2@Fj6Z-h8%}X%BLA@q+mD9431Z4cezN^B2OBJ!LrW zvhlvaTcHc1zC6UvqkhlMpV#&!n7*W}r+*kM$Gw)$Ihyl{nRy@-*|2?S%h|hZT@%e@`omMF&-U%n5;S02E| zqZLr`s1C;G&q3W3UlkD^2}2UnUuyH$m)>}~lC2?<(e&L1=50NJawi{(HXc#W8;%~0 zH{RWj^b|k#|3VrAYy(W3z5teNJ_uuT3vnMXQ@J|3Lx6vvGIznI29ADr@u-Qj*q9ML z3L&^Qwab%CM0+PE!_gfybLwJPu>CY^@3?~HAd=Xg`~v)R_XUN*74-%dOcqC-tE!)y zC!~x@K^=qQ)!L)iKEa`li_)}z&E0$qmZ4mqJZ>7>8@0={c@)(&U;1UB%jVSl3Sxuo+jET8_|8Z zNBbwW5z7h)|H6e!U~J~tPukGm%SV+tWuCG-+}iJUTzJ@{_Hy;;tY3*TE2aHIqe*_~ z(fQSh|KACJO3D~mzi#6v_=5unLPYGS7LFGe&swnq@)m9SBAi@ZeQ~D@O?rsHp3a}n zHq@X^K~-__3KY)jx(;an_YEAZI-w;;FWFLz=Z^q3oc!sZ)`sN=E^zzr-@g$5bQ%LU z+%r#{JOev+?1HR}EH)NHBco71>!H%Vhh0FOM08>r)Bn)Esjp9anon{s>Pt!Yclk@O zclQ6ccwXJ2Ht_L|F=x&KxQJtM=JZ(@lb+QU7&^3_?(v#qKzhXT2Q}dC<scp!DgMqj{F?<|P>SuKvls7?CgAAc3{DDH z<*2c_HQZgE?uT5Tl0NZbbsbD!xDH}QjDg(wYt`?#GWz1P_uhf4haWdzG-F zM{>jZ^_yVP!lmHtu+7_-}y8-+59f0)IbQlymxN&6WqL6t#2*)f-ATl@LXF8mo8m}IkV@23~gScvL?4*e$v3k;R@af zOHselo3Yq_?mp>7>8?oFyR{Q(a!*w9WUvyPU1OnOiNd-IX0wY+?DYNdQb86rX14$Gv0A#V^CsS zRB5Rh5E;>NPm4{?P?c2vqHYH_wxtL9NVxO1L^qG`<;b_sBLWSY}MxCa=84szDrwZ&)RVCEb75#fu~Q9x(?PS`|ZHq z;P}0DkT2OR$6VroW{Q!kvNH`_T#64!4<2LeiZL-jsU4^S7Gh~GrhF>L#jcnx#WckjQLLi4@5h+& z!*sVX@@MZgVnzqkI%5s`+WfdQbmDh11Yv0!TAj}Z|=KU7ygfjf8ZK+c4$pU7|Nz`C*E?IdJ$g%;*??*6YP3{0auY1%dw zeHwCxyZ4B{kF%srEZhEu^M4UaH$FEr71i(AdjOt3djYr5_b6=8;M#;p?-%5E)X%|+ z(MA8UR%1hxFWK2O@JaO__v5{EYk zgxS>+XDEUm+*gXeCWBgR(NBis zkNzmFR)QRrKsdEYAe{Y@d>+vEr~L zagEI8Bse(zhWIK$CSL*p?j1f;nZumG-rA}e`8jg@xft^lI1ldHh%G;mUk2wBLOweF zZqjz2{(jBnq`=A%BymgG5bbnSmWee&G%X_Mxhi(cly3*1LsLeCsvrbWRptUE$sn07x#>lv(I`*}ifJibz1+3{N>S!dVp) z-|OP1FAs5S_3|CqM)Jk6I~}$u(vv1;rh%sbt3-{X-$z$4)W(3e`m$x(fC0J<^jRP9Dkz2atbz zMos~fDLMYyG1*&BqAitloMdbC^dH3LT^Q;UY3%8q@8s?ao6b~{yhJkeUv;&W%ft77 z!*cIqQ@&S{O(-%x73K8L+eEjO-DB-1TXbydL>QAd4`!^~13NEN!=jxf%+3&(p3`7r z%8%f9>55UeJUKCKVxx9DdzvQwdcmqaK)T22$UihXnaxSEA#XZ!oBC+3F20n9ZSR9V zIC)|?+@W2d)wp%9lArbx82mopYp8)($?eGtHBR#Q{*o+arVk~9Fummul33% zMSdaVAHrcred({=@i#ZKd>Vy%wk^dKaIpMmaCYn8=Iz-Z*VHHW!mzi`JH zH6P+f_CZIyACT>r~38RunF`Z;!K=4zV zznZkUXSyF>!u#g#vPx!G;_}I-g7{LJY_Z2HUo*RLKv>K(K(abDbxaq% zbm+iA?=@{G91kug-RR6^+u`ud*J_O9-@v`6JN|)+du)7ge|n1i_1wY zjbRuT6$2SLGu1lz)%(lPk2Jemeww_w=baZGp}())ZEc>KY2^PWoCCl4`3Atgy$4}V z(OgJ~PgJF3=czbfP_lc)CTF4TPuDY+ z;o%y1Z)qU}91DnwicuCU-Q0Xp!?&vB?pvmZ4ULS4b=}w-cb%;SXFPvM*Nk*280+Gx zorgNf5q^GwsyTD!LvYYQm0w`kn~RTXf#w?5z7wc7;^YD@Pdldbx#D3hc=`?`f32h1 zeDtNU?AKC>{O7Q+NTwH!i%n<_i%M+0iaMV2xIcHNv(mg?tCdhQ@S(8v1cuq*7bu;!OuSbVn$cUPLUUBPN?k*~5A$sj7j6n-8l3LWkC2yUD-9 zt|xrDD}yxpU!hOhEa^McP`)nY(3 z!&fTQxS%SbFBd3{`QJ8ogm18%-hgGk=?tt6)x|)xTu~R3i~0-z)mK1z3ObA=7}R~dS@b|C46TeF>{GUQ@v`Qsd-q}P>{<6o9}ynl z1TjG}{EmcoPm_;;&pS=`PP(J&qDy&ne-}$vyyouR66HQ{p!^?NaM)jppW|^;QC(=X{IMclJ)t@H>PmkV*U?Aw&)tF)kps)K@@bRF?V5I%{-oaX$agd{aSUuJ z{TXH28aRaKKHbStwrjJpuxuO`?+#)sejDmJayWYWVw5-UtlD)#?dvvk^*(lQ(6yIQ zUhcw8O8j0;WolxUr;jF=k8SuH>QtFLO?65B=j33HKKZwhd`$Nyy314k;Mml6M#koY zIXe1cq(5wO;qo@l=XFcz$8(s@YT`vcl;mGScd5m@OPKH2=8`*r`d+RXQ&4~K z#w%ELsKbweqpK?>IhyYC>ACap9=nRQr(o>?mCVj%h&FxX*~ebt{_^1U=kPh1jN^3> zCm?-NAq)r^(%|Ce{er`)8Iy0<{_7v*Q1Wl|9XJ&BU46mmMHM08R$N0*DqM!E0eL-p8@FBS!QPrmnfp8_|pK%Mx_&G^%^vnQy=XOCz2 z!=n;wINcYGiN3oK$D88a7oU*tyUx2d&e6Zv$nEQjismujkJ#a9Z@4wQ?bMIp?H`JM zS2=2*H%+=5&_8|Q&NHM_sY5z?{bp=!?CsGPuie+jANqOvzPW^c3$!QDJUn&#HLTjJ zTX(m!IJ7i~!U zl^M@6hTNcX0OB60%x7Y<&0M`skISQ7n&zK4?4ZV^E9F&1gVV zvhXd7Yw?-FV|=GC(0i)ajK(x28o&UJPVhLF*^9nI=7t#5au|N|ul+-eU-`O7-;9Y; zd?LTt$y2L@(lz(pT|zDw7awa~yL$Dnd0F8NAzl)&ms;#1T3BBk;w^6_J4^h)k2Z;6 zf!3(MSoD(Q8*AH>@1@e%S0eGuH`ek6%|rdI!Oh<4I@bLI1xud!3|D(m-499qn7t-p zNYL9^1!!jxXVr>ikqbIIjfiI;(StHs+lyb4<06Bgf~@YnRM9(@Nh zck^+!ztFiu=Pt=7$yvEIB7xODu8wAm^=*q*?QEmHiuMayCx>y5BHeBN((N#9#V)p& zqij&2S&yFm0tW}-H(QTa?d|FdMr(rfP|2wGnmBt6`+jRg_B8r#n8a-LFM4>!eNBCd z<{G8vUF8p9CE7ShpG|TLo+}YtWu(<-{M7jkG)0v*A^#Wp7L4{VPEO$ZW5cm)N`@EAiW1 Date: Mon, 25 Dec 2017 01:03:41 -0800 Subject: [PATCH 0196/1380] [maven-release-plugin] prepare release jenkins-2.98 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 3366e9d2a8..9e1369fb85 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.98-SNAPSHOT + 2.98 cli diff --git a/core/pom.xml b/core/pom.xml index ee9de7d7aa..e4fba270bd 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.98-SNAPSHOT + 2.98 jenkins-core diff --git a/pom.xml b/pom.xml index 37d28ea832..fd3c80849c 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.98-SNAPSHOT + 2.98 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.98 diff --git a/test/pom.xml b/test/pom.xml index c3a41f4cab..7ad8704be5 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.98-SNAPSHOT + 2.98 test diff --git a/war/pom.xml b/war/pom.xml index fff73572dc..00d5356a45 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.98-SNAPSHOT + 2.98 jenkins-war -- GitLab From 0ac93deba0d165b0b3ef5c2d84a00767eff511a4 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 25 Dec 2017 01:03:42 -0800 Subject: [PATCH 0197/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 9e1369fb85..97b8621288 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.98 + 2.99-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index e4fba270bd..a18fc8263b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.98 + 2.99-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index fd3c80849c..d6324453bd 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.98 + 2.99-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.98 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 7ad8704be5..a0ecb62a44 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.98 + 2.99-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 00d5356a45..2b69d21b1d 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.98 + 2.99-SNAPSHOT jenkins-war -- GitLab From 1aea33742950b7b1e1ce002ecd0393c123ac1b2b Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 26 Dec 2017 11:52:13 +0100 Subject: [PATCH 0198/1380] Bulk cleanup of code in hudson.cli. - [x] - Remove unused imports - [x] - Use diamond operator where possible - [x] - Use Jenkins#get() --- .../main/java/hudson/cli/BuildCommand.java | 4 +- core/src/main/java/hudson/cli/CLIAction.java | 2 +- core/src/main/java/hudson/cli/CLICommand.java | 6 +- .../java/hudson/cli/ClearQueueCommand.java | 1 - .../main/java/hudson/cli/CliManagerImpl.java | 4 +- .../src/main/java/hudson/cli/CliProtocol.java | 3 +- .../java/hudson/cli/ConnectNodeCommand.java | 2 +- .../java/hudson/cli/CreateNodeCommand.java | 1 - .../java/hudson/cli/DeleteBuildsCommand.java | 2 +- .../java/hudson/cli/DeleteJobCommand.java | 3 +- .../java/hudson/cli/DeleteNodeCommand.java | 3 +- .../java/hudson/cli/DeleteViewCommand.java | 3 +- .../hudson/cli/DisconnectNodeCommand.java | 2 +- .../main/java/hudson/cli/GroovyCommand.java | 2 +- .../main/java/hudson/cli/GroovyshCommand.java | 2 +- .../src/main/java/hudson/cli/HelpCommand.java | 2 +- .../java/hudson/cli/InstallPluginCommand.java | 5 +- .../java/hudson/cli/InstallToolCommand.java | 4 +- .../main/java/hudson/cli/ListJobsCommand.java | 1 - .../java/hudson/cli/OfflineNodeCommand.java | 5 +- .../java/hudson/cli/OnlineNodeCommand.java | 2 +- .../cli/ReloadConfigurationCommand.java | 2 +- .../java/hudson/cli/ReloadJobCommand.java | 4 +- .../hudson/cli/RemoveJobFromViewCommand.java | 1 - .../hudson/cli/declarative/CLIRegisterer.java | 85 +++++++++---------- .../handlers/GenericItemOptionHandler.java | 4 +- .../cli/handlers/NodeOptionHandler.java | 2 +- 27 files changed, 71 insertions(+), 86 deletions(-) diff --git a/core/src/main/java/hudson/cli/BuildCommand.java b/core/src/main/java/hudson/cli/BuildCommand.java index cd6a13efa1..8cd09281c5 100644 --- a/core/src/main/java/hudson/cli/BuildCommand.java +++ b/core/src/main/java/hudson/cli/BuildCommand.java @@ -89,7 +89,7 @@ public class BuildCommand extends CLICommand { public boolean checkSCM = false; @Option(name="-p",usage="Specify the build parameters in the key=value format.") - public Map parameters = new HashMap(); + public Map parameters = new HashMap<>(); @Option(name="-v",usage="Prints out the console output of the build. Use with -s") public boolean consoleOutput = false; @@ -109,7 +109,7 @@ public class BuildCommand extends CLICommand { throw new IllegalStateException(job.getFullDisplayName()+" is not parameterized but the -p option was specified."); //TODO: switch to type annotations after the migration to Java 1.8 - List values = new ArrayList(); + List values = new ArrayList<>(); for (Entry e : parameters.entrySet()) { String name = e.getKey(); diff --git a/core/src/main/java/hudson/cli/CLIAction.java b/core/src/main/java/hudson/cli/CLIAction.java index 2c37db46cc..f9b0ef6d92 100644 --- a/core/src/main/java/hudson/cli/CLIAction.java +++ b/core/src/main/java/hudson/cli/CLIAction.java @@ -250,7 +250,7 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy { // do not require any permission to establish a CLI connection // the actual authentication for the connecting Channel is done by CLICommand - return new FullDuplexHttpChannel(uuid, !Jenkins.getInstance().hasPermission(Jenkins.ADMINISTER)) { + return new FullDuplexHttpChannel(uuid, !Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { @SuppressWarnings("deprecation") @Override protected void main(Channel channel) throws IOException, InterruptedException { diff --git a/core/src/main/java/hudson/cli/CLICommand.java b/core/src/main/java/hudson/cli/CLICommand.java index 5efbaf3a33..d4a140f2da 100644 --- a/core/src/main/java/hudson/cli/CLICommand.java +++ b/core/src/main/java/hudson/cli/CLICommand.java @@ -618,9 +618,9 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { * Key for {@link Channel#getProperty(Object)} that links to the {@link Authentication} object * which captures the identity of the client given by the transport layer. */ - public static final ChannelProperty TRANSPORT_AUTHENTICATION = new ChannelProperty(Authentication.class,"transportAuthentication"); + public static final ChannelProperty TRANSPORT_AUTHENTICATION = new ChannelProperty<>(Authentication.class, "transportAuthentication"); - private static final ThreadLocal CURRENT_COMMAND = new ThreadLocal(); + private static final ThreadLocal CURRENT_COMMAND = new ThreadLocal<>(); /*package*/ static CLICommand setCurrent(CLICommand cmd) { CLICommand old = getCurrent(); @@ -638,7 +638,7 @@ public abstract class CLICommand implements ExtensionPoint, Cloneable { static { // register option handlers that are defined ClassLoaders cls = new ClassLoaders(); - Jenkins j = Jenkins.getActiveInstance(); + Jenkins j = Jenkins.getInstanceOrNull(); if (j!=null) {// only when running on the master cls.put(j.getPluginManager().uberClassLoader); diff --git a/core/src/main/java/hudson/cli/ClearQueueCommand.java b/core/src/main/java/hudson/cli/ClearQueueCommand.java index 8e76a477aa..52cd7c4063 100644 --- a/core/src/main/java/hudson/cli/ClearQueueCommand.java +++ b/core/src/main/java/hudson/cli/ClearQueueCommand.java @@ -26,7 +26,6 @@ package hudson.cli; import hudson.Extension; import jenkins.model.Jenkins; -import org.acegisecurity.AccessDeniedException; import java.util.logging.Logger; diff --git a/core/src/main/java/hudson/cli/CliManagerImpl.java b/core/src/main/java/hudson/cli/CliManagerImpl.java index 750a38d210..d799e6bf99 100644 --- a/core/src/main/java/hudson/cli/CliManagerImpl.java +++ b/core/src/main/java/hudson/cli/CliManagerImpl.java @@ -90,7 +90,7 @@ public class CliManagerImpl implements CliEntryPoint, Serializable { cmd.channel = Channel.current(); final CLICommand old = CLICommand.setCurrent(cmd); try { - transportAuth = Channel.current().getProperty(CLICommand.TRANSPORT_AUTHENTICATION); + transportAuth = Channel.currentOrFail().getProperty(CLICommand.TRANSPORT_AUTHENTICATION); cmd.setTransportAuth(transportAuth); return cmd.main(args.subList(1,args.size()),locale, stdin, out, err); } finally { @@ -99,7 +99,7 @@ public class CliManagerImpl implements CliEntryPoint, Serializable { } err.println("No such command: "+subCmd); - new HelpCommand().main(Collections.emptyList(), locale, stdin, out, err); + new HelpCommand().main(Collections.emptyList(), locale, stdin, out, err); return -1; } diff --git a/core/src/main/java/hudson/cli/CliProtocol.java b/core/src/main/java/hudson/cli/CliProtocol.java index 58b5c95a64..2a0cf8d423 100644 --- a/core/src/main/java/hudson/cli/CliProtocol.java +++ b/core/src/main/java/hudson/cli/CliProtocol.java @@ -116,7 +116,8 @@ public class CliProtocol extends AgentProtocol { private static final boolean OPT_IN; static { - byte hash = Util.fromHexString(Jenkins.getInstance().getLegacyInstanceId())[0]; + //TODO: Jenkins#get() is not safe in the current context + byte hash = Util.fromHexString(Jenkins.get().getLegacyInstanceId())[0]; OPT_IN = (hash % 10) == 0; } } diff --git a/core/src/main/java/hudson/cli/ConnectNodeCommand.java b/core/src/main/java/hudson/cli/ConnectNodeCommand.java index ae20f5e608..189d2b5406 100644 --- a/core/src/main/java/hudson/cli/ConnectNodeCommand.java +++ b/core/src/main/java/hudson/cli/ConnectNodeCommand.java @@ -62,7 +62,7 @@ public class ConnectNodeCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(nodes); List names = null; diff --git a/core/src/main/java/hudson/cli/CreateNodeCommand.java b/core/src/main/java/hudson/cli/CreateNodeCommand.java index 6a7f6dee76..edab3f7e71 100644 --- a/core/src/main/java/hudson/cli/CreateNodeCommand.java +++ b/core/src/main/java/hudson/cli/CreateNodeCommand.java @@ -32,7 +32,6 @@ import hudson.model.User; import jenkins.model.Jenkins; import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.CmdLineException; /** * @author ogondza diff --git a/core/src/main/java/hudson/cli/DeleteBuildsCommand.java b/core/src/main/java/hudson/cli/DeleteBuildsCommand.java index db86c433ab..37d6623f1f 100644 --- a/core/src/main/java/hudson/cli/DeleteBuildsCommand.java +++ b/core/src/main/java/hudson/cli/DeleteBuildsCommand.java @@ -57,7 +57,7 @@ public class DeleteBuildsCommand extends RunRangeCommand { protected int act(List> builds) throws IOException { job.checkPermission(Run.DELETE); - final HashSet hsBuilds = new HashSet(); + final HashSet hsBuilds = new HashSet<>(); for (Run build : builds) { if (!hsBuilds.contains(build.number)) { diff --git a/core/src/main/java/hudson/cli/DeleteJobCommand.java b/core/src/main/java/hudson/cli/DeleteJobCommand.java index 199296f87f..03e63e8579 100644 --- a/core/src/main/java/hudson/cli/DeleteJobCommand.java +++ b/core/src/main/java/hudson/cli/DeleteJobCommand.java @@ -31,7 +31,6 @@ import org.kohsuke.args4j.Argument; import java.util.List; import java.util.HashSet; -import java.util.logging.Logger; /** * CLI command, which deletes a job or multiple jobs. @@ -56,7 +55,7 @@ public class DeleteJobCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(jobs); for (String job_s: hs) { diff --git a/core/src/main/java/hudson/cli/DeleteNodeCommand.java b/core/src/main/java/hudson/cli/DeleteNodeCommand.java index 03001fccc3..60a8821e0f 100644 --- a/core/src/main/java/hudson/cli/DeleteNodeCommand.java +++ b/core/src/main/java/hudson/cli/DeleteNodeCommand.java @@ -31,7 +31,6 @@ import org.kohsuke.args4j.Argument; import java.util.HashSet; import java.util.List; -import java.util.logging.Logger; /** * CLI command, which deletes Jenkins nodes. @@ -56,7 +55,7 @@ public class DeleteNodeCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(nodes); for (String node_s : hs) { diff --git a/core/src/main/java/hudson/cli/DeleteViewCommand.java b/core/src/main/java/hudson/cli/DeleteViewCommand.java index 894978e683..955a476ad9 100644 --- a/core/src/main/java/hudson/cli/DeleteViewCommand.java +++ b/core/src/main/java/hudson/cli/DeleteViewCommand.java @@ -33,7 +33,6 @@ import org.kohsuke.args4j.Argument; import java.util.HashSet; import java.util.List; -import java.util.logging.Logger; /** * @author ogondza, pjanouse @@ -57,7 +56,7 @@ public class DeleteViewCommand extends CLICommand { boolean errorOccurred = false; // Remove duplicates - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(views); ViewOptionHandler voh = new ViewOptionHandler(null, null, null); diff --git a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java index 65c95106eb..ad003521d7 100644 --- a/core/src/main/java/hudson/cli/DisconnectNodeCommand.java +++ b/core/src/main/java/hudson/cli/DisconnectNodeCommand.java @@ -61,7 +61,7 @@ public class DisconnectNodeCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(nodes); List names = null; diff --git a/core/src/main/java/hudson/cli/GroovyCommand.java b/core/src/main/java/hudson/cli/GroovyCommand.java index e7b2479516..5e27f54781 100644 --- a/core/src/main/java/hudson/cli/GroovyCommand.java +++ b/core/src/main/java/hudson/cli/GroovyCommand.java @@ -59,7 +59,7 @@ public class GroovyCommand extends CLICommand { * Remaining arguments. */ @Argument(metaVar="ARGUMENTS", index=1, usage="Command line arguments to pass into script.") - public List remaining = new ArrayList(); + public List remaining = new ArrayList<>(); protected int run() throws Exception { // this allows the caller to manipulate the JVM state, so require the execute script privilege. diff --git a/core/src/main/java/hudson/cli/GroovyshCommand.java b/core/src/main/java/hudson/cli/GroovyshCommand.java index 31f998d0aa..6bd34dc691 100644 --- a/core/src/main/java/hudson/cli/GroovyshCommand.java +++ b/core/src/main/java/hudson/cli/GroovyshCommand.java @@ -56,7 +56,7 @@ public class GroovyshCommand extends CLICommand { return Messages.GroovyshCommand_ShortDescription(); } - @Argument(metaVar="ARGS") public List args = new ArrayList(); + @Argument(metaVar="ARGS") public List args = new ArrayList<>(); @Override protected int run() { diff --git a/core/src/main/java/hudson/cli/HelpCommand.java b/core/src/main/java/hudson/cli/HelpCommand.java index 60fcc0970b..d417244a12 100644 --- a/core/src/main/java/hudson/cli/HelpCommand.java +++ b/core/src/main/java/hudson/cli/HelpCommand.java @@ -65,7 +65,7 @@ public class HelpCommand extends CLICommand { } private int showAllCommands() { - Map commands = new TreeMap(); + Map commands = new TreeMap<>(); for (CLICommand c : CLICommand.all()) commands.put(c.getName(),c); diff --git a/core/src/main/java/hudson/cli/InstallPluginCommand.java b/core/src/main/java/hudson/cli/InstallPluginCommand.java index e040328936..79bf8a7c5e 100644 --- a/core/src/main/java/hudson/cli/InstallPluginCommand.java +++ b/core/src/main/java/hudson/cli/InstallPluginCommand.java @@ -35,7 +35,6 @@ import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; import java.io.File; -import java.io.IOException; import java.net.URL; import java.net.MalformedURLException; import java.util.HashSet; @@ -61,7 +60,7 @@ public class InstallPluginCommand extends CLICommand { "If it is the string ‘=’, the file will be read from standard input of the command, and ‘-name’ must be specified. " + "Otherwise the name is assumed to be the short name of the plugin in the existing update center (like ‘findbugs’), " + "and the plugin will be installed from the update center.") - public List sources = new ArrayList(); + public List sources = new ArrayList<>(); @Option(name="-name",usage="If specified, the plugin will be installed as this short name (whereas normally the name is inferred from the source name automatically).") public String name; // TODO better to parse out Short-Name from the manifest and deprecate this option @@ -152,7 +151,7 @@ public class InstallPluginCommand extends CLICommand { if (h.getUpdateCenter().getSites().isEmpty()) { stdout.println(Messages.InstallPluginCommand_NoUpdateCenterDefined()); } else { - Set candidates = new HashSet(); + Set candidates = new HashSet<>(); for (UpdateSite s : h.getUpdateCenter().getSites()) { Data dt = s.getData(); if (dt==null) diff --git a/core/src/main/java/hudson/cli/InstallToolCommand.java b/core/src/main/java/hudson/cli/InstallToolCommand.java index ef8db698f9..4b06b51686 100644 --- a/core/src/main/java/hudson/cli/InstallToolCommand.java +++ b/core/src/main/java/hudson/cli/InstallToolCommand.java @@ -78,11 +78,11 @@ public class InstallToolCommand extends CLICommand { throw new IllegalStateException("No such job found: "+id.job); p.checkPermission(Item.CONFIGURE); - List toolTypes = new ArrayList(); + List toolTypes = new ArrayList<>(); for (ToolDescriptor d : ToolInstallation.all()) { toolTypes.add(d.getDisplayName()); if (d.getDisplayName().equals(toolType)) { - List toolNames = new ArrayList(); + List toolNames = new ArrayList<>(); for (ToolInstallation t : d.getInstallations()) { toolNames.add(t.getName()); if (t.getName().equals(toolName)) diff --git a/core/src/main/java/hudson/cli/ListJobsCommand.java b/core/src/main/java/hudson/cli/ListJobsCommand.java index 68c30dd015..c358eced08 100644 --- a/core/src/main/java/hudson/cli/ListJobsCommand.java +++ b/core/src/main/java/hudson/cli/ListJobsCommand.java @@ -26,7 +26,6 @@ package hudson.cli; import java.util.Collection; import hudson.model.Item; -import hudson.model.Items; import hudson.model.TopLevelItem; import hudson.model.View; import hudson.Extension; diff --git a/core/src/main/java/hudson/cli/OfflineNodeCommand.java b/core/src/main/java/hudson/cli/OfflineNodeCommand.java index e003a633b2..39270918dc 100644 --- a/core/src/main/java/hudson/cli/OfflineNodeCommand.java +++ b/core/src/main/java/hudson/cli/OfflineNodeCommand.java @@ -34,7 +34,6 @@ import jenkins.model.Jenkins; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -60,8 +59,8 @@ public class OfflineNodeCommand extends CLICommand { @Override protected int run() throws Exception { boolean errorOccurred = false; - final Jenkins jenkins = Jenkins.getInstance(); - final HashSet hs = new HashSet(nodes); + final Jenkins jenkins = Jenkins.get(); + final HashSet hs = new HashSet<>(nodes); List names = null; for (String node_s : hs) { diff --git a/core/src/main/java/hudson/cli/OnlineNodeCommand.java b/core/src/main/java/hudson/cli/OnlineNodeCommand.java index 5cb190fcd8..0594d3770c 100644 --- a/core/src/main/java/hudson/cli/OnlineNodeCommand.java +++ b/core/src/main/java/hudson/cli/OnlineNodeCommand.java @@ -56,7 +56,7 @@ public class OnlineNodeCommand extends CLICommand { protected int run() throws Exception { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(nodes); + final HashSet hs = new HashSet<>(nodes); List names = null; for (String node_s : hs) { diff --git a/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java b/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java index adb61e12c6..6b29dcf529 100644 --- a/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java +++ b/core/src/main/java/hudson/cli/ReloadConfigurationCommand.java @@ -46,7 +46,7 @@ public class ReloadConfigurationCommand extends CLICommand { @Override protected int run() throws Exception { - Jenkins j = Jenkins.getInstance(); + Jenkins j = Jenkins.get(); // Or perhaps simpler to inline the thread body of doReload? j.doReload(); Object app; diff --git a/core/src/main/java/hudson/cli/ReloadJobCommand.java b/core/src/main/java/hudson/cli/ReloadJobCommand.java index c8849a317f..3e42d64e68 100644 --- a/core/src/main/java/hudson/cli/ReloadJobCommand.java +++ b/core/src/main/java/hudson/cli/ReloadJobCommand.java @@ -26,11 +26,9 @@ package hudson.cli; import hudson.AbortException; import hudson.Extension; import hudson.model.AbstractItem; -import hudson.model.AbstractProject; import hudson.model.Item; import hudson.model.Items; -import hudson.model.TopLevelItem; import jenkins.model.Jenkins; import org.kohsuke.args4j.Argument; @@ -64,7 +62,7 @@ public class ReloadJobCommand extends CLICommand { boolean errorOccurred = false; final Jenkins jenkins = Jenkins.getActiveInstance(); - final HashSet hs = new HashSet(); + final HashSet hs = new HashSet<>(); hs.addAll(jobs); for (String job_s: hs) { diff --git a/core/src/main/java/hudson/cli/RemoveJobFromViewCommand.java b/core/src/main/java/hudson/cli/RemoveJobFromViewCommand.java index 786f33d21e..5a543c91c5 100644 --- a/core/src/main/java/hudson/cli/RemoveJobFromViewCommand.java +++ b/core/src/main/java/hudson/cli/RemoveJobFromViewCommand.java @@ -31,7 +31,6 @@ import hudson.model.DirectlyModifiableView; import hudson.model.View; import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.CmdLineException; /** * @author ogondza diff --git a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java index 5868d62b9a..2bfcdf4650 100644 --- a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java +++ b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java @@ -47,6 +47,7 @@ import org.kohsuke.args4j.ClassParser; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.CmdLineException; +import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; @@ -90,7 +91,7 @@ public class CLIRegisterer extends ExtensionFinder { * Finds a resolved method annotated with {@link CLIResolver}. */ private Method findResolver(Class type) throws IOException { - List resolvers = Util.filter(Index.list(CLIResolver.class, Jenkins.getInstance().getPluginManager().uberClassLoader), Method.class); + List resolvers = Util.filter(Index.list(CLIResolver.class, Jenkins.get().getPluginManager().uberClassLoader), Method.class); for ( ; type!=null; type=type.getSuperclass()) for (Method m : resolvers) if (m.getReturnType()==type) @@ -98,12 +99,12 @@ public class CLIRegisterer extends ExtensionFinder { return null; } - private List> discover(final Jenkins hudson) { + private List> discover(@Nonnull final Jenkins jenkins) { LOGGER.fine("Listing up @CLIMethod"); - List> r = new ArrayList>(); + List> r = new ArrayList<>(); try { - for ( final Method m : Util.filter(Index.list(CLIMethod.class, hudson.getPluginManager().uberClassLoader),Method.class)) { + for ( final Method m : Util.filter(Index.list(CLIMethod.class, jenkins.getPluginManager().uberClassLoader),Method.class)) { try { // command name final String name = m.getAnnotation(CLIMethod.class).name(); @@ -111,7 +112,7 @@ public class CLIRegisterer extends ExtensionFinder { final ResourceBundleHolder res = loadMessageBundle(m); res.format("CLI."+name+".shortDescription"); // make sure we have the resource, to fail early - r.add(new ExtensionComponent(new CloneableCLICommand() { + r.add(new ExtensionComponent<>(new CloneableCLICommand() { @Override public String getName() { return name; @@ -120,12 +121,12 @@ public class CLIRegisterer extends ExtensionFinder { @Override public String getShortDescription() { // format by using the right locale - return res.format("CLI."+name+".shortDescription"); + return res.format("CLI." + name + ".shortDescription"); } @Override protected CmdLineParser getCmdLineParser() { - return bindMethod(new ArrayList()); + return bindMethod(new ArrayList<>()); } private CmdLineParser bindMethod(List binders) { @@ -134,7 +135,7 @@ public class CLIRegisterer extends ExtensionFinder { CmdLineParser parser = new CmdLineParser(null); // build up the call sequence - Stack chains = new Stack(); + Stack chains = new Stack<>(); Method method = m; while (true) { chains.push(method); @@ -146,15 +147,15 @@ public class CLIRegisterer extends ExtensionFinder { try { method = findResolver(type); } catch (IOException ex) { - throw new RuntimeException("Unable to find the resolver method annotated with @CLIResolver for "+type, ex); + throw new RuntimeException("Unable to find the resolver method annotated with @CLIResolver for " + type, ex); } - if (method==null) { - throw new RuntimeException("Unable to find the resolver method annotated with @CLIResolver for "+type); + if (method == null) { + throw new RuntimeException("Unable to find the resolver method annotated with @CLIResolver for " + type); } } while (!chains.isEmpty()) - binders.add(new MethodBinder(chains.pop(),this,parser)); + binders.add(new MethodBinder(chains.pop(), this, parser)); return parser; } @@ -162,36 +163,30 @@ public class CLIRegisterer extends ExtensionFinder { /** * Envelope an annotated CLI command * - * @param args - * Arguments to the sub command. For example, if the CLI is invoked like "java -jar cli.jar foo bar zot", - * then "foo" is the sub-command and the argument list is ["bar","zot"]. - * @param locale - * Locale of the client (which can be different from that of the server.) Good behaving command implementation - * would use this locale for formatting messages. - * @param stdin - * Connected to the stdin of the CLI client. - * @param stdout - * Connected to the stdout of the CLI client. - * @param stderr - * Connected to the stderr of the CLI client. - * @return - * Exit code from the CLI command execution - * - *

    - * Jenkins standard exit codes from CLI: - * 0 means everything went well. - * 1 means further unspecified exception is thrown while performing the command. - * 2 means CmdLineException is thrown while performing the command. - * 3 means IllegalArgumentException is thrown while performing the command. - * 4 mean IllegalStateException is thrown while performing the command. - * 5 means AbortException is thrown while performing the command. - * 6 means AccessDeniedException is thrown while performing the command. - * 7 means BadCredentialsException is thrown while performing the command. - * 8-15 are reserved for future usage - * 16+ mean a custom CLI exit error code (meaning defined by the CLI command itself) - * - *

    - * Note: For details - see JENKINS-32273 + * @param args Arguments to the sub command. For example, if the CLI is invoked like "java -jar cli.jar foo bar zot", + * then "foo" is the sub-command and the argument list is ["bar","zot"]. + * @param locale Locale of the client (which can be different from that of the server.) Good behaving command implementation + * would use this locale for formatting messages. + * @param stdin Connected to the stdin of the CLI client. + * @param stdout Connected to the stdout of the CLI client. + * @param stderr Connected to the stderr of the CLI client. + * @return Exit code from the CLI command execution + *

    + *

    + * Jenkins standard exit codes from CLI: + * 0 means everything went well. + * 1 means further unspecified exception is thrown while performing the command. + * 2 means CmdLineException is thrown while performing the command. + * 3 means IllegalArgumentException is thrown while performing the command. + * 4 mean IllegalStateException is thrown while performing the command. + * 5 means AbortException is thrown while performing the command. + * 6 means AccessDeniedException is thrown while performing the command. + * 7 means BadCredentialsException is thrown while performing the command. + * 8-15 are reserved for future usage + * 16+ mean a custom CLI exit error code (meaning defined by the CLI command itself) + *

    + *

    + * Note: For details - see JENKINS-32273 */ @Override public int main(List args, Locale locale, InputStream stdin, PrintStream stdout, PrintStream stderr) { @@ -199,7 +194,7 @@ public class CLIRegisterer extends ExtensionFinder { this.stderr = stderr; this.locale = locale; - List binders = new ArrayList(); + List binders = new ArrayList<>(); CmdLineParser parser = bindMethod(binders); try { @@ -207,7 +202,7 @@ public class CLIRegisterer extends ExtensionFinder { Authentication old = sc.getAuthentication(); try { // authentication - CliAuthenticator authenticator = Jenkins.getInstance().getSecurityRealm().createCliAuthenticator(this); + CliAuthenticator authenticator = Jenkins.get().getSecurityRealm().createCliAuthenticator(this); new ClassParser().parse(authenticator, parser); // fill up all the binders @@ -217,7 +212,7 @@ public class CLIRegisterer extends ExtensionFinder { if (auth == Jenkins.ANONYMOUS) auth = loadStoredAuthentication(); sc.setAuthentication(auth); // run the CLI with the right credential - hudson.checkPermission(Jenkins.READ); + jenkins.checkPermission(Jenkins.READ); // resolve them Object instance = null; diff --git a/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java b/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java index 6cd1d86eba..e854cdd9c7 100644 --- a/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java +++ b/core/src/main/java/hudson/cli/handlers/GenericItemOptionHandler.java @@ -57,12 +57,12 @@ public abstract class GenericItemOptionHandler extends OptionHan protected abstract Class type(); @Override public int parseArguments(Parameters params) throws CmdLineException { - final Jenkins j = Jenkins.getInstance(); + final Jenkins j = Jenkins.get(); final String src = params.getParameter(0); T s = j.getItemByFullName(src, type()); if (s == null) { final Authentication who = Jenkins.getAuthentication(); - try (ACLContext _ = ACL.as(ACL.SYSTEM)) { + try (ACLContext aclContext = ACL.as(ACL.SYSTEM)) { 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/NodeOptionHandler.java b/core/src/main/java/hudson/cli/handlers/NodeOptionHandler.java index 90905d5348..a90199e8c1 100644 --- a/core/src/main/java/hudson/cli/handlers/NodeOptionHandler.java +++ b/core/src/main/java/hudson/cli/handlers/NodeOptionHandler.java @@ -53,7 +53,7 @@ public class NodeOptionHandler extends OptionHandler { String nodeName = params.getParameter(0); - final Node node = Jenkins.getInstance().getNode(nodeName); + final Node node = Jenkins.get().getNode(nodeName); if (node == null) throw new IllegalArgumentException("No such node '" + nodeName + "'"); setter.addValue(node); -- GitLab From 72224fabb389afa489af8ae14173eee1a4595409 Mon Sep 17 00:00:00 2001 From: surenpi Date: Wed, 27 Dec 2017 09:01:21 +0800 Subject: [PATCH 0199/1380] typo fixed --- war/src/main/webapp/help/parameter/trim_zh_CN.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/war/src/main/webapp/help/parameter/trim_zh_CN.html b/war/src/main/webapp/help/parameter/trim_zh_CN.html index a3c56e13b7..f6cc94eccb 100644 --- a/war/src/main/webapp/help/parameter/trim_zh_CN.html +++ b/war/src/main/webapp/help/parameter/trim_zh_CN.html @@ -1,3 +1,3 @@

    - 清除字符串前后的清白字符。 + 清除字符串前后的空白字符。
    -- GitLab From 67076834a24f03b832ab03b1bf9b96e05a5da81e Mon Sep 17 00:00:00 2001 From: Daniel Trebbien Date: Wed, 27 Dec 2017 19:45:30 -0800 Subject: [PATCH 0200/1380] Switch to calling Files.newBufferedReader These changes were suggested by Extra Hints for NetBeans IDE: http://plugins.netbeans.org/plugin/73447/ --- core/src/main/java/hudson/Util.java | 2 +- core/src/main/java/hudson/model/Queue.java | 4 ++-- core/src/main/java/hudson/util/TextFile.java | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index 1e13ea9957..0ab2f52713 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -193,7 +193,7 @@ public class Util { StringBuilder str = new StringBuilder((int)logfile.length()); - try (BufferedReader r = new BufferedReader(new InputStreamReader(Files.newInputStream(logfile.toPath()), charset))) { + try (BufferedReader r = Files.newBufferedReader(logfile.toPath(), charset)) { char[] buf = new char[1024]; int len; while ((len = r.read(buf, 0, buf.length)) > 0) diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index bdec3a3f15..17440221a7 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -76,8 +76,8 @@ import hudson.util.ConsistentHash.Hash; import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; import java.lang.ref.WeakReference; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -377,7 +377,7 @@ public class Queue extends ResourceController implements Saveable { // first try the old format File queueFile = getQueueFile(); if (queueFile.exists()) { - try (BufferedReader in = new BufferedReader(new InputStreamReader(Files.newInputStream(queueFile.toPath())))) { + try (BufferedReader in = Files.newBufferedReader(queueFile.toPath(), Charset.defaultCharset())) { String line; while ((line = in.readLine()) != null) { AbstractProject j = Jenkins.getInstance().getItemByFullName(line, AbstractProject.class); diff --git a/core/src/main/java/hudson/util/TextFile.java b/core/src/main/java/hudson/util/TextFile.java index 2cf752d43c..401d275ea7 100644 --- a/core/src/main/java/hudson/util/TextFile.java +++ b/core/src/main/java/hudson/util/TextFile.java @@ -38,6 +38,7 @@ import java.io.RandomAccessFile; import java.io.Reader; import java.io.StringWriter; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Iterator; /** @@ -68,7 +69,7 @@ public class TextFile { public String read() throws IOException { StringWriter out = new StringWriter(); PrintWriter w = new PrintWriter(out); - try (BufferedReader in = new BufferedReader(new InputStreamReader(Files.newInputStream(file.toPath()), "UTF-8"))) { + try (BufferedReader in = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { String line; while ((line = in.readLine()) != null) w.println(line); -- GitLab From 29362a5b7b94ddfe0ead38c423c54c81bfced53d Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Thu, 28 Dec 2017 17:24:49 +0100 Subject: [PATCH 0201/1380] [JENKINS-47736] - Use the new snapshot: remoting-3.16-20171228.162243-1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e9459a85bd..f07e86f2f6 100644 --- a/pom.xml +++ b/pom.xml @@ -175,7 +175,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - 3.15-20171221.175805-9 + 3.16-20171228.162243-1 -- GitLab From 9e9cdbc781b948a956bd2a82459f19a22a294dc9 Mon Sep 17 00:00:00 2001 From: surenpi Date: Sat, 30 Dec 2017 21:14:30 +0800 Subject: [PATCH 0202/1380] Add Chinese translation --- .../hudson/model/Messages_zh_CN.properties | 5 ++++ .../firstUser_zh_CN.properties | 23 +++++++++++++++++++ .../model/Jenkins/loginError_zh_CN.properties | 5 +++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/firstUser_zh_CN.properties diff --git a/core/src/main/resources/hudson/model/Messages_zh_CN.properties b/core/src/main/resources/hudson/model/Messages_zh_CN.properties index 7c3ccde407..15f139a58b 100644 --- a/core/src/main/resources/hudson/model/Messages_zh_CN.properties +++ b/core/src/main/resources/hudson/model/Messages_zh_CN.properties @@ -55,6 +55,11 @@ Node.Mode.EXCLUSIVE=\u53EA\u5141\u8BB8\u8FD0\u884C\u7ED1\u5B9A\u5230\u8FD9\u53F0 MyView.DisplayName=\u6211\u7684\u89C6\u56FE +AbstractItem.Pronoun=\u4EFB\u52A1 + +MyViewsProperty.DisplayName=\u6211\u7684\u89C6\u56FE +MyViewsProperty.GlobalAction.DisplayName=\u6211\u7684\u89C6\u56FE + ManageJenkinsAction.DisplayName=\u7CFB\u7EDF\u7BA1\u7406 ParametersDefinitionProperty.DisplayName=\u53C2\u6570\u5316\u6784\u5EFA\u8FC7\u7A0B ListView.DisplayName=\u7B80\u5355\u89C6\u56FE diff --git a/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/firstUser_zh_CN.properties b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/firstUser_zh_CN.properties new file mode 100644 index 0000000000..5014b275c1 --- /dev/null +++ b/core/src/main/resources/hudson/security/HudsonPrivateSecurityRealm/firstUser_zh_CN.properties @@ -0,0 +1,23 @@ +# The MIT License +# +# Copyright (c) 2013, Chunghwa Telecom Co., Ltd., Pei-Tang Huang +# +# 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. + +Create\ First\ Admin\ User=\u5EFA\u7ACB\u7B2C\u4E00\u4E2A\u7BA1\u7406\u5458 diff --git a/core/src/main/resources/jenkins/model/Jenkins/loginError_zh_CN.properties b/core/src/main/resources/jenkins/model/Jenkins/loginError_zh_CN.properties index fd26271850..e9ddc36afe 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/loginError_zh_CN.properties +++ b/core/src/main/resources/jenkins/model/Jenkins/loginError_zh_CN.properties @@ -1,6 +1,7 @@ # The MIT License # -# Copyright (c) 2004-2010, Sun Microsystems, Inc. +# Copyright (c) 2004-2017, Sun Microsystems, Inc. +# suren # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -20,5 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +Login\ Error=\u767B\u5F55\u5931\u8D25 Invalid\ login\ information.\ Please\ try\ again.=\u767B\u5F55\u4FE1\u606F\u65E0\u6548\u3002\u8BF7\u91CD\u8BD5\u3002 Try\ again=\u91CD\u8BD5 +If\ you\ are\ a\ system\ administrator\ and\ suspect\ this\ to\ be\ a\ configuration\ problem,\ see\ the\ server\ console\ output\ for\ more\ details.=\u5982\u679C\u4F60\u662F\u7CFB\u7EDF\u7BA1\u7406\u5458\uFF0C\u5E76\u6000\u7591\u8FD9\u662F\u7531\u4E8E\u914D\u7F6E\u9519\u8BEF\u5BFC\u81F4\u7684\u8BDD\uFF0C\u53EF\u4EE5\u901A\u8FC7\u67E5\u770B\u670D\u52A1\u5668\u63A7\u5236\u53F0\u5F97\u5F97\u8BE6\u7EC6\u4FE1\u606F\u3002 -- GitLab From 7975ee0cf97f3adb72a6ce2b1ee603e395d0f6eb Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 31 Dec 2017 18:15:37 -0800 Subject: [PATCH 0203/1380] [maven-release-plugin] prepare release jenkins-2.99 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 97b8621288..3a82019f78 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.99-SNAPSHOT + 2.99 cli diff --git a/core/pom.xml b/core/pom.xml index a18fc8263b..00a8c5ac00 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.99-SNAPSHOT + 2.99 jenkins-core diff --git a/pom.xml b/pom.xml index d6324453bd..e2667f16ee 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.99-SNAPSHOT + 2.99 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.99 diff --git a/test/pom.xml b/test/pom.xml index a0ecb62a44..1bb59718bf 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.99-SNAPSHOT + 2.99 test diff --git a/war/pom.xml b/war/pom.xml index 2b69d21b1d..4a352d7e11 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.99-SNAPSHOT + 2.99 jenkins-war -- GitLab From 99ca10114332e7c6fe12b676ea1919617070a488 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 31 Dec 2017 18:15:37 -0800 Subject: [PATCH 0204/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 3a82019f78..ab9aaa34df 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.99 + 2.100-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 00a8c5ac00..5be417297b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.99 + 2.100-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index e2667f16ee..976aaaa8c7 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.99 + 2.100-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.99 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 1bb59718bf..f5ff5a8d15 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.99 + 2.100-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 4a352d7e11..54d046e418 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.99 + 2.100-SNAPSHOT jenkins-war -- GitLab From 4eb314b32742a07dcc68e716b7de1d31f0f917c8 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 2 Jan 2018 22:10:37 +0100 Subject: [PATCH 0205/1380] [JENKINS-48761] - Restore binary compatibility with agents running old Remoting versions --- core/src/main/java/hudson/Launcher.java | 8 +++-- core/src/main/java/hudson/model/Computer.java | 2 +- .../java/hudson/slaves/ChannelPinger.java | 2 +- .../java/hudson/slaves/SlaveComputer.java | 7 ++++- .../security/MasterToSlaveCallable.java | 29 ++++++++++++++++++- .../jenkins/slaves/StandardOutputSwapper.java | 2 +- 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java index 0e6d8c607f..1c5e884a4d 100644 --- a/core/src/main/java/hudson/Launcher.java +++ b/core/src/main/java/hudson/Launcher.java @@ -1289,7 +1289,7 @@ public abstract class Launcher { } public RemoteProcess call() throws IOException { - final Channel channel = getOpenChannelOrFail(); + final Channel channel = _getOpenChannelOrFail(); Launcher.ProcStarter ps = new LocalLauncher(listener).launch(); ps.cmds(cmd).masks(masks).envs(env).stdin(in).stdout(out).stderr(err).quiet(quiet); if(workDir!=null) ps.pwd(workDir); @@ -1308,7 +1308,11 @@ public abstract class Launcher { Channel taskChannel = null; try { // Sync IO will fail automatically if the channel is being closed, no need to use getOpenChannelOrFail() - taskChannel = Channel.currentOrFail(); + // TODOL Replace by Channel#currentOrFail() when Remoting version allows + taskChannel = Channel.current(); + if (taskChannel == null) { + throw new IOException("No Remoting channel associated with this thread"); + } taskChannel.syncIO(); } catch (Throwable t) { // this includes a failure to sync, agent.jar too old, etc diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 8e35bf5b1b..b802f9e4d6 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -1426,7 +1426,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces private static final class DumpExportTableTask extends MasterToSlaveCallable { public String call() throws IOException { - final Channel ch = getChannelOrFail(); + final Channel ch = _getChannelOrFail(); StringWriter sw = new StringWriter(); try (PrintWriter pw = new PrintWriter(sw)) { ch.dumpExportTable(pw); diff --git a/core/src/main/java/hudson/slaves/ChannelPinger.java b/core/src/main/java/hudson/slaves/ChannelPinger.java index 85d6d78675..b791df2813 100644 --- a/core/src/main/java/hudson/slaves/ChannelPinger.java +++ b/core/src/main/java/hudson/slaves/ChannelPinger.java @@ -134,7 +134,7 @@ public class ChannelPinger extends ComputerListener { @Override public Void call() throws IOException { // No sense in setting up channel pinger if the channel is being closed - setUpPingForChannel(getOpenChannelOrFail(), null, pingTimeoutSeconds, pingIntervalSeconds, false); + setUpPingForChannel(_getOpenChannelOrFail(), null, pingTimeoutSeconds, pingIntervalSeconds, false); return null; } diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java index 650667520f..029565f137 100644 --- a/core/src/main/java/hudson/slaves/SlaveComputer.java +++ b/core/src/main/java/hudson/slaves/SlaveComputer.java @@ -38,6 +38,7 @@ import hudson.model.TaskListener; import hudson.model.User; import hudson.remoting.Channel; import hudson.remoting.ChannelBuilder; +import hudson.remoting.ChannelClosedException; import hudson.remoting.Launcher; import hudson.remoting.VirtualChannel; import hudson.security.ACL; @@ -867,7 +868,11 @@ public class SlaveComputer extends Computer { // ignore this error. } - Channel.currentOrFail().setProperty("slave",Boolean.TRUE); // indicate that this side of the channel is the slave side. + try { + _getChannelOrFail().setProperty("slave",Boolean.TRUE); // indicate that this side of the channel is the slave side. + } catch (ChannelClosedException e) { + throw new IllegalStateException(e); + } return null; } diff --git a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java index 1e71fcee5d..90dd080b3b 100644 --- a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java +++ b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java @@ -1,7 +1,13 @@ package jenkins.security; import hudson.remoting.Callable; +import hudson.remoting.Channel; +import hudson.remoting.ChannelClosedException; import org.jenkinsci.remoting.RoleChecker; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import javax.annotation.Nonnull; /** @@ -11,10 +17,31 @@ import org.jenkinsci.remoting.RoleChecker; * @since 1.587 / 1.580.1 */ public abstract class MasterToSlaveCallable implements Callable { + + private static final long serialVersionUID = 1L; + @Override public void checkRoles(RoleChecker checker) throws SecurityException { checker.check(this,Roles.SLAVE); } - private static final long serialVersionUID = 1L; + //TODO: replace by Callable#getChannelOrFail() once Minimal supported Remoting version is 3.15 or above + @Restricted(NoExternalUse.class) + protected static Channel _getChannelOrFail() throws ChannelClosedException { + final Channel ch = Channel.current(); + if (ch == null) { + throw new ChannelClosedException("No channel associated with the thread", null); + } + return ch; + } + + //TODO: replace by Callable#getOpenChannelOrFail() once Minimal supported Remoting version is 3.15 or above + @Restricted(NoExternalUse.class) + protected static Channel _getOpenChannelOrFail() throws ChannelClosedException { + final Channel ch = _getChannelOrFail(); + if (ch.isClosingOrClosed()) { // TODO: Since Remoting 2.33, we still need to explicitly declare minimal Remoting version + throw new ChannelClosedException("The associated channel " + ch + " is closing down or has closed down", ch.getCloseRequestCause()); + } + return ch; + } } diff --git a/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java b/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java index 9a8d10a12f..5c0966e8a8 100644 --- a/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java +++ b/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java @@ -39,7 +39,7 @@ public class StandardOutputSwapper extends ComputerListener { private static final class ChannelSwapper extends MasterToSlaveCallable { public Boolean call() throws Exception { if (File.pathSeparatorChar==';') return false; // Windows - Channel c = getOpenChannelOrFail(); + Channel c = _getOpenChannelOrFail(); StandardOutputStream sos = (StandardOutputStream) c.getProperty(StandardOutputStream.class); if (sos!=null) { swap(sos); -- GitLab From cf1589734f98d895ecfab0ffda18976527f8d507 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 2 Jan 2018 22:24:26 +0100 Subject: [PATCH 0206/1380] [JENKINS-48761] - Use pre-Remoting 3.0 ChannelClosedException APIs --- .../src/main/java/jenkins/security/MasterToSlaveCallable.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java index 90dd080b3b..9dbe3904a4 100644 --- a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java +++ b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java @@ -30,7 +30,7 @@ public abstract class MasterToSlaveCallable implements C protected static Channel _getChannelOrFail() throws ChannelClosedException { final Channel ch = Channel.current(); if (ch == null) { - throw new ChannelClosedException("No channel associated with the thread", null); + throw new ChannelClosedException(new IllegalStateException("No channel associated with the thread")); } return ch; } @@ -40,7 +40,7 @@ public abstract class MasterToSlaveCallable implements C protected static Channel _getOpenChannelOrFail() throws ChannelClosedException { final Channel ch = _getChannelOrFail(); if (ch.isClosingOrClosed()) { // TODO: Since Remoting 2.33, we still need to explicitly declare minimal Remoting version - throw new ChannelClosedException("The associated channel " + ch + " is closing down or has closed down", ch.getCloseRequestCause()); + throw new ChannelClosedException(new IllegalStateException("The associated channel " + ch + " is closing down or has closed down", ch.getCloseRequestCause())); } return ch; } -- GitLab From 7d407a6ec0d7885c727283bfb8abfe694416c0e1 Mon Sep 17 00:00:00 2001 From: surenpi Date: Wed, 3 Jan 2018 15:52:49 +0800 Subject: [PATCH 0207/1380] Add console Chinese translation --- .../project/console-link_zh_CN.properties | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 core/src/main/resources/lib/hudson/project/console-link_zh_CN.properties diff --git a/core/src/main/resources/lib/hudson/project/console-link_zh_CN.properties b/core/src/main/resources/lib/hudson/project/console-link_zh_CN.properties new file mode 100644 index 0000000000..35521a5319 --- /dev/null +++ b/core/src/main/resources/lib/hudson/project/console-link_zh_CN.properties @@ -0,0 +1,24 @@ +# The MIT License +# +# Copyright (c) 2017, suren +# +# 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. + +Console\ Output=\u63A7\u5236\u53F0\u8F93\u51FA +View\ as\ plain\ text=\u6587\u672C\u65B9\u5F0F\u67E5\u770B -- GitLab From 6d44f7e080aed5c4a59f201e1968f97d679e47e2 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 3 Jan 2018 11:59:24 +0100 Subject: [PATCH 0208/1380] [JENKINS-48766] - Create restricted API for getting info about Remoting versions --- .../jenkins/slaves/remoting-info.properties | 6 + .../java/hudson/TcpSlaveAgentListener.java | 1 + .../jenkins/slaves/RemotingVersionInfo.java | 104 ++++++++++++++++++ .../slaves/RemotingVersionInfoTest.java | 47 ++++++++ pom.xml | 6 +- 5 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 core/src/filter/resources/jenkins/slaves/remoting-info.properties create mode 100644 core/src/main/java/jenkins/slaves/RemotingVersionInfo.java create mode 100644 core/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java diff --git a/core/src/filter/resources/jenkins/slaves/remoting-info.properties b/core/src/filter/resources/jenkins/slaves/remoting-info.properties new file mode 100644 index 0000000000..5a3a6ef14a --- /dev/null +++ b/core/src/filter/resources/jenkins/slaves/remoting-info.properties @@ -0,0 +1,6 @@ +# Remoting version, which is embedded into the core +# This version MAY differ from what is really classloaded (see the "Pluggable Core Components", JENKINS-41196) +remoting.embedded.version=${remoting.version} + +# Minimal Remoting version on external agents which is supported by the core +remoting.minimal.supported.version=${remoting.minimal.supported.version} diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index 6b41b6dcf1..e6e2a7807d 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -290,6 +290,7 @@ public final class TcpSlaveAgentListener extends Thread { try { Writer o = new OutputStreamWriter(s.getOutputStream(), "UTF-8"); + //TODO: expose version about minimal supported Remoting version (JENKINS-48766) if (header.startsWith("GET / ")) { o.write("HTTP/1.0 200 OK\r\n"); o.write("Content-Type: text/plain;charset=UTF-8\r\n"); diff --git a/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java b/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java new file mode 100644 index 0000000000..e4ac3b71c7 --- /dev/null +++ b/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java @@ -0,0 +1,104 @@ +/* + * The MIT License + * + * Copyright (c) 2018, 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.slaves; + +import hudson.util.VersionNumber; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +// TODO: Make the API public (JENKINS-48766) +/** + * Provides information about Remoting versions used withing the core. + * @author Oleg Nenashev + * @since TODO + */ +@Restricted(NoExternalUse.class) +public class RemotingVersionInfo { + + private static final Logger LOGGER = Logger.getLogger(RemotingVersionInfo.class.getName()); + private static final String RESOURCE_NAME="remoting-info.properties"; + + @CheckForNull + private static VersionNumber EMBEDDED_VERSION; + + @CheckForNull + private static VersionNumber MINIMAL_SUPPORTED_VERSION; + + private RemotingVersionInfo() {} + + static { + Properties props = new Properties(); + try (InputStream is = RemotingVersionInfo.class.getResourceAsStream(RESOURCE_NAME)) { + if(is!=null) { + props.load(is); + } + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Failed to load Remoting Info from " + RESOURCE_NAME, e); + } + + EMBEDDED_VERSION = tryExtractVersion(props, "remoting.embedded.version"); + MINIMAL_SUPPORTED_VERSION = tryExtractVersion(props, "remoting.minimal.supported.version"); + } + + @CheckForNull + private static VersionNumber tryExtractVersion(@Nonnull Properties props, @Nonnull String propertyName) { + String prop = props.getProperty(propertyName); + if (prop == null) { + LOGGER.log(Level.FINE, "Property {0} is not defined in {1}", new Object[] {propertyName, RESOURCE_NAME}); + return null; + } + + if(prop.contains("${")) { // Due to whatever reason, Maven does not nullify them + LOGGER.log(Level.WARNING, "Property {0} in {1} has unresolved variable(s). Raw value: {2}", + new Object[] {propertyName, RESOURCE_NAME, prop}); + return null; + } + + try { + return new VersionNumber(prop); + } catch (RuntimeException ex) { + LOGGER.log(Level.WARNING, String.format("Failed to parse version for for property %s in %s. Raw Value: %s", + propertyName, RESOURCE_NAME, prop), ex); + return null; + } + } + + @CheckForNull + public static VersionNumber getEmbeddedVersion() { + return EMBEDDED_VERSION; + } + + @CheckForNull + public static VersionNumber getMinimalSupportedVersion() { + return MINIMAL_SUPPORTED_VERSION; + } +} diff --git a/core/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java b/core/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java new file mode 100644 index 0000000000..3e894b404c --- /dev/null +++ b/core/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java @@ -0,0 +1,47 @@ +/* + * The MIT License + * + * Copyright (c) 2018, 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.slaves; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link RemotingVersionInfo}. + */ +public class RemotingVersionInfoTest { + + @Test + public void shouldLoadEmbeddedVersionByDefault() { + assertThat("Remoting Embedded version is not defined", + RemotingVersionInfo.getEmbeddedVersion(), notNullValue()); + } + + @Test + public void shouldLoadMinimalSupportedVersionByDefault() { + assertThat("Remoting Minimal supported version is not defined", + RemotingVersionInfo.getMinimalSupportedVersion(), notNullValue()); + } +} diff --git a/pom.xml b/pom.xml index d6324453bd..d0f11bbe36 100644 --- a/pom.xml +++ b/pom.xml @@ -104,6 +104,10 @@ THE SOFTWARE. 3.0.0 + + 3.15 + 2.60 + true + + maven-dependency-plugin + + + old-remoting-for-test + generate-test-resources + + + copy + + + + + org.jenkins-ci.main + remoting + ${remoting.minimal.supported.version} + jar + ${project.build.outputDirectory}/old-remoting + remoting-minimal-supported.jar + + + + + + org.apache.maven.plugins maven-surefire-plugin diff --git a/test/src/test/java/jenkins/slaves/OldRemotingAgentTest.java b/test/src/test/java/jenkins/slaves/OldRemotingAgentTest.java new file mode 100644 index 0000000000..adb17cf9ce --- /dev/null +++ b/test/src/test/java/jenkins/slaves/OldRemotingAgentTest.java @@ -0,0 +1,140 @@ +/* + * The MIT License + * + * Copyright (c) 2018, 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.slaves; + +import hudson.EnvVars; +import hudson.model.Computer; +import hudson.model.FreeStyleProject; +import hudson.model.Label; +import hudson.model.Slave; +import hudson.model.labels.LabelAtom; +import hudson.node_monitors.AbstractAsyncNodeMonitorDescriptor; +import hudson.node_monitors.AbstractNodeMonitorDescriptor; +import hudson.node_monitors.NodeMonitor; +import hudson.slaves.ComputerLauncher; +import hudson.tasks.BatchFile; +import hudson.tasks.Shell; +import org.codehaus.plexus.util.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.SimpleCommandLauncher; + +import javax.annotation.CheckForNull; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URISyntaxException; +import java.util.Collection; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.assertThat; + +/** + * Tests for old Remoting agent versions + */ +public class OldRemotingAgentTest { + + @Rule + public JenkinsRule j = new JenkinsRuleWithOldAgent(); + + @Rule + public TemporaryFolder tmpDir = new TemporaryFolder(); + + private File agentJar; + + @Before + public void extractAgent() throws Exception { + agentJar = new File(tmpDir.getRoot(), "old-agent.jar"); + FileUtils.copyURLToFile(getClass().getResource("/old-remoting/remoting-minimal-supported.jar"), agentJar); + } + + @Test + @Issue("JENKINS-48761") + public void shouldBeAbleToConnectAgentWithMinimalSupportedVersion() throws Exception { + Label agentLabel = new LabelAtom("old-agent"); + Slave agent = j.createOnlineSlave(agentLabel); + boolean isUnix = agent.getComputer().isUnix(); + assertThat("Received wrong agent version. A minimal supported version is expected", + agent.getComputer().getSlaveVersion(), + equalTo(RemotingVersionInfo.getMinimalSupportedVersion().toString())); + + // Just ensure we are able to run something on the agent + FreeStyleProject project = j.createFreeStyleProject("foo"); + project.setAssignedLabel(agentLabel); + project.getBuildersList().add(isUnix ? new Shell("echo Hello") : new BatchFile("echo 'hello'")); + j.buildAndAssertSuccess(project); + + // Run agent monitors + NodeMonitorAssert.assertMonitors(NodeMonitor.getAll(), agent.getComputer()); + } + + //TODO: move the logic to JTH + private class JenkinsRuleWithOldAgent extends JenkinsRule { + + @Override + public ComputerLauncher createComputerLauncher(EnvVars env) throws URISyntaxException, IOException { + + // EnvVars are ignored, simple Command Launcher does not offer this API in public + int sz = this.jenkins.getNodes().size(); + return new SimpleCommandLauncher(String.format("\"%s/bin/java\" %s -jar \"%s\"", + System.getProperty("java.home"), + SLAVE_DEBUG_PORT > 0 ? " -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=" + (SLAVE_DEBUG_PORT + sz) : "", + agentJar.getAbsolutePath())); + } + } + + private static class NodeMonitorAssert extends NodeMonitor { + + static void assertMonitors(Collection toCheck, Computer c) throws AssertionError { + for (NodeMonitor monitor : toCheck) { + assertMonitor(monitor, c); + } + } + + static void assertMonitor(NodeMonitor monitor, Computer c) throws AssertionError { + AbstractNodeMonitorDescriptor descriptor = monitor.getDescriptor(); + final Method monitorMethod; + try { + monitorMethod = AbstractAsyncNodeMonitorDescriptor.class.getDeclaredMethod("monitor", Computer.class); + } catch (NoSuchMethodException ex) { + System.out.println("Cannot invoke monitor " + monitor + ", no monitor(Computer.class) method in the Descriptor. It will be ignored. " + ex.getMessage()); + return; + } + try { + monitorMethod.setAccessible(true); + Object res = monitorMethod.invoke(descriptor, c); + System.out.println("Successfully executed monitor " + monitor); + } catch (Exception ex) { + throw new AssertionError("Failed to run monitor " + monitor + " for computer " + c, ex); + } finally { + monitorMethod.setAccessible(false); + } + } + } + +} -- GitLab From 8fa79061bb0beaf93be4e05620463fd363ac5b30 Mon Sep 17 00:00:00 2001 From: Alexander Akbashev Date: Tue, 21 Nov 2017 14:18:01 +0100 Subject: [PATCH 0210/1380] [FIX JENKINS-48349] Cache permission id to avoid allocating of new strings Every request that comes from Jelly is checked against Permissions. As result it leads to a call of `getId` method that produces the new string. Usually it's not a problem, but in case of stop-the-world pause user requests are accumulated. So, once pause is finished, we forcibly allocated tons of strings for every request. That leads to new stop-the-world pause. (And this cycle can repeat multiple times) (cherry picked from commit b2c40cb9e0db72c978b3a50be0d4c467cb33eb20) --- core/src/main/java/hudson/security/Permission.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/security/Permission.java b/core/src/main/java/hudson/security/Permission.java index 431ad0fd1d..31f6a944e2 100644 --- a/core/src/main/java/hudson/security/Permission.java +++ b/core/src/main/java/hudson/security/Permission.java @@ -68,6 +68,9 @@ public final class Permission { public final @Nonnull PermissionGroup group; + // if some plugin serialized old version of this class using XStream, `id` can be null + private final @CheckForNull String id; + /** * Human readable ID of the permission. * @@ -158,6 +161,7 @@ public final class Permission { this.impliedBy = impliedBy; this.enabled = enable; this.scopes = ImmutableSet.copyOf(scopes); + this.id = owner.getName() + '.' + name; group.add(this); ALL.add(this); @@ -222,7 +226,10 @@ public final class Permission { * @see #fromId(String) */ public @Nonnull String getId() { - return owner.getName()+'.'+name; + if (id == null) { + return owner.getName() + '.' + name; + } + return id; } @Override public boolean equals(Object o) { -- GitLab From 08444b29c608e759673d60be295bfe2858e1171a Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sat, 2 Dec 2017 12:36:06 +0300 Subject: [PATCH 0211/1380] Merge pull request #3164 from fcojfernandez/JENKINS-48080 [JENKINS-48080] Setup Wizard hangs if confirm password is incorrect while creating admin user (cherry picked from commit b3ebef20ece11cd8cfed0eb32558065150a18c9d) --- war/src/main/js/pluginSetupWizardGui.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/war/src/main/js/pluginSetupWizardGui.js b/war/src/main/js/pluginSetupWizardGui.js index 923f6a3f27..2aac81ed33 100644 --- a/war/src/main/js/pluginSetupWizardGui.js +++ b/war/src/main/js/pluginSetupWizardGui.js @@ -889,16 +889,17 @@ var createPluginSetupWizard = function(appendTarget) { // ignore JSON parsing issues, this may be HTML } // we get 200 OK - var $page = $(data); + var responseText = data.responseText; + var $page = $(responseText); var $errors = $page.find('.error'); if($errors.length > 0) { var $main = $page.find('#main-panel').detach(); if($main.length > 0) { - data = data.replace(/body([^>]*)[>](.|[\r\n])+[<][/]body/,'body$1>'+$main.html()+']*)[>](.|[\r\n])+[<][/]body/,'body$1>'+$main.html()+' Date: Fri, 22 Dec 2017 14:36:34 +0100 Subject: [PATCH 0212/1380] [JENKINS-48593] - Add getSystemProperty(key) to fix hudson.consoleTailKB system property (#3200) * Add getSystemProperty(key) to fix hudson.consoleTailKB system property * Add Javadoc, @Restricted and delegate to SystemProperties These changes were requested by @daniel-beck. * PR was not merged in time for 2.96 * Changes as requested by @oleg-nenashev (cherry picked from commit 006c51256e58d9b691956962d9fd624fb0c1e08b) --- core/src/main/java/hudson/Functions.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/src/main/java/hudson/Functions.java b/core/src/main/java/hudson/Functions.java index 99e76891f0..abdd3c490b 100644 --- a/core/src/main/java/hudson/Functions.java +++ b/core/src/main/java/hudson/Functions.java @@ -471,6 +471,16 @@ public class Functions { return new TreeMap(System.getProperties()); } + /** + * Gets the system property indicated by the specified key. + * + * Delegates to {@link SystemProperties#getString(java.lang.String)}. + */ + @Restricted(DoNotUse.class) + public static String getSystemProperty(String key) { + return SystemProperties.getString(key); + } + public static Map getEnvVars() { return new TreeMap(EnvVars.masterEnvVars); } -- GitLab From 065d3c830361471651a6c59ff78a33f0f4aa0900 Mon Sep 17 00:00:00 2001 From: Johno Crawford Date: Fri, 15 Dec 2017 15:04:55 +0100 Subject: [PATCH 0213/1380] [JENKINS-48505] Invoke optimistic before computeIfAbsent to avoid contention. (cherry picked from commit dce450e1b4565b682f321329caea080293ef0b9b) --- core/src/main/java/jenkins/model/Jenkins.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 10deb6e003..5bcd97f138 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -2628,7 +2628,8 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve */ @SuppressWarnings({"unchecked"}) public ExtensionList getExtensionList(Class extensionType) { - return extensionLists.computeIfAbsent(extensionType, key -> ExtensionList.create(this, key)); + ExtensionList extensionList = extensionLists.get(extensionType); + return extensionList != null ? extensionList : extensionLists.computeIfAbsent(extensionType, key -> ExtensionList.create(this, key)); } /** -- GitLab From 0319362666bb9e742c46a8fb28e70a660e53d4f3 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sun, 17 Dec 2017 14:26:51 +0100 Subject: [PATCH 0214/1380] Merge pull request #3193 from dwnusbaum/JENKINS-48365 [JENKINS-48365] Install detached plugins when upgrading Jenkins past the version the plugins were detached (cherry picked from commit 968b6adf007523ca7726e8161be65c9753c4df43) --- core/src/main/java/hudson/PluginManager.java | 5 +- .../java/jenkins/install/InstallUtil.java | 3 +- .../test/java/hudson/PluginManagerUtil.java | 11 ++ .../install/LoadDetachedPluginsTest.java | 121 ++++++++++++++++++ .../upgradeFromJenkins1/config.xml | 34 +++++ ...enkins.install.InstallUtil.lastExecVersion | 1 + 6 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java create mode 100644 test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml create mode 100644 test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index c816805ba6..7fb2ed8712 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -679,9 +679,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas *
*/ protected void loadDetachedPlugins() { - InstallState installState = Jenkins.getActiveInstance().getInstallState(); - if (InstallState.UPGRADE.equals(installState)) { - VersionNumber lastExecVersion = new VersionNumber(InstallUtil.getLastExecVersion()); + VersionNumber lastExecVersion = new VersionNumber(InstallUtil.getLastExecVersion()); + if (lastExecVersion.isNewerThan(InstallUtil.NEW_INSTALL_VERSION) && lastExecVersion.isOlderThan(Jenkins.getVersion())) { LOGGER.log(INFO, "Upgrading Jenkins. The last running version was {0}. This Jenkins is version {1}.", new Object[] {lastExecVersion, Jenkins.VERSION}); diff --git a/core/src/main/java/jenkins/install/InstallUtil.java b/core/src/main/java/jenkins/install/InstallUtil.java index e4868e95c1..5b3f95555b 100644 --- a/core/src/main/java/jenkins/install/InstallUtil.java +++ b/core/src/main/java/jenkins/install/InstallUtil.java @@ -70,7 +70,8 @@ public class InstallUtil { private static final Logger LOGGER = Logger.getLogger(InstallUtil.class.getName()); // tests need this to be 1.0 - private static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0"); + @Restricted(NoExternalUse.class) + public static final VersionNumber NEW_INSTALL_VERSION = new VersionNumber("1.0"); private static final VersionNumber FORCE_NEW_INSTALL_VERSION = new VersionNumber("0.0"); /** diff --git a/test/src/test/java/hudson/PluginManagerUtil.java b/test/src/test/java/hudson/PluginManagerUtil.java index 2263622b4c..423213b7d6 100644 --- a/test/src/test/java/hudson/PluginManagerUtil.java +++ b/test/src/test/java/hudson/PluginManagerUtil.java @@ -26,6 +26,8 @@ package hudson; import jenkins.RestartRequiredException; import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; +import org.junit.runner.Description; +import org.jvnet.hudson.test.RestartableJenkinsRule; import org.jvnet.hudson.test.JenkinsRule; import java.io.File; @@ -47,6 +49,15 @@ public class PluginManagerUtil { }; } + public static RestartableJenkinsRule newRestartableJenkinsRule() { + return new RestartableJenkinsRule() { + @Override + public JenkinsRule createJenkinsRule(Description description) { + return newJenkinsRule(); + } + }; + } + public static void dynamicLoad(String plugin, Jenkins jenkins) throws IOException, InterruptedException, RestartRequiredException { dynamicLoad(plugin, jenkins, false); } diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java new file mode 100644 index 0000000000..eecc1e2b49 --- /dev/null +++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java @@ -0,0 +1,121 @@ +/* + * The MIT License + * + * Copyright 2017 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.install; + +import hudson.ClassicPluginStrategy; +import hudson.ClassicPluginStrategy.DetachedPlugin; +import hudson.PluginManager; +import hudson.PluginManagerUtil; +import hudson.PluginWrapper; +import hudson.util.VersionNumber; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.RestartableJenkinsRule; +import org.jvnet.hudson.test.recipes.LocalData; + +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class LoadDetachedPluginsTest { + + @Rule public RestartableJenkinsRule rr = PluginManagerUtil.newRestartableJenkinsRule(); + + @Issue("JENKINS-48365") + @Test + @LocalData + public void upgradeFromJenkins1() throws IOException { + VersionNumber since = new VersionNumber("1.550"); + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + assertThat("Plugins have been detached since the pre-upgrade version", + detachedPlugins.size(), greaterThan(4)); + assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", + getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); + assertNoFailedPlugins(r); + }); + } + + @Issue("JENKINS-48365") + @Test + @LocalData + public void upgradeFromJenkins2() { + VersionNumber since = new VersionNumber("2.0"); + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + assertThat("Plugins have been detached since the pre-upgrade version", + detachedPlugins.size(), greaterThan(1)); + assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", + getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); + assertNoFailedPlugins(r); + }); + } + + @Test + public void newInstallation() { + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(); + assertThat("Detached plugins should exist", detachedPlugins, not(empty())); + assertThat("Detached plugins should not be installed on a new instance", + getInstalledDetachedPlugins(r, detachedPlugins), empty()); + assertNoFailedPlugins(r); + }); + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(); + assertThat("Detached plugins should exist", detachedPlugins, not(empty())); + assertThat("Detached plugins should not be installed after restarting", + getInstalledDetachedPlugins(r, detachedPlugins), empty()); + assertNoFailedPlugins(r); + }); + } + + private List getInstalledDetachedPlugins(JenkinsRule r, List detachedPlugins) { + PluginManager pluginManager = r.jenkins.getPluginManager(); + List installedPlugins = new ArrayList<>(); + for (DetachedPlugin plugin : detachedPlugins) { + PluginWrapper wrapper = pluginManager.getPlugin(plugin.getShortName()); + if (wrapper != null) { + installedPlugins.add(wrapper); + assertTrue("Detached plugins should be active if installed", wrapper.isActive()); + assertThat("Detached plugins should not have dependency errors", wrapper.getDependencyErrors(), empty()); + } + } + return installedPlugins; + } + + private void assertNoFailedPlugins(JenkinsRule r) { + assertThat("Detached plugins and their dependencies should not fail to install", + r.jenkins.getPluginManager().getFailedPlugins(), empty()); + } + +} diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml new file mode 100644 index 0000000000..6fcea351c2 --- /dev/null +++ b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins1/config.xml @@ -0,0 +1,34 @@ + + + + 1.550 + 2 + NORMAL + true + + + false + + ${JENKINS_HOME}/workspace/${ITEM_FULLNAME} + ${ITEM_ROOTDIR}/builds + + + + + 5 + 0 + + + + All + false + false + + + + All + 0 + + + + \ No newline at end of file diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion new file mode 100644 index 0000000000..415b19fc36 --- /dev/null +++ b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2/jenkins.install.InstallUtil.lastExecVersion @@ -0,0 +1 @@ +2.0 \ No newline at end of file -- GitLab From e9ecbb326c0e8974c9624fc623be2497299c377f Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Tue, 19 Dec 2017 19:53:38 +0100 Subject: [PATCH 0215/1380] Merge pull request #3201 from dwnusbaum/JENKINS-48604 [JENKINS-48604] Do not downgrade plugins that are dependencies of detached plugins when upgrading Jenkins (cherry picked from commit 1dc2c6d5ff666d60a0eb54125ce7694986d1025b) --- core/src/main/java/hudson/PluginManager.java | 12 +++++- .../install/LoadDetachedPluginsTest.java | 39 ++++++++++++++++++ ...upgradeFromJenkins2WithNewerDependency.zip | Bin 0 -> 150149 bytes ...upgradeFromJenkins2WithOlderDependency.zip | Bin 0 -> 168835 bytes 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithNewerDependency.zip create mode 100644 test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithOlderDependency.zip diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index 7fb2ed8712..b35149190b 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -650,7 +650,17 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas continue; } - String artifactId = dependencyToken.split(":")[0]; + String[] artifactIdVersionPair = dependencyToken.split(":"); + String artifactId = artifactIdVersionPair[0]; + VersionNumber dependencyVersion = new VersionNumber(artifactIdVersionPair[1]); + + PluginManager manager = Jenkins.getActiveInstance().getPluginManager(); + VersionNumber installedVersion = manager.getPluginVersion(manager.rootDir, artifactId); + if (installedVersion != null && !installedVersion.isOlderThan(dependencyVersion)) { + // Do not downgrade dependencies that are already installed. + continue; + } + URL dependencyURL = context.getResource(fromPath + "/" + artifactId + ".hpi"); if (dependencyURL == null) { diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java index eecc1e2b49..856e5263ba 100644 --- a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java +++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java @@ -26,6 +26,7 @@ package jenkins.install; import hudson.ClassicPluginStrategy; import hudson.ClassicPluginStrategy.DetachedPlugin; +import hudson.Plugin; import hudson.PluginManager; import hudson.PluginManagerUtil; import hudson.PluginWrapper; @@ -44,6 +45,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -81,6 +83,43 @@ public class LoadDetachedPluginsTest { }); } + @Issue("JENKINS-48604") + @Test + @LocalData + public void upgradeFromJenkins2WithNewerDependency() { + VersionNumber since = new VersionNumber("2.0"); + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + assertThat("Plugins have been detached since the pre-upgrade version", + detachedPlugins.size(), greaterThan(1)); + assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", + getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); + Plugin scriptSecurity = r.jenkins.getPlugin("script-security"); + assertThat("Script-security should be installed", scriptSecurity, notNullValue()); + assertThat("Dependencies of detached plugins should not be downgraded", + scriptSecurity.getWrapper().getVersionNumber(), equalTo(new VersionNumber("1.34"))); + assertNoFailedPlugins(r); + }); + } + + @Test + @LocalData + public void upgradeFromJenkins2WithOlderDependency() { + VersionNumber since = new VersionNumber("2.0"); + rr.then(r -> { + List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + assertThat("Plugins have been detached since the pre-upgrade version", + detachedPlugins.size(), greaterThan(1)); + assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", + getInstalledDetachedPlugins(r, detachedPlugins).size(), equalTo(detachedPlugins.size())); + Plugin scriptSecurity = r.jenkins.getPlugin("script-security"); + assertThat("Script-security should be installed", scriptSecurity, notNullValue()); + assertThat("Dependencies of detached plugins should be upgraded to the required version", + scriptSecurity.getWrapper().getVersionNumber(), equalTo(new VersionNumber("1.18.1"))); + assertNoFailedPlugins(r); + }); + } + @Test public void newInstallation() { rr.then(r -> { diff --git a/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithNewerDependency.zip b/test/src/test/resources/jenkins/install/LoadDetachedPluginsTest/upgradeFromJenkins2WithNewerDependency.zip new file mode 100644 index 0000000000000000000000000000000000000000..557841273ad6240326625333b6bda5b61586225b GIT binary patch literal 150149 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'_RD7)2@On9eRhfG56 zbO!c~M}B_G40jD#R(h@&uy4%p6qoppISgtL*ridFX#WmTMxoCYYm^v$hq>GX9OfX> z;1pABxtqy7VmVsK+%q8T32ilmXcqVW1?BV^8~EgF<-$F_YbDknLk7n^yOm9R;~3cr z+bgvRm?RxaDI-li(a&g>0&=D^7?dJym^@v-WDMuIzC_Smr4`R+Df@L5{B?)=Jpq2* zJc~lUm`*?5$G`d`3+v|ko;T~V0M+y~5EI!?0Jh$RZNPu^q5>Kl<5A0X#1qluKjxXc zJWx=!kiQcBRw9UT^L}VI&OBK`9J!b=TTUnA-aOaK^X(zT5ei|HuuW@&4clTbz^_c3 z#Z6f@jOKAAlMLzZSTyvCIX2{-T%_$KL(#}Ys)0BRd0WwkbR4HS(X%TqJ@YTUL1~4( z@@@AYn2P$S8u%_o+GJK&drzrT7oJ9^?@8sW`lVFg$&rt2npfgYW_Z(J6ix%d@5U15 zA|C>ITZ)u8z#NBRak9HRXNxX=6zYyiS{kG)$`923ReMNMGoG{SAE`fFW zwk*i7lSVG!IAR?bWWYu>nnhG)`O(Mw_`3fs7oYLbw(a%jpZ&b0U-U<4{5{1q^yW54 zCs#K{_a=8G7Y_gXr`mWFgVI1iWq)WGS!>yqMiyj%<5nzGgOP*~2tt2#{Gvnc}_vX*%dKbVm)(8J_Rh!eokJ{_OSG#0&E|#}hYU$04-o4sdBm zyt(?%>O9yOe_?RI>~JG^at{ZzB{?170y+>X1MuD;jAgZQD1uSncO;vMX|-|r(9Ea( z0F<Fq>%%z z+JoEuOTaz zae^iy>~wqmomI$bl6(i;;PXSi()HCbG{wER&krxGLX1kwK`M(PjnQaRC|v=vpPV_X z=^@|#CS;6-%#oJ{dIW2uedaBfvh>hZF^k0K4@>jATmJ8*&0w8+?e4yO>F>eE=r=!Y ztG`@XAgWb-5xZa129os4cQLF`3850Wt;F-Ui8w>MiEOjbfXyk=kJtDP{{b>asRLUi z1GMN@UnD=A^1r(f(KpGu$4PA!I$Z%CQ z@wi60Sfgl>pt-ph-2Mi#{5;z$7mkD8w=p>R%GCRyODcZ*!XT=yxYdmw)ZKY~yFwTn zb}Q6=|H_QhO&dg5nnCJ}Lz$tDu%(p?Jl}(8lWveoRUg>vz+5EN9OiOEipn+=72}nw zYY#xzEio35sPd~Ks)HW}+JM8@q6n+j`%YtF*bzFFdt60J7vmwi^_XGS;)HFn+5ruU z-=;O!sOSjDlg7wBvTz6Eb5hi#%PIc8T6BxZIDsthjKf&v^(NwS(2$X{?w4J&!})G> zD(RbdsNNilU-5wB@#H$}n5Ku*VELyy`rfKN`7S3->S3!uTOx~&<+B&;Do@U~(H2>G zB&LEY8OpflgiIyLl;875rF9*PE^}^cP+pvAMzz>eqgW9m3Yr{cu-cnl<+=_-7fkUY zUX%pR;Hx!L#Ad4k&t6igG?kVmqAzmNIP2SrC)@0Doq^2{#~+|YQ;1#^eO?pDg-gFL zksOg^$upFu)?U=abVq#?i;rHG0?%`o+z8YBsAe7)08OGr17IBeM4$c$I z_Yx|h^Yk<*AG1>o-R<-jQd25-j`ZXKx`c7!2&0sDlXoT%XHTft<*jGKFZ=TZePBV{ z*Ei%E{7AV)J(Jn@4hGN7H>uqP3zf(GMv=Wqk{*4*kt0zmvesN znf=%C^ZGag>j^6tOf151&O!xQ3>38%zUUe0FqTgq((E@HtgMvrc%9a5={Dz^<(#JU zz_MKjV@uHnzI4QKz?oj9DpZ75)?b$`#2c^ba^U0i%W3mXrN-;EBvrm{#AdY86Lv#M z)cIt0wp;t+f@^X-Hr!X2&ePzAS{JRMHk#Y73yp52XTj$TC1)qokev%Q8J-Ohx?idI z$!{A(HE#s7Bl)JRAL0*_7%XK7HD31*ayv2F<8}6~LSXl{)jU=Whz&1I#QJUlsB*i# zCLW(EYH?X;we*JwVHIOeR719-r;Ddf{y{Pl2mlvTNMDqYicJRvyjPS&WM zewTpE?Q)bYeCN#D-$ne4j~=TE^mO}w3=H{-zV$EHJMJ@eI!Mx$kzrorJ?c{5mk35_ zmr7D2NsF%pI@b0?Rt(J`yOTL7JJLm-=puqc0c8ZjLloGdTnZ+H+Y2_BtkEBseBWd= zNBBcY5-1C=w2<}u9T%K+6UIHCulD3?<5sn11nDYCINhBLSJYLoDCl0FYF=f)O;7a8WBG zfOe5dxTJE!k|1Fm*3PD{`su~~>wEcCDM<9Kw4b(=zv09{HvVxC5Yp`nLmDlDs3(ZK z$%7zag;Hx!b7-fMLWJ*in~C}}S!pHq6}-xR0}~>x0ynG=@qGF$$bbWdO!M@+%k>?z zvehj5o#2>$?biYSkV0xY zV>vD7wbB)9CNd(OZ8bO0l2dgKjaL=b3j1#BV`pO`|L$})n;Ys-g*6tr90tqg*d?gY zX*8hs&qEiY1x2X(l@f@49HK0wTbuZ&e&Q1Acc%*+sg*U&=hhYx98*gdJ)4SFqGnt2 zt%zzrsxO1S1MehXz>!pt8Dl-ZdPHm5tIvdXhF~p7*6Ge#3Ik2_ePt@gWOPF~Z=hU{ zp?16uT(4
    d`!c%x?@8w`mGt83}fw!pogJ)XL>lnX*xRwt5b*o0T=Pl|i8e&R5I zGD`iF_OJ3$)8*9BZO1u!C|SAEUGw!?y!g>zHq}V8J=ZV_C67q#Q9E!r#h=kkcAcGh z4W4DGOj<|<$Z4-8y`L2r<7#f_053EbqsSh*{jFin?!`IZ?o1vc&Q{j z_m&(aO4Uv3(Wb)p64z9)<@6*7Y8c=yT#`CClQw3tdyvFqhHs%5{8PuS90kufq3MJA z-xEn>J_Gs*?}Xpa_OJ>JkUZU)fZtk=Iztda2x8}UvBWq(PK(}z=H8!si9fE0F+E52 zMh-H0!F@UsdId+B%VXX_J0cOGfn~=o;S)~T72?+Od>ZZZxCdMB<9L?#q=_%i(N1P> z2nU%`l49S?$PeOQ(Rj|8og-?1a48UGN+vS7FW#|!@7qrIO@GM&ws{CTsQYQj{urta zKll4d6|$5CR$*lU(?5keVckj=>X_R3HR2ED8WGFwH!qZq*5?r7E^I1%+?zSsK{{;4&{X7o$TXKw@YN z`58Fr>r|x2q-SWVIHI|Q?ly%(Of_1Ex?WY<%bKX`a&3@D+o-9vTJ@PUFHxRb?LAQ1 z)hd+gXh$pT7)jmb*bKSRYYd{!-5R$VTW&MEIT8fHnv70462A~Cc8%UvbF4UXRxacz zPQ2Q!`wCXC!xAabrar|wQR9EZhDy0Qhw36zll1ns&SNysYBUKi)Q7-=r`Z*oPqxJ- zQ~WR>@0z7?vQsFp+Fl+irV+~0kU1>>)6=GKPkzkJqL3GfRhCim$v^|1|A$>+nsjR| zTY!R;SMpA1G~n!Ep}+GLwsyUqLQ{=by()Hep9-+()dLbs+QY7VnS`98 zS4I)N7u7MD^Bo-crL%Km7Y*$*pk}J9A)^emgCfl2;He2jKshD~6D_jtdfDj-!DvI^ z9=8KAm1Bk|+t8`?j>tK>q@Rt^pwf;`%vYk|fw=oBNm$T0;Izp{3|s;ET4|-QpqRm5 zmZb;K1ByDg`h+4^MXm0(zva{mM+^Xh*`v+}mi480dqy_w~G& z&L#i=aSbn?7x65b!!Xw!hPd|*2V}%4Qhrvo$lyISh2YHyPRS1_bA}==owr2$>X=Ra zgYb49!e4)=Xweyt+h-fK<2R}9DM~q&$KLP1_$=fB&Xr&H@rv9Y(5v>DGmr5Jo8et{ zgc|B?`_R}G7|b)9!gx^kNZC-+KSIbC70{-8vgBfhCJSCGmjsobpjApO9JSe}W@8QrupzuAwx7-nZl?{7=ms%DNI}ycC)^R}kzi?&MH~{G zsgA_w74HE-+Cu)D4wzgPW;($Frp7~R|FGe>BN(m)aGPBYfPhw0Dm!^Ip41s|R3_@9 z$fl5Id1@xEa7)G1Hl>hCNPN&|&(Mi+>rgx_mlM?QS48wR1C&POzeE!oz(73(x4%Qw zq$Y`8Vx6Jm88>H_=_g!-HLr17?#~vU*a^){2sFFdas#IS*VYT$#yBeZV<)!rHlqs2 zwdS-Ov`jQeal%y$HHO5T4$$-I*SYMAfBUPLw8zO>HZwE2_du~6`Iry}RkI`3%tueP zZ4kjZtG&8;TI%^!HLRKZX#5E_69oaS3@cYW#U*XA_!`u_bc%>CFOq&((Seo?P;Jll z#)i>_L7ZGpX^ZzQR9rB$$=Or%aOO+^H$ce0-;~`msb?crLbtH@=qm;@E-Qso9ngxrGFFXEPVefN^dB&hz?f%`EYS zyPex^GcpTkKQ#7z@*QNEc2JHj*ej8>#!Jm6>DIEhpf>5Gi6d7Pu}RV$@uEa7;7Yk; zxn@&bee(R1V9lNUX94ou9I@36#X4;=at;_U%aV2y$dpWJ3uYQ34%k~BlO&zTp)4py zZ@rk_h@xEzir4;%lTkC!Vpt|oB`f?!4i2YvaT+2TT0{Ax99xF7zNO*D6k0VbowQp( zsrT)dw5kB~{X|=5k1MIKKqN+d2H=)ucHr#c+x14W_T!ue*$6c5w_~C>5lV!f!)bTJ|Z5kC}=~ukosQG^%`*rf`D^EhcurkrZtb z7fqnmV`U#xAv#3`yA%!~)rZoE^m^!;N`8V+w7?{+Zc9?RPtuCpE57XzJ+4Kr;?rC4 zYxH-wh4G5(cN6_G90eXYKl2Tvl3G5U+IIg`ofgftsp+3e+LrR6p3}J)vMlh6pLf?g zRhQ&_RFWro=t}jXD#!1fw@-MTk$$Hu&*ec~BcM<_D?TM{T9md;Qprx`qAo8>_04WxNxMm*?Q6l2McOF6+<|+z+h!5) zd)j6O_x+@gFxYF7*QfmI18nFDl}kO9OC30_vF)*$_;erm%-G~Ehjf0dZV!|j4Ec(9 zSgrib)K`OVyuFCtfrgI|a&H1hlKc>0Hx1?Qiy?g8|glSCWW zJ0l}zsi~o;$x2xXse(x@E3dhOp=QFk<`O>l+w#@?z$`D-JTRRbXOU zEEjD!r*m$(cc(Y-`}@AZZ^M5bn2D`?58L@GwNiTDn08=}fh^gTASx!TChSAhZPeA~ zGr^QaZ<$E3(znuKjK!S1{Vp$NP*e*q7k-&#qq{EtcQ36^ohL&dYf; z6Ui_mt*TYpJ2!r(oN_G6xe!;Uk1EO%E0}G+X&uK`ob-cZE&k_Z!!)!k6`SqdVA<<@ zt{WW~EaTLg2t`=h7?G790=sMr3-h_VC~GH}LLFJ`Wl2&`2w;=;xvkydF~mm!?k%K^ zYL$xg*z%JRcN%4j%Y`_cCY^~lf+`*|A7uz3;Y7mPj1Ts+@ke2dTL#qLY71rUj~G=T z@OjGK#l3R#S1^dIRnd8E&!L zrl=AsmGqrtC{2_b{oW2T&8tev#EyMHS!9i@4D3ztm3z(8KOSoQ8Vi4plB*;a+Ww!f zaQ^1>gZ|&s4|SPo9Slz%R^>XS36RYX6Xcvz-p-#0LST}6z0u!ktSf!-_%K`tbU!B1 zvfa?8Lw*7$5$=WAs{J_y_gvvJgdwaHTe>-G9sh>+{ra$8CIe(ykih44nLm;+SIA)l z0loq>G7{`t%uXVCxtwUZC?&d4M70|8VRbzWiMYDUx-w?_<~;6FbDQpA(x0;ri2NbU z014pe7qwAn^h4KNKA`F#{bgSO4OO92INx}U24t=Ak_IYP)1j^WG_lA`O_P38DaRMn zE}KPKCYAbPG$WDfY*NzAw~LV7u{=;uAIaUis?6?; zJc@;U->(7Bsw}6Rl1HvE7-K?uobkGRP8R*rXYa|W<^^)+51hYPTBI>~XyG!!=f#7M-h*TRGC5-`Lb$h*Xkn!cA~Ijb8zAfT{r z7^ee#0kjyRxr_@puaAv*_{EG6=rB5X(hzRV1uh}A6qMZ(JFqjDXFKl)llQB{O>m4J zQyJI-zoEKyis+#2W{2;g7=ozy6^mY{q`u*mMcZLCld^pA@-|=~Pk?^49;7M4)DG zj;-5z!S+_oEPtpsK&y5zhN?Nh((0fq#??6Nd)_vE0dfCEmhoYFzt|{HQ|=2^*?ujS zMlsA6i&K;IlbNEEvX9417Ny3v*`2*J{N4mK{|FD{Ezv*HR4m=0G*I|eitf7XSNsWC zO}$~P{!3rG*6#5cVY?>Q=8oUYvMQcCWLFlkcDtfbHbYu{2Oc^@+vcb&1L7h$!#mS%)RQH6;%RO|lxg9& zvlPo-F7KZX&Mn}m)zx$HIG3#KV{nhz)Y}Zri1nmM#|F~4ObulvSJbzfc?5{!SF=!d z!H0~CD7&H#af;r9z{MgX_ny%>EHCg*^%h#|C&b8j)EOCqLP}K@*0;1{1_8I!IW>wg zx=g>z+_=-IP<^?~qn*aPgunUfCkM0Bk{`D1&)|f`W6ZH>pF5ZzsfC3x;T~Nh+EmlS zut2MW5B3x_Gl)Je}SY1;oc;FHja7F_ZlK45=OkuuMZa15)61bD1K7-xe~B z#Zvpg@G-FjhY|^zOcKk}zM9qOob7Ogi)F*^xI#j|2;KrQe57MJd-X?f{2Bs(4vwUU z(AF9d0Ko3=RIj1`cl8=68B{(*k5=vKh~m)KWS?y}I*_ZvT@3`XC^2F{;1OZ_R~N?2 zyC+)r_X$qgr$UL6Z^0xbAB6f2KtOdH%~NxVn^s#~m1bAke|SDYty2T}XXx;C2n3~OKiZwP$?Ser^M3+N@@9- zA)qf}Zpu+1+AyZ;YGB?kBbg7FA)cDR*WTtOXPZ{Q z7v0@gMOl+=q_p)yNnl`1q<+9jN87WjiZu4`DSIcQ|sDcfq z=CwO%-1Xe$T)W|2a~!)}&W}8TsQAP))SYq0OHtO5@y(k32jQr8?p+F#SHccH1?yAX zj+YYC#(SP02(P}`yv`7}Qza-6G}y2*P#Va~^0WO)tU@5vHor|QgB8CZ*LGRsPVt%F zKO_+ojZ!fyN>4F9dNEjSDN(KIT`W*%n_yaEJ(TG8djr*lw(q$cBb|+eiK2>CdmNSedNmUoWC; z61yrMU9NeXN;#ZL8m^M51f5nsm9RHV!6Q?Qrj|VcaZyJ8wOS!C?SaA}rYtKk6}nWZ zU|4#ELJu{{;zPb{B$qGr)exgWf0`roZY32`^a($wkRex|tVSj{K|41hOi#C*lNFKZ z#Byd7p2h3$di31FWN?0)t~2E#o5NSmP7T$a=mvypLS)zvQ;6`ro0EPSE{NigfhvOC zNZeR!qGUKY#gW1uTbtZy5zhd>nX&14iMfTwv}0Wga|$PoNGmOJsl`{}$X-xg zBoZf^x2QoG(iO@h7Nql7&OjZM*ZZTvtMJ^5JBP0?g0jhvZXlLCCO@_0Lt31l0BE(% zO1_|+L=d}P^5uGDjzw5&m-f5Y33+7BFIv~AL|zoJv{$YqD29<2^5TSubceaIb-Fdi zY!_pvNY|WB3DgeTvryxwK&eY=oMH!Zp~z1HU7@P`bD7~7{dp_X^(82CDxIxTkGs&n zxLrjhBA1TVRZ4)l(v7mQel%vsyP>amOXjIC&UA_LDj?MJh>)|hxKVF^98uz}g4EW@fBq2FDv8bC_8f$MB4 z_{Ydz^w*TM++DJB2I!g8NKgluFiX83MjH>3b;E`r?**Klkaysw(?&-#f`k`p%oY+| z!G0{dZcP@I&$`UeiWY4ce;!86a2ezA;>X$%mcJAqY43~+_h`n6RY%uA4sLOy8#u z!$5N)%_wR58q_uy2SgY8DV=>*7Vu{=y|3*J_7bf%hBloYv}vT@w?o1iw*N1UV~ zbc1{8AWcK?_ks-q^DleyizC;ilZKaQV?tP((kjOG#G1WV3(J&q==Y;1#}k#~6>;ceninUi1k4zX55Q<&Y+9=^jhiDjlRH~Cu< zk0gEOG_%Ln?A+dn;ugHBTlP9RhPQ4cetj@qP|U1}dF zyq3eso{7+lojniy{LX%DYH#ThH~a*7wwN?FmZ>S_gdjCasfpl1SJf+_O+@u}LB2xx zCWL*Nbd=?Ln=tQZeACN!8>-B~#!{-urF&Q3ko@5m*1AdkkLZ2P8pM>wt7TAUPQUE+ zUjlR|zwUfY9mq@CjgpRx)(8~IwF$QpB;l9}z)nW+O^5K3b$p}<$0Kx{!L=_I=G-SG zhb2LZ^qcL*y4w1Td77Vr&@|8 z&5&ue(fd7oS8+%ajdnO`HUdHQCHgrcLQ~tATqSUxEL;N__oK!$jzb;3$?@t(xXkTt zS%tK+8{_n{Yu$FYGXKzFn_S2|ti+1V>BA46b+%`j*U^c^ut-+Z$o-gxw;6@_?UgBE zry%-`DT`k=x0&w-TwhCo=m?R@o@V%J_6<}&&c5)*YogSe+!7usx`(6y?DOzAFPRP? z^D2E!YtWiC||Xhs+t{>h-lu*mBL za2Ed=75jTVoR1;c2Px{wD{xm*CrVcqC%}*VhI2!XxAruJJBH=Hj0M{rB^O$WHh$ao ziX*rlo3*bXmC};Wx#7GuMCsZ;Zt-`i5nL5g8??mb?g!rCE9_!_f!`@I58F!U=(f}m zoU^UO_6<&vkm>Hc+|&8caj}aLc4yATBf-csfR}HZ*~uQsjy;=%69Grkhn?WHWrx+& zg9HyT(#d!f=c>_*1dmh*6J)}5T0)Od1MOw91IvuuH*@ulHpxa{5SuvV2xfrZM7-B{ z*-$&~eMu!Np-TQN+DUM_uTU$wfElx&ue^5hZlw>#F!IdSMwH2Yb-$W(jTG8<4o*gj zhyyBgfp=r&gb~$QCRE;GRz4{%p&ybA5rkLfF|pZ3fF+tf`*F}Wi6m4r-G`rxs3gMM z?3KVO>rUQ}w0s*w=#lKEUp1#^G3^dtwxy2QqqCtdwr=k4kkhB1T1YVTtkR2Nt&DBj zx~tb-33rh*1kZ)@c6Q?6nUNynfnCb^dDkN%@7Ia%^=7n!4|Vn6Zc4h5=M}bB3cGGP zKdlu$xiC2D96HfioY9fPLYdr^KEgU~ICqd#84Czu`L^pl6%rjlgs)BlI@42YHGVLy zAB|r9-NYvR8;Y*woARNzhKO8fTfWR)3#0?nuSqKH=?at2j@w5li!mGo-gW0ozy2zx zY}CPMZm%DfuvxMl>&rIWB&WBsN4JfqKVTvFjh;%qPa;5gC9`3bm^rd)6NK-}e#V2E3CVN` zJa3D!b@;`)SfYJP7k6|81CkiY7E;24y5lTcc;C_5~sknF*(9yK$tXhEFupyaZ( z2r>6`N>BKt>~!6yXLq;8Tt7iI{(3aCnVw|$s&{0>1E)VXI8JbyQId5VJg@l;GmZA- zj;6v|lX>pL)Cj@D*P~VXYwuKa@=a@!1YQuYx2qo?9L!wp-^#A0HbGZ-(tU|G@byJY(j#C(SZ0EZO!HllE^Tdo14f1SZSw%wFO zVL909Joe1Oy^*gL|8X(TpF)jH%qdDd&S)-o_(;PO z`|%rMU5fQC-l%C@rL@rOW8&h-C{u#Pt2{=cv6Vz!-DIqM3nI4ZSDoxb@#^vX4=PJk zw6FJf{q(K@5f_V)vL{lTdqfe@gUxi)hQ*FMm-=%osPOBm<b;tl$k`eEBO)*ml(Okwc}bX`V}g zL!OGqB3H`p1a{{5MVdtl$Nep(6H^?TWMItdawj!Qeh$>StkM--h6xCyC*3$I>0~iI z?&+z!L--w_p6r9U^zE@}jbN&KrS=TX4@A=Y{e{x;{pjo(>~6BVweUKXPFl$ME-fti zbCJ_GnPbzDjE-B9Fk@&?2bvILpemWw)~8*Bhh3ZGy$T^GosvcyahrE#$oU+fh_#Hw zkl}X1C__z_HV_OZZObhX#0|W~PXR@(oYug%Uln7GCG|{@hq=0b91$%)h6!|PEkU?h zM^N=BjBSuhY4u+aCF4?ym7KA`l#KCXEBqXxP7j5!jkS2zRN)Yl7Z=2)SDHA`1XddZ z@XAHWBo}cgUrn5($&WCcbTx{lCis1cb6whaG|3w=O&lnOv~K(IA+9GuGTLctSb2aX zsE{o+Vy1{xly-YM*JMV8@gP@8tORL9A~knER|~Ov#O6)NHTrAmhe`0R1i`>zxnuhv z2fRfxPfNy!2X3mb*EHAX&C%mL9KNmgNN#(WN7>;=vD8PA=oKQ4m8{oQ_`>rjv|V6a zH+<=pY3)bvc|Co&mwEh&euQbp`K~B_5-C6hV?v9RXTO%EzMdjH$|rUX18&-wcB15L zu{R-&Prqq-zjRK>-8XJ@#(>*F`3767cBI1$xOLu?`^HLjXG=-~X0_^a!(v#-x!;y} zwRSo1hDv7BW4QTt;DX-Mp@PbvZEzFAEv4#-=&<8XJ{Q^NQ!Cc2})enlIqpQyF=}#aF*&k6mFreS1fv^ zyZeJ6wO}0%!U|82#*5qU8){4L`Y~9dL!zBwu#Zz%#pB|-dF=9Dd%WeE(iz^9NXa5E z{XHFas=hqRT|9v&BHO3$0;*AO>^5{q_{LsK3tM_-IMiYFzQ^Sy{zewB)YE)Fp))kj zEI)k;>ziyE1Di)f9N*+=+2RqE^+PD+DzK2Fdz8dzn^a^%XmzOQfau}f!qcFRSC|;f z0FM_PsPOum%toBJ z$#o6VR=^yde9~KjOi>EUj~aEQXs%1TFsgLvxN!Tb`lBQ7OoU~!{qxL)lVt4!+BB7y z@AL(NKu@e{f_(ZnB+uSqnXUHxb2EV>h+FXX4I_Bd`A5qgv{W2fNe=pS@&WcB{yxtn zpC_)@p*~N^jwI(*Pu!|et;ziQ-vF*HLwMk=$|6p@uI#~V{C$l5NY~DGxJctP8i|7T zqcU5KCK0Z3wy;*wj0wKXj2i~$KW=^bzic_Ymis~W#S`- zM>5lHhh!U7x;uO+g~NrY^pT8g+J|fb4nSYkkL=))@i2VFxCD;v66}{b!|edGd(MZ| z^>44TAKxX&`&5O4O>JwaffVchP)~HF5y4LjZh87g0`u2|)SpvTdCNEs!QcRZ6a4?x z_wt_!Ol4hj2SZ^KbNhd1s%n)r6_$iixhb(>Bluw(WSeWCMn%G58>B=R*WKmX4(6hm z^5R&;6c!4EJ4M5&P%h?1E)y z%m{pi^nlnY5O89aXV}Dw!CzRZ6mVA3YDt$mlme{OSfaoh3(+c>7Nu1n<(93s#Kd6OU+0u=P}EebRpZ1Mx@n_SJ`)B}y@@3a%seHHUSSpw zXw+weMVG%5`5uv8xTnl+_w#N-*0@mVs{qR#QG)r}b!|=1(JX>k6jPe*2Wg~RANiL$ z*x}qhhkVIUtZ8<$m3hNG25fyNeW3S(fw&nlbGrBn%mgl-2!P~qXMjO3d#JNRh!mzw zYf)pWp4&@lNsVC{&do!)T#@iLnjogy@kpvnbo2voV?_DVboOmWe&N%aU#ifBL|>g5 z=Pp<35g%q-lFwnHGCTH+LDWOg0;<#YRZrB0de++9%9R0~;I5O4zlr+nYx@lZ_MK$n z6NK(@s}t|DZ*Ww5yq|=cP1T3{wo;WG9Et^-O>q-Oq=lkC33az_%`XLP@vy%Eu^1OW z(>|{INUGA#3V13m8 zU`zT6a*uPwOHXGRGeqDrE;D*^K6kb=T`o%}QLjGnsJj_;whxpjDks_ozjf0@t@LbG z%@twj^uFko(3PydzshL!&AInm?iB**D!4IB1fv>()JYaLd{;Ij&K|MaPWIi6)C4va z(Oy%<%-ap5GdXX>9#-K%j*P! zUjw=}c^mK~_vCv{Z2K7h=-w5d4qeW|y`0^8O5q90iUC@<& z54$Z7ky?L9!Sl)*d?Izt@mmEzv3ejQ%k%S#i$?jCL`6j*BZDCCA)gU(5gD8uRmk|T z5P%DUfsa6esAUU6V*o(|azE+vVP8x#cpf{~n9YFp2Z`+-vLSUOd^_(Y(w4mF&Do`2 zMuxk;+Y8_{&)@$%n+aJ#gRh%!HJFy3+h`5*w4C{v4y!|hleu;W_DEwtVWkZ?Ttxbw z#+#%4-PQTwrd1!!>cR}gjZH~}>eHil`EG+jq{Q{lU!kD|wMO4nT~g7?cMV{Ss!x9a zcCyN?o_Z$@q{Vk~`bVlCJt%L**LTjL!sU{OCthuCovc3nV}I|jx3fRr=vFK85mcc8 z0O5B4faL!rC$Dd1ZK!Cgt8ZpvX(aH+Jx;*Nz);B9+Sbs{&cw>{za{HO6ZhQ|`qBE< zLV;$(=+%pF-yVgkgnhsib*k?F`8owG7 z6r)TNF%*TEH=^kEQa2t__m}!lzSk~`Q%=I6#Oh5dC*7~SCNw{eCE4vbjV&`~BmB5O zp#~J6v7!UlC28JlmB2W zG#+*BoQ6AKcXUiP6ZIn3bY_x0v`i-rwT;eGg%;pLvp;d zlCNA`!yA0@-pM1Ondpsk(7WY|bY_KhadPV>hl%py3ZvF@letij_4)V&GXTQV@djmt zPjNrKK@si!462gByBX@%N<`?0;56$7VnWq?G_w&p<_u4G~4!p%X8v2~A&7>AVb-I)oiHVo}>tH^Zl}ZuYvtbRUCBsRdmd zM19E&EZ~+*RdsbS-E@9Gk{oDIp1-MXQ$0GOUoOL!>%UN-QM9k2&!!KIl+x_2M;i%o zwIgFZm2#MpSnu&|mE>`y7SLowovL51?v!jGp)U4KcKia9#AMe|0orYwPuE@?7(q5` zs-m4_;~{D>MlK<@^}d*Z0X%;IlA?Ry)lh#E#sur(DqvX5?>N9jYp60NN5nbDI{Hlt zC8-HnBI4xeT)d+3{xQ@Zxf)Ad3UPaIEUd?St4P>-Yq0Lp@Q~B0`Fb%lkA`{LL0Cc> zqWsngwXjK3_^W_c*EH?07CqSesO>CEQ!@U_fLztQ4L8+Jt1}E1W&XQ+suQe$GcY*P z^PE1ZZBsa2L3;E}Yfa0I-7Fb0lUpPn9h*$)@GG_{KqsEhG zNL)p_yzs`x{we~m+%@#Dnh+-t`6D60kkELi&+Hz2;)j`xZ8BoN2rsBs33St*Q=+xc zonb!!=D8Gp@D3>*L6jlt@vFY$q5@kAJ(=YbcYjCfxEC!*h^3dIovPW#(Q=s# z#Wp!TH@Cd>uK$aRY+{l-vV?m>4z+qig2Hfi?^i3Aovt`@8hvMP2F`# z*oZQ}I~?5xZ}&WT4>lG!PXV0BZwl(pGuNa(%86Ns2qf`}!qsA@kplv?Tt!0(`!+66 z)*@#4Cuk%)>hTQlqS`56ZCD+`dc0M_m8M|(x4EzHRKth?s6SMvoYT7SDRI=rZxuV6 z_w3@9KiQ@spvVptE3l05VV0{Y0=89YUKMqOBwj#@%dB(Pf3=V#!}U8}3<7yG3<90a zUkTEqK&&j@Ocg$v8JAtr+1pl4Jj9uJhIbS>MIe$r)uWrn>{$P(9N(?HO=bOtO^ZKs zNo!r>LpHEsmal*`ZKz|#D=u#Jz4QWNT>?VpXLH0xFz13kt&=(gLMwn-7WHtisFdaX zF&2UATE*T>^0KW{8LE;{DCh3AgLES>x53*}YDW>F=j8aZ>J`%E`6IynVufT^gV0D; zvK3hzXnSIqYD7L~yNVrj3|Lxn;T8B~D8D+^jAAAWfp*6haF0pQd0_4{`@vKtRt_%X zro#cXylr4;$4;?%M*sf36Tpbmyo(!rb};QVObWX(|Fh9<8$ zSFQfl-+XUI!AB6k~>GK zf8J{i9mDIBb5A`j-2>v2*1Z++d8$6AwF|vIef7i{mBX4-5CDol$0kL$e z73^@qE$;Sa(3^4K-O`|BfjE!Kx3NN6|{i!A`{wOgVp=dtWF##9UeL-74i**Nwa=eI)+MqMp5Gg=E4}!k8~DcAqQreC+K#Bcfx5;ctw8}!EH(&Ezc*tIoU;Ti)#N{17yg2y*6IAoF+^TQ z?u5xrrt*dCWGE3yJ8CMVvOUK3AT%4 z8xL#J`G5*vWt~F~&1d(C59Jdd6kU%m&}D8E=jVHgmLHcpL3X39xwz0`AC;8g7Sc-AJRPtP@>+u*meir5+6~VR=eo{yM|Q z(EK~-6CJ+r?4IKw^3})tEAUUbsd#4$ovom7V2R!;jo{kdge_y!tJuqPTKbMHNo;YJ zc@J{RlCOw|>VA1-YeEHEx72OZCRXeU@ zof%kWo95S8?=DVLQdA3c5JLl=z-T3HD&lukSuv;*bMORp45j0YN~;_^SKg;2*6uZF~#lH4No`3N9m$Fu7hZxeE5 z5LdqqqKxLdL&@O>3*PQE2xMWDXO8cvmFp0o5mN9PMQ&kredBMMPC!CE87y14@J@;u z*;KAK$H@`Y%2=Umbj^-Yvr4v9r6(NP_JX*1fN~v6RS}Vk}EYiue*1b{R{SC>AWm32SQXTmqPu^jIiV55Eg$5)a}8Msm~l zk?-#YxNNhOx`(Lbv!cnD&lC;9b_XL*GrAEl_T$2ag-+E3Dq>3gs|JzyPG-`Uwm#eA z;lfjB*~#|nQser_%gvU6V{+s^9;nqSo{0ciV-Cvd(zmj@s&tMYy)sG*NBB7Fp4L{y zh0A+Ojs!Vy**>B`FBXv1pSK`Yqa;T-gw_pM3osIm~k}H4tM}9{KjAy0^YNnZ=gsf=-b_9 zv=W~JPGhm}gWD#jEilk;rBk1@-LVI-0wa2g2gUf%h%oLMM=Q%toS}1^s#ntEu-iSC z$Am%HL3_d8*2oNK$z9{uSMu8%vPIHlx8B$xC@V;FGpFv0v9|R1p(jl0;loslpC?r* z^$=J+$ykqnEjiALllpFQ&d-j%I0nbOf1wB7+V9GBG?AoK+Cm3Udm@5`oWMT4bML1; zSyJ%>?fg6^tmS^C?OI*sNw87O=`@)|%#3Wb+pE>;eIckyo5D!g%E1wHmE;2rd)X;E zZ?i^UPY|b&)VEHpH%*Vjbmrg9tIVJ1a*5R!cAB$sn(G#6l0x>&-1GyEbp&9{;<^^A zETqCs46yEEh?bZ z`PXoL4&>H9swenSM63P`U^T!$y$OCVkgaWh&Q@*hO$_be1}mXIy+s5}tdt`r#}eV- zW))^evRzHRZef!;IJNVzQpL>Fi#mxNhU3Xx&eR1~GngX7HKxXuDlY~NTrR6u^vG$( z4cTP^%638898aW;l% zQ`gsm{I^T-KQ+ewQ{&D5-uTz+_&-WZ~b}Tf)M{4KRlz|@&9w**z4+>DeKyr=<1mpDj3@THQt&VGWw5`R|@{J z>k|oox9b`HT4Gigg_iPDhT~5XZ-{?$E%v{hUjAAKaNIt`^s|?AkpKCR56}+ zrM>I3P=@tyo4vk-zx2j@hPT`OmQ&_S@JoxwXRw^Z|0l!8my$1i2A?H)oqpSO@Fo1E z3)?f?)AjFrvb|J!>8katg6i?x9$PQrFCB}X;b@SuUd*l+KsehGiM`Scmy5c%6%QD4GeZr*!_&&K?{t$Z&PUTy?? zRyd6NTie233cOst^DF?&|2vC$Uh-c)HGk%NmHhvGhW@MaUu8Fcx%Jimja=u;oW;wW z%QGIU`R`|7WF;UV|De$Q{Ki580Op(j`0xJ!P)h>@3IG5A2moMuB}=Jbhssci0RY3; z0RSHW0037-LM=&7MlWn>VlQ)Ja%pgMEpugKb#iHRc`j;Ua;&@sR2+%cu8q696WraM z;O@b#ad&qDG!Wd~2@U~*69~cGEw}{D{`>Z-1)r}lpL z`*x!w3l0GX^7;UHQOJV)%LDdhkr!8c%OI^F!KC#2YA_&%udBI(bn1eFfLMTmfMERV zYVvOtq$R{v)fnX^T$T0g@mNrN8n>RSkK>MW#jITIw?q*O-^N*-YxA@tHh!B=Zi8O> z8iS0K1aWCunKm&zdMC~2)z$qI(wOv^76+S*rf7n_uIC2Xuc65vJrjn;j?Ai8d)Wn3 zE{GGcGhrn^VWWs*2wL~+&#JjAsmkrkmd0o0$fsy3W16Gu$SQXY__l9w@!=Sn6zsO- zM}?YD>=zOe&P;dmHlMAmDpx5|3v?1>bN3N>WC%F!Qv(EWj=81M9@{_$v3-@^w^-&+l`+VKSVHm?H@(rX-?iSzpz#M+yX|d z(R1%;ZCV1;p!dVv?)y#uGv7C+V|DOGeurv;rU$aNKn(x<%3J_aDeVwpeLCE)Pm6z-he3m z{NHZE8DM8(Wbg48n_~Y@ zH)ZZ*Z}0Y3A5QYW+@k91>!y;wo~Zs>^DlOy{`c)f%=EXn*gHwvI@tWhh5K(0OwH8U z<+nYmm>SzVnV35L#es4Er#o@CuyirCv2=EE{#$_g?de~f1%dx`Q*Rv{ob25IHlp@+ zE~XxTby4~M(@l%o0Gyp204^5db}mkT#iZ>2t5gYyfuVt^jk>zdGgr z@p<`;>_1NYzqGU0Ezd!Gf$)2Imj1H9zbuw^CZ-;YR;D&Ko`g|+NS(|mkoTDzuBBku z{gQP{oi*5+ng`QuewlM!SkA;&JI|?d`%yj43Q^xk=>fgWa*bR75yb zT3t3T{cphk`7vM5YTuDbPU1_~ZGrr)Wd4tJ-on+y+1`#(+{4Ax&iUn)k7}|CUCb!1 zx3ILF0o|~|MnT>IGH=vqb7j6`Kp2@%DsRlzLc6Tmrj znMWz5Jt$TOT|4#>k!OZ9YAJkS+#w39bJ}6JNaN?N3Rd7pE{W2&v*B;U42`B4`-2<< zvVjT~S6}^@MisqRGxQZX)WXexd8|^==Ik6#hYHDlt7ot{+Qg8Kr1o=Yx0%CI7qN@Z z5OZ{Bb-FiNxE{u)CxFtEJC*sY(`Om$0$pPaK*L-I{{Zs1?+2duYgiGQSQRu0%NiVo z$Ew^KYO#hMnldL%7;o9_Fu9^fkrK9vyt8#hpTyGmxT?i%Qmn}t)@luc7j?@ohZfAg z?d33aK9ABebUDEnt!reO;c!u;+k9L4v?NpcAg_#Yhy3Tusz0%`&iOmj^#4DX)zZ$= zg;DzDk0rpy(#zCoh)HH{NM>YcbeK+IbegW8MjpEM;zL3NE4<7H)dblHR#{qruE0)a z_r}Ki?s$Z`FY);@j39sBKYpOjdiKjR7jO^|*8gq)FE<;bwY`P2tFhN8U_!5n5#$3;-+AbmdIM<(-a73$k^KH^53FD z+J<5$2^US+Bv3c)W--HbU@Kshpue^DO79DULly725RK)sKUhDknFe$bWbO$-L!(=`cJBP=YqRu-;y z$LJ=nm9S)?{E}kaiaR=^iW+FPcj$>Bp1(O1zD4-sOPAb44kbH zt~Zkz{jD9R=DJ3lY<9FU%0yMX&;@a(@BiyDpATVA1PgnpUJHpt=I+=lDF4}0`pYYDD94Xh9 z@^B?6fmjfYRuN0)sdDcj7GV#Q!lE0U5&+CIpD2buAh2BkP(ZK0GXYe-q+;oQjDQ0n%4AKp-1 zx1F#9Rq%jx7p6*kU2l);`JLj`;yGLWy&?0%1;7l`PO z`y|uGBT^nXo?a&){6?CVjNvjmJ6ebSy_R0Z1E36xZH^vY{X?k+wHG!&6s7~OOs z5`3p>nVk;RpElS>A-ohmMQ3ZW+vB2nx*$@d%8JDYI zZsr0WTJ=iP0!YiuScn)))8i5yT4#dSaTqsL(A1;@qKky7@{V~(@zPxy(MAPewPkp& z)k_kKq~dq{Fy}}gt%6}{pu)=`gMf^a zfPg6dm-qBR!mo8RwhEqD)t8P}&zS1jc-nu?ze>nX@=aHU)0 z8{@)3wXirlp#sT?YQc91S8%!ANJuQu<=Y_w!r7mq#nc41@_p2Zd#^ zwSbq#Ul3U+^kff=XSbAf2&*|;<@;Qaug>oFX|wC+gbEz*_3At$w-(BqRceUeD*$A8$z;v+8DUIrM9F+ZsQ?y&pF zcg;PPbev~fJ~>lGdEY=2Z$+?QN1+k)!BgDeKUDhosCGrDJh)Fjt=^v^dq@lFC)e5! z^fhuk6lC603AG1lJe+Lg{Mek1TjYoyj$ZEHt~kwWq{eTqY!}x%}Wg zk|)N_mcG$MLk!xy>++_e$&WpbB{WKq&31CO{=2cg9C>_NNvF19srDBpG}qv`Y!gxz zyqI%lcyrz5Ou0+bEUPR7ay)pW&F~_XiAPp6NJ?2VP8-w{#Ps{fKt{5+nnnZbaVDKo zCgeacOh_=*e0o)1>dy)0Oc0po?=2~+{Z^!~-0}zI+$ks1EJ)3CGgw1MBAY`)TXIQ` z)gTdAS>F>S?Z~-Opo`Z19Hn0MDYDgeY9D8T$G=2t%#tpWw-;>}i7wn^6 zF=kfKD%8RKvHrn$qC0>GWduiSGE+jidsk#I47Zz{gKzX|_CgI!y2~FP!aTtRi)(CX z$E;6*tRX}9paw=8t-y$q18<1Dd8WwcVQ0^@FEY*byYErj|E+e(ouiBbRmE+_ScyckkHZZwaL9E^T}SC0mn%%#3n%Rxot4 zE`<@wWv^23oc7vKoxCrf>^@v~+F{E=lv zdfUS_IEQ(phzg^8`B|)GQiOH7zKm*0(N-p=?;V*8?h>*XZRZYHxmk;X8glg-Nodc| zFE&5$IWF=rCd_>CNR?F5;Oui`tm(6(!7j;&u{7G%Bmq*Kg{0g_xQ4fWRJP)7_z3iI zWWUPJSVZe}KG&^O)6mLDV`e)9tAfHsaW^X-ICpWSP!)~SlQ809>QBYR4Ua)`#E|Y& z*_NWT?UqnAGxH}O@wSq5GBBY|k~{iN+${3wBDQ&|&t`mqcSc`EdcR=g=#B4h@5GMD z#+gyOsjcqOW96wy$hCIu`arxgc}n-Cay>ZOV3Uv#d~NWo8FxV9n_vib?pQ`twMtCd#)piNcZLw}DIoK8I%BHLsxul2}i$=~|ba4a#w zCp?nHq|jv@RiDrwC^Q#b1fN5_omBxZw2V&izBPFyJT@BqrkW8eOhsR~g&O@wOG&Ju zZnQk_QP<8Xp1}vDR(%=&@wkQ86_63kC#z8(ADo+1nEK$|63Z z82a7ZpNERQf3>thAO~ zJz9NjT4YhAwq7RH^rlaI1!uyR2V_jRWRHye^qvu>C>$(J5) zYbdaH$nWq~oqLD8u*}U2g17AQpgNl=Qn1oT^ie3xc#D%vH&~D#*=!lVYQkZ<+@_Hz zhewOuCSj3Ockx>x88=LF-BNJG*SAr4iqi73l6EPl__J_sOZOj=kWI0)K7O*IE^KNN zo~RMzltJrV!BvF2*vpE#9YqbPi=*)|b;Ih&w|9e_&l$z3^(g-oujv%r2QxCJVqRSL zj=+5ON_?|Gox5tV)^zre<|`J#2K`_@YlPC7w~IOZvhhIy(^2r9*1XdAYR^m6B*QYd zLvR`X>7c>Rp54}0xpro~<0Hb9*~W}tX5_q`T{`@IW`j0_BZSMPslrhi**$$f;yiqv z!;UaY=I6VuR1K5jik9k28%B&b@ykQE0Jn}qT~B((%iK)RD&i`FZhd5u4M3< z^TFBIl9m)5#+PFKfBHxsOxD{)`vv-0RkbrE4C{*ois-}vF6HL>l~{Q&#?0)xrKeoF zOQCAD=7%yV+D6#zX>PI63Rkw(oHX-ua=L1LYh(|Sw=5fu$@QQk>jrYUv2*CR3M{9M zz-2(g&M3~}w@NVlJi9tVMlc#r=H&NmXN;+lLa0buI#u&Zq1PP=UF%;^M9LG=ojL=P z5#?Y&r^f4=sVX1<{>ZRKSRu0*8Ko~c;Rf@`3g)BFA^M0?SUBm8W11)cEk9uK6d+5G z=GX6=*xKp{nIllL@j1la+N1Y6AIzGj8E41=c2ts1L^G7qrm!%mK;EJldbtUWD<7U~ z6AxjNH_{MZWqLkTa5TVr3FZw@L04RHkE*JObGjsh500Y+h!x?$(AUDyoZb+ye_Kq@ z-jQ|Uy0^z1Ss&3&Y%V{wCyLq8bIX#@?O8?VmI5zuS$3-uW5O0G4; zCS-u}IQj{&9DK!33Uj;u;n6d#j7}RsV|>3XTTE+Qhw$P8s%>-K5qfQC7vV%O z+lr5{4RUBAhF5GNj1a;FWS>P!GZdyz0gP8{kMXG$-?F||m!1Q^&5kmdCx$?v^dth0JrpI*~bt5q1sNKM-#jFHxL;uZy&&7bEF6{|u zz9;n5p#uA+DEVn<__*Zm_dZOcdVX2>WRz8sAI8|ID^=rr@1+sMkZ!khzsd_D>plq= z9Ii3;G(n1g#?ctQ>9)=BA2kT~HMc;M%?4ZS%-$}pRx?DPtEkf!yn@t^E4h96AaD z_%%TqvYaY!XmR+synfk_%1<&DWoA^ySh)i8Lzk>1LW-`$rpTd`)n9Zj*rl0^W*16- z@B~Na@~zog&b(iM7=<4)KO`R|?xF>&<+lh=s%uUwFym-@56Mt+S;GJD0aDlFgQbt< ze2-J36N2@5L+Q}6+0QpJNh3j%qfNEv+DI3N)CrQ5kvXOup251jk=JW5BwmT#9>LA- ziJoWT+Y_4*SFlSVOl-{P2>s|cvINeZ%0sp3YC>b(8op5+TpUkz&>Mx;_@$B0}tu>Os9wiI3pi>Jyyehk<=U9O=b z-F9cjktN%9x{W(XuK~E_MLy;4TQzhP8kO@7WcSQAJrfV<3ZZ3U>XDSjE4Aeg!$Bei zMupK=sn3oY8qd%7gBXBZvmay8br7qvw99t&Rf4#m^}tmJ8M8crla(N za(x%KHdbx0xEx>YZQjjF3DWNu8I#UEX2b5$71kF))HiFs!;+n8gzX%1IpX_bt?#HE zBAaTR(@m_>3gk&vb5BI@Z&12CTWj1_vzDdvmPA!4=a)|~g)j~UBdb)pC4-f?tgF}^ zg%H*qRX7pq&6$S~5_8pB(@2nuT9#KmWc zeEyEU*IG0DnAPr6ZM^^3KtxZ&y^3{xSLlp$X2W)E4O7TIhD>6Rthv!cvAOdQ({oXG zk?_}i93@W#fSZ`S9^h_{KYV6A4TJGr+zm*aYsIO4YwdTvN5JKb7oM|Q9I?ykA-9;4eK)Ow5HsuHLvp4Ebp;2hrzgoX69 zg#NXLa^l^=XShKL%A7LA_Ppvj{zWdxdDE7Q&lI& zwU(S2A*I$}%df>#P25Ee&NB;9Gz6zrCusI%x-OCt%VyYv*$!HaQ#m)wUz)y^zqEXt zb1=4*NeO+HWi(pO{tVWrGnO^0nEME7aYW|Oy6va*MV!ko1ntWR{yqfHIlg*O!a;~) z%~X1?`q?ekb`i~oYMmx#+NF8AFE`XWBdx*6*I`DJTNp>?3@Ie7X|oMZm*5B=dik!6 zIQbWGkK_;TD*aMp*GX2sC>4kdj)A9w7Z?%*j~64FR@ngXEh;T*X~! z-Mds3hbn31s)stX<)E=mc-qEj-Uuc&Hp9!71GLb~ zCxW3PVZY~+(8N|lWSh4p996gihwIAszp|dj-*h0KNa89kB6_TcSR`Z*&9Rxc`|bEV zpB^G~u(9h+I}UZRtMf1QmIZM@?;{;Cdyr*w+S+Vc_Euw1NbFB=%Qee%+J)P(41bB0 zF=N6_)=9y%Az+*4Yhl8{A}j6odupH>Mm_!*3=tRY=`|che+H{L1~tig(<%cyCEZ;A zelklcgPZ{FQi2wL=!AU){X^%6EKEl`6HX)sjmK<1VujQ%jCze=8H(X{BU>MA%GZ9l zy4o3?$|URhmHYB)t&oq}{?)sll?LaAa|{tepyx4x-3w@CnI^`W#f-@(x<7AeGk12vIK>76w464!KU z@PeYKu&hSk?eYR+G7=Tl5)}_h=#X_lFWI1i)$MQs+gYPnBa4NeMz@;BklH5bJDW7^ zx@Is~E8EkOFvO&1yQrwsDP1tlZW%4pgA*XD){|U|io^GuquVReshf=)pOekrw>xq; zgvi#JU99~Gv0=#ybMeErFCB@FE}6@|BC*+-@i<+=^V^9{b2O)M5zWa$I0oWyh=QR; z1>|BQjrE}8l5iI4nxWtFJbQy{K~1S*YczWDkja5ifc_o%F`oDkx`(US>7Hp9%^m|) zDgM|z)e>eGRrHfN+83O(G-u95oI9@-TA-IE&CjAwpB5AxDd=w*% zkCSY?mFO<8%_>7VI26v>PLmO-Qd1POi$w;jm{0UuI!8D6#QNqiK9h@+!na(#qlON- za{sWQxVLhpyOlblz{8uON{t~vINLIpKr7zO}lHrXZ=O|vbat-x?a2*%vb4Z|G@~!-5fg#un_D5e{jfN%% zGt*^O3a4+AUO#x?AQ=hihqA2N*gp}E@bY|(ZONECi37&qR=n49iVT3HsOD&9cAU>{ z9s8!A;BEvRtMkJgL;YiOZj(Y%jvfbXTA?jD8zL)Kc><;>=101Wl8pPppN6=wJ}1xh zReJpf^JkG(GiP`vcz!Y{2@S^3?v{OF-5AKTHk8J99_eIL)~;x-5PJdd@oSBazL)h7 za}%aO!7T~8IV%-EaJuy-MMTzsg&+7gcn)|2ez0?eo0NhJ7~u6yypIY<@BhyBNi(w@>~$vb4{K zG4fuaTERl(y@pcN)~FxXF4DE+NUOGBjnU4iu$lIQsRrjumFcm=H3-?C3kB322ib2^ zEp+w4G@=g%p%WV&viOz;Zb;ca^fc7>+o4DE^PVMLFL^0s9q{np3{w&E0j##z-9;PC zlekkVDN%Vx#B~L8Jd7jjY$qOV?LjF%nLrMT@cM3zMK@XJGdE8Jy^`&YNKV&g z8Al_Z2&Gw%@~@es&iM0|?@Qz$2%yV7e``r_nx7EOC zEo(Kh^ccf#eo~tnI9-@x9)+7e@MlF$*%W@D`+4xAv7no!9|f6~P-CVn;wvZQr-Xbt zJm?f2BqtlAW?*BSx@HweHG+gDYgB+#8NFoq5vf@4kv@D2RMWkOq&Vj_ee5a~J1nd0 zBa3uxj9?ln)6xTe`;|Bu9okQa2q>S5;Wjn0-qwCPXKym+34}k(x?Kw$!NF zAFIPUdj-RSqYSQo;peQ!O!QGl8yk#_hk0MCF}`Zecm057MQX$e-DANG)kBc$?n}JS zk#i}NE{^Qt{!A+FNz+0$(_ap-0{v7a=+k*wEO#&_b(>n3Gc^<=ecKGiUB=2a1(F&L zqW|&Qze6h$qrTszgj+Y+QHrDrpwP4KK)tZ7B_}w>oq{yZGkD@Q`4}WWHwSm7$N>3$ zphZ(@C*tR|fUYu$k%nr6+w;E##YtkTPqJVjAc#=^eRTPIE0nf#F?BLFef|2zKNORi zj{Oufj(}aO867UeCM!-ODCB}2{pJ~zh{zNt5Ka^$YKNVpa?VhrL8pPs4-H-J4bctg z19kUK;S42|l&Cz$ed~PRQ?%m4=1$`=$dR!p9D37kx-8_DV{-sq(@(%k7YFhju1pX! zBfcHO+*tEr309Y`FCRhhTY^Ei+}p)eNlqzeWZbg`GC9w*bxXZHx9Sm74Jq{STC;aEO?p)_1X;@V?lyU4sw7LCphMVJY9}sDp^}|Jtxtqs&>t@dM`U18W z>m-fpRRoBR%1ub^1;R<~hJQwbW6GeJ<>lSg^`<*;3=`>j3@D3{R}50COZ2OW1}#1mfNLal3|l0O10huHBVKq{dOo@ z>7~1(QHGd1NI17O6Ccetn`IijHi}X}al92;d-`AnsL*0piqR zQk`EKC*8M`(Z__;0I}dJ!&V}}3Ln*S3h?u_Zu_1&h#tLQaEKm|V-O<1#0csgtcE7l2K)cQTkd35;p zFmq`1SV+hp)Z={;Y~*^nw;t1*7<|a3ZU$s{B0KMbFV%( zOb~bCq40SOON>pdTqWz4tz_yMT-OkA^3X75h|yD$bhqb`Q#vrbZda?TpdIV#4Rf9i z$Bf_jtPCR3`z*Ce*&)|%Z*|K)U38WC9fZokl3a5MrDfCp%5p4oYR=#-`@5WU-LJ?Q zELvuGv~ZitO7O+VQ9RfzK?W)D`IsuJ5At0mfNuS!OyxhmbehW`P}_ih)mEHA=m_7@Ng}uBx(e6mw}Ox|$!W7f&Qq zU5q#y0U^PX4=E<7+UaVQJ>BdQg_-{`CzFLfs}B+01!enfkH`zTBZ%WtSqh=1Kq6vQ zcJIJK*U8W9Sk@7thV>ssO{`Yq9@FXRCl>)7*?D4TN!`t1mbwsV10=Z1DGr`6KAF26 z9*6j>Lb3N}$d(Kww|nU%G_od*R#+OA!vLi7R1(Wl&Fv58Q{c!)Y3DsIzKKLqpD{)7qw;_ZbYr z)H1=zG^j6c__3frpz=^Tl-2Pedl|s&8n=Oj{jLhf%WX?gNB`rrnL=}53!gcQQYW3V z0;wjdpIIirPQNU^ez1*W{WiLK(C(uc+mL%AHMF2Rtqep)vb}eYX)_=xF>yxTn#BoT zzayz@C{PXWyy`^EA!psUK`+<8$msanHIcJLIEf%dL?(#<6MwssmcjPVk8KA|@)0gJ z?gQ!~_fdwrQz8gh{4wP;b636!M+uySRBSAHmMXf(DJ5e_hsZbLn*9+8(Otg&W}*df zd#Gks%IX$zT6xG5U)_JOj3Cx9Z}L=KglB|_gEz`Aee*?s|6si?35+s(Zcov>4ueYwj1)n* z^^JS76NgyB^jkwOB81s-WfMk=R-3gcdwZ3mJ@9F{K|BR2n^pHQ=X!{_HitDcFGdNE z9Id;xwLTiOmi)(fe7dMCX|bXv^dmI0)b~Ei24O#{@86aj5b3#8L?!L&fSPu1^hNm_ zLq=l&Bd~brYT%rq3GB!4j!=#tW+k|%cBjD|uy*j~KcP%v;*9!=^l6Z<*nG>itcW%t z)N?KLLK+v2<4)rp9^<~rGp*@cH!_2m(xeBT1Jt3g`+FTxQyh4gL?SK1{FX7=lBCud zv)AltwJ{)fd`D{P@fo~%Z3JMrUZ}5qW9cyVa$SCl1lngc;)z4 z>S^%%UK+&|QRTa51fza+T^;+Zk|tzAfG2$<2gIRW;2arvR!|+G>iw?1h5D0YS?^RG z5f~8qxr?xCQhKsl_}Yl5&ayvIrfL8buXJhpv^JH1tZ_|)2aLK7uUOF) z(OBc+?>&c;u2y@T?T~5wAQm-ntVbSJ+${%-_jf68ssn!P} zLCj&I-;vl?qdZN5rW*%i8wmk}LkLWGGL1n3jIAQ+QV?xTtCTd%aaaP@9@BH3L=cpT zl|>843)-dM-d1Ooxk{gle=$#TOH~ZUxS|376bXNEOrhqE<78ddgZlxIg4l`| zmiSWYrEe&0BKdGMdFKGd3O#sW=Tl0_O(qCvr<{EdPDHbF|3A6=3& zq9c7^Shzop=`!J)WG5(6t)z7=;g7~HFA{xjHlvvK$XHU72;La+ZR^n5 zW54ju8{58V!4~ZFB>&8^Q})Wxfc4X7hrVi$boSw?%T#DvJl2o#)=mKuKRsGR*e-5U zDUY?{XQjp_H7ghh-p^nm$CdhuUpb>`7Jdx^T@(-Bl9hHs-Ekn2B8MK>Z3~DH?T`#d zlACRRbB@@o=>NtQja(=Sm(&#YyZFckYZQ;}Hv%)Mbh0ss$v(3EEFaM86Oik3)vus` z=GSaqJLXlSKmE-jo&67yZu?SvQTm=`Ws!dZ;052@1Z&qb^yi%+85_a80Bnrd-mX9A zw%5FPU`OXeCr^%N@cOOT-tVnwc(6>&Z9|8n!$%Kx=5B8O&$qYm9jX=b#%ObT&SWtJ zcH55djRN$ZO`GeCq5za$ir4CE`)w1eOT<^`kHXvcT6lq+I8HCarDfE*>FaPg_K)F` z{GY>R*RSCc`gOSEM!+l8PDtK=9WJA9<)i>PY;}X1@3V2=n#Pc{mTG5$eS#lYy%6GP z_9Nyrt{t9vhtcbuhowM__nFZM>MJG1T!y0s0@dYAs=BtTJ5S#RN`3DE`RBPS75sWz@W))3fajvVgQU)de+q7BkWcwv{F zdW!Y8+RL(?+LBOGAWO&_jb)o2r7YWJwNMZ>xF?N}R!g^^L-f2y)x_ z?7Vg*JwdSYDgr?M9bww1fo`yRB=90-@>{D7-41$2(D1F;$d#ccRtQ097sIkUM0BCp z$1~&sN^+qrHgeOj1GAA$yh|;5LsVyShRJ^NS905QiI7iVke#Re|BYNxJFZ%&7m+>+ z{kOUzf2f9czoyG4;H+I~`5S7u70mj?p^Znd{3RwQmV6xIltWFZ*(@_R?H?l=&ry`` zCD1oM3k`ahHDVT~23RJHra50_+MBg!9UqJttTuowb^DM-lUf5A*|vtqv)-b6(O;Ow zio`hHu{w;~FYQ|{F<+TMH99PI62iMPEE#hvyFHG^?ib5oHBat4>wfCz7wtPJ({UqQ zMTCpq_~d(JN#)1cVVFVMBfvPQ%TjmHuc!H<(XA9}-ru>8=BtxZD=El)3akE{1F%vr zd;fVbhyyh^+u?&>sW2PWMI!ajSZ@l7ix%{jw|-jf&Tuk=XDy#`TBicH2RFqTlD-jO z1dfX$5Z7LUJ#t~zLw^!5n?nAUTtOsV%2VUG}zD8}DrMVxUM)nrUwa9BFDE7eSCS1)e zMvjIf-Y3r1(TNr3#(Q2P>NRcpzq4t?Y1G^T<)~XZAeyXzeaDD}SyUe>ijV;9g=nu4 zyxk7tDwSG=wK8NCKu$PRwP51vW>EQK%x<4l4c-}{@o6WD?Sh~X0eiP#8X_Jseq=;q z9z<)>%|v=7j$&qtsxt8Da5wj0iamqs8RdJl?lqKVl{q+K^9mxl)8POjxwC=8uBw_d zuoq(nM2B0}SSNTY(7BE?KkaRCQ9wkK+Bd&IF|~=T`$*uiRNEa`L{YeWA=gAxxZDr` zk2;?GGweNGw9iuge2fJPAFz}@x#I_QIl4war6cB?gW!V5dy6pt5sbE+K|>V96Xuy4 z{6F7uSD>Ieo8;gesT0_{}crBl2q`cMP)sd2G} zuw|5;Z3@m^FrE@E3R@85G%fP^AX!+9=|!iHKJ?i?O^vR9Iu-({Ffs)*ZrVsLhum_k z4A^g42Yl<2M{dL^dXYLSg9#zFFL7vs2U?y%oniN_pe}EWEN$da`;*pNo<8-7xb>D~5iVIq<)mIY|F* z=7^C*)7k?k9TOTdEI}J2gR^JFU(FmRWA-%!RT<7E-;eKNtXq!(Y2Oy(U;0GhLn@M^ zhO6Ayp%}wxB`)+UlM>Os1hNeE&B8(eNBu4n=gbr-(55Hqb~|nIaU3;-;A7wg+uJkMj8`KvYlfb2D!Yr|MWaviTlT(abZPq?_@;>arj(o;#6NQjvTMDl z_QEah-^(rgKe)9+XF(BAn6$xfAjh!CTY`g-nw+f6wSPmdR{xP&NleLe#YS$!tcGk6 z_7Fb@0u3_^S}=fE_yNJ_OGXJMS1GktPj-YazcYXKCm}(9PzUVS5fhS2m;OzRETnEP zCNexxl~+?QGat-mZA~7ga*)o@CHl*y?_oChC%?2W{Hm-9{K2oxa%Iu_hnKlH<>$<)$6;>nTc$n(u`YY!B3%@y61~2@gzAByH`F&A3&Wxu2 zSOW2i%XSL|v(mTSb<1^G@7*{ss7$EPGN1d4}XS6n+ z`V{6PK>pGlU9@3i(C1upuYC_@$QV$O*QKlB!NWSi9kXM*QA2dOG;a~ZTn5tVQoyQn|$)RutsB;KA@dPIE>c=f_0C~JL8f4d?U zmZb}G2Uns+raULMBV2Kym{V+II~zA2#FvEK4wFN0)|^=HYxv1)hylepgt*$^1ND)0 zf)Sp~KK>VtPW7VEb0lZ?Z^7&~IPHEE`~BNb*J?#{C6r(IMf!XB<@iVb^GoTx)ZLPo zy8HG7s9ld?5}ra86;2Y6O52O)vtrlhw#z#;EJyd{Hi13P!si6~hAYy)>qYGZOKgF@ zjG4pDe$Jz5pLhC>?N>pUx&|S79VQr3;QBY6Ks6j@y1s;36IHQc-2XOaYGd=;u@29= zb^76S=h;A}e|;S;#V%t>>=!!|HE^skE00^R~%=aOT43-GTj2HqkNE*x1GtoZ54j5dla5VW5 zavH_U&8cDZF20stm|vw=Ri;Yg)g3wb{8D=T*5Gn)kwT=zE9o;4uHS9+XDH@};eT$} z7Y>OJ)+#9{%Bo@1MPifuz4i{g*4|lXyT!7-m)cADQhT|77!#li`p|on+-Y{Bg>aXB z!IcS3shg34ujU}VsAH62izJTUF90=9RjVC6l&x&hT@*kbLRcv}C`Y3D5n30{S7z%) z=m+8)&=ObOm4`$Ss>4~?TU}_!p*JC9gBAVZb;8`nr;@q$)%i7H5^e*(Wq^ZrsV(GO zu9KjuM?d#TOmz`~0q>txWq}_PngH2AVgnHpqXqB=m6Lvf_a5?Z zg_rXm{)pWaGsYj`r7?3m6oy4>jaW=p8^03} zevA!X%lZ$ng?W@8e1#5-S2xk0A1)uE10*%GzF}0gY-@vh>N2fC*?DPRI1F{!0w>=@ z4%yl<%rX56FaKq}y#Ezm{%-6Ip5#}#@Xhc1!`Q3+r?Ch4W$Xq0GWMXNrk9iJ^nWw< z)L!OGmXpE4U&da{Z^oV`cKp|${uWM;oHo4}Y31eAk=G&&nL-G!#@*C z%|hFH;g<20+f8FUHbEaXfoUIlKw1NLX^XT^Uvb@{9PE4+iq~MoB*IkR_O?j}u36I3 z##Q~Rua_5!31zQ?q?V$pQbhYNzFyO&4xs|UoXudb$FJ;C`*Q2ei(&}(+E?rGJ@{2~ zNp{CS>@HIa%P7upw;-n{`9H`fD3||oMXGzNebOy83p1FJc^OS)D2nX@yyAJ6lX35S zhE=ZUwf48nOPOYqqdTmIB>Sh^N$+`ZH4A6I1(+(6-m9=ShdFa2lP3q6Ta#NGmziiKi}(8z@d*~A9XYVkQ@hNgEMAmLNVtOHq9rHEtl-OnJGy15Xuf8(?+N2J@5@Ai>Bjchv*B#Zl497A9$x#FU%= zsTp(v#UMA{L$BqbY%YscmI%|zsB4Wy9*$+Net!C8?8Q{;cD!IaS5JD@q~DFWbWy$b z%h+@7Vy>!7{NPE=)l{$hb*rI#0pWb4%>f6>k(w`X``DQ1`uPDbCM|#TmB>8 zH=+~)5_$6Xe*C+!n5j|`?Hv4g764>0^g%b-LukUv>616}(#!JF`{G~hliY@jMd0ou z12w21AHCt~^?*xdR?a{bRjDVdlf0hG#cE7dlZMiK<`u+xhbQV(XBxJf3>Bv~ywF|; zr$)-UnL+g8fNk$ONtJqSKEi8x*0{zTNmMQ~+ts%r@!j-CC7_kBW!L$IT_?0S80%l` zCctgwZEP}@34VBG7m`@eis+{x)s@2Q4Dwex;Y4$0EuS% z?Rs_JS4)}#%3c-eU5sk6TzOM*i`n?5@o;KOTvJ~oZ@R}9GgXdgNwYveF4IJ)!HS{3}H;S+nCK*)DuPJT#bxM1FHKd{a z1B3=W%o`EK@?l>?_j^yXqcpzr^SNtc5F+E(DQ(@43%-9-9kh{U2e8sbhzyS~^~lV~ zr30O@q+Mm;xTr1<{C?**j~v9ESSfst{F=JospFnCFvxj^ygGX7)nlfTuZ|x6we~MZ z&l)ktOMAYcBe?dvQs`S*x)~ujH&s{JNgvc>?T>`pY$PGRqaAbwV2|FgGXlV+bSo#I zk)G63i~IQ6o!)%d0P8yqWt`&>ZEjz~UtbqtplQ~R5H;p5T_n@Cx-B&pKKIootI;ma zM!vR<@6TX*6$^g5zbfwhJArg#(Iz~zQH3uD)TC&$rmuz`9mQ*sBJi4|eCy+;{6~_) z;Hc)xmZ;!nSsaH}%|?2Wi2V__SFt;4B7jYfR@!_!N?ot&FfP;-%3k*BU1)-8k=~NB z?bQAW$uP=`&Q)Y`jrC|TSLIaDajPt1(5}ozd`(hR?X%09Vr+j)QeLqo#@odxTuxmG zZ4@aui(%tP&wdTvZ;6j^l1CGOo-JtSx$>e#YEA-3<5GxsaKhc_abwR zUt}%|YyGEwKLxOe3wLHiXX-c~4r#^>w4z@!_tnn>@kx9Q-QihRWA87ZCq>?G6|;aB z(v!57;OZ8zgzl&IU`T)7!WF&qFlozRS`#GNcvkYmR6w5yiMP3udJw9j%lw$wp!qw_5G&U^YZ0`4T z_XOTSMlTNl(^x+KhfYYn{-qO|-{Pd`9JXQ0^zbL{|3D@50AQGntOr)Nxh)M6*HU9+Q9{+ljwPXZAuYLzt|ltSD;_rGqKeH0AP{1n zL-~MrmXpkiT*kl0fPJ8NrR#fFzr}#&(%K}yVn7EN^>SiT)%%yCOI|JI4-Kv^`9H)c zq5m4AK*Gl7^!^&r^35F6dI_owk=V#b;>^MJHsdZ8+5@~j0`6~1|E=h9qaLY(fr5Yx zye#kjK6X`1t^W1D5hw}Bg1<%aEk9+1g)(JtH5Fq!)0$KD7d-;y7Yf&d+Xpjnw)Rc%*>3(%#N9vnVFe6$IQpf z%*>22Gses@Gjoiw6Z?Di-Q8Pv_q%`Hcekc{sx;G6rP4@h^~_JLeGciQLmje*R%&AC zu{Cb>Q~6Hn05+o5q_n!J)XBo$Dl;-eU(9dpv_?#Zl&Aa@bs#-;xP-WJdV=v62;7J4 z67Mo=%<09&#C6FPGtoV_E-k_pnWybk<@nBaF7%l2n%?rHCV)%dSr|3Zvt|7}@3Jy;i8i4O**ECvRq@E;TU-(}sa2XBIP z{1oW?oVmU}*@kAqbA%}6*tAJ5*g0y}u7tF= z0asS-pcF%}5;tIO?F5@v?N&BcG265lCO-WiW|X{A6bWp8`(F7y5a0Tq-uB++2;4sn zzk{1pyF?@LyW~Q)?*Z{?_Npk9ELbXd*-&Ykn*7iaEf^)ZFY~8QuqJt+V=Ng5tJr0D z6o<|!(xZW0Nb5Y}<+iO>)V@9vxl{)Z_@D!#_DTg1k$`xZAjNE&DIh+@l_b4o)ej!G z(HdI6J+Td^37LLf8GG|l0evpnr3tCRl#{Y8?7lq}xqxY(_sTLW(g;379?0ygPrfgQ zuWcF^c{<5TtSt}hi?p}M48~>k!DXkgmwcRLCH(6=r2?v1mmwxxkf_ZlBC^kOLF8gB zC$OX)3W~jS5iB9aYLXp7!{lW&ZHXkbsI=;qu!uFA4t8}wcP&Aha%qdqvCx^#&#}}Q z^vVjsjRH0yaq2Maf0O8u06pA8ptGdyLvY=0vezCz^idw*;Qw~mv2@s-qT2eNug$v9 zU{QA)4aD{}V%mQJ^nj`!-!VQ!hU|53Ju!?}_POAlP2~O*K{}7%;I|(n>`)%CNd8WXe35&# zhrdm^3xL{#JEYn6NM*DZd4rdg69WpQ63I=XpihhcA$#tmDer(ZgPPVtwIQI zo%mhILacQjhro02DZa2c!n zTs{uGjhw@A2ivlp@gEno_{1G(oD0d1Vn!(m#GDe~TOOutlxCCNYo-{|4Rr*ay^q7? zPc*-n>B?UDa<;F(F6Xbyw)Ql^MRCV0G|!=gK{T<4>3Y*Jtd(NJt^V5Qi*rSXvz0$k zY}2#oY~Yd+tRFS)XXzNig>U>?vP58ylz79S>@W_J;n`~LoR~7M5o2RISj(QQ1b{G5 zH0_(lT(0+>^?gxU%mxy@t3d05(9$zrOsI2;j925^)rxuxuiJa-_YY0~PbFS)AJ- zyz|1BYvHNm2T62e6=Y_hvlgVf?6@?~`r6j)AUTF-$-FT0BG{7cM@9Goe#*F~&r#l7T3_n=f;{I?8q&J{UzQY=1dgovp)Mfx z^3d4^dp>kO!iq!3gS5EkZr6$zEMO;=w>^zwCsxPIM^c8@R@ztG^VvPGWU~Penn{tj zIY00EJZ#8usZJnDClvWuj8Zb3p|F)>`L(|QD1g_0=tjQUMsnL0;eFxY{j~M|92K2@ zbi8MS9pBKnIvPH{7fH;GP+K<8DoTW?PKi!-e>>*2{dLxsJqY&4IGmN-5@GGI8Ayv` zvzXjFiDQ1SxE|KQ4bp}x2B)V!2^TYk!A`Zu^!8Ctn!94m-mUz##)2fBJ`yULUZJjC za2@kndA2}7X90Qvv;c?@ToD!>0_SNbnY?RT(|h-UVf&m6L7-;^tQ34U=$pX>ge@2k z&|4k^?EMSex*@_LRVTz(@Vs(9Y=N)@x_N|db!ykT=+}-A<-HVytFTRMKZF9Ddwl56 zptpSLQ*1wQV*t+)wE0l9?)E%QILEi55SIO^(osO)&`w8xQVQ#J(Hv1f&aY?1qFpcuViiI3V9?KqV#!d>XpjjA|UC;H?hx1^^Uc#davM)P0fKth~dB< z;RD}T`;O%m=GQXbDo}k8X+uM-M?PIoTzpO6^-FL}19;#AT%& z+C3z+@JPa0ESJcN0-O1#xt0|bE>Ye&;hdo+l%hXR!m&bCtJHCMW zz5e@{U`js2L6&=A1PTO%fN*I^f%3gIph56ofjPwB2bQMB8C08!c&#eBh=>~`y(Ucj zuo@J(2$-)3I+UL(7GtzY_;YYc;M+h_Hk(VS6@5tDdZ%4shz$a?intpXGT!k4c&R3P zP-{7Th#?wm;$ACB<|O^?q$2$EZ-RJrrUG{H25R_Mrn@2vnGkVNe#q~+^{IniHib|z z6mlAh9F4^-??OJOnIl54)}rMe&DkHuC54#SdaFdrEef{^j3u;sUn1BO_pqQP(b>Zk zR43HJ9LOv5SLKb*Y46luvuHc8OcO=$pRodYltNU|*+DB74wwZ^s{TA;Ir|o>6PC8| z?fAuT;^C%nh_GX5j8%PQaMi3trgFwv0l`|gF|g(aWcllr?X(y6@XCAJS_(JrqhtAq z2=4}s@lNu!7Ro+9vLVQyQ?q5B zf=~9WynyhnDs6kH+??+e8$Ve)DY|bO6D`lbV%l$>|1O+s=4fJ{Mqj=#-x(7T{MAYX z&%r@$WMIg03Mvi;C2G+iP-2;uvVNa851bQ)lZSn6hc?}qBpN{jJ$Y5dumb+Dg`1Sy z&-@&d0aErK5v`nR8p~B>yK#oQoJ`V&PL0%)^aN*~tb~9_dT0U}FAoFwDUVB_yfcM~ zqJKeL>7?dZ4a=UEsWhwD&txU*U6J*)}#eeKiTS*gc6l=4_HR3Od z@j1egZ7~zA(jyy_cr1o2ON}k-EKfT}nY*#{zGQU$!+&OmAk_OS0r8VTQpfc$zw>TQ^JTL9W7~UI0I$0AR zzR8sHrvput`T?p9sW8x|l=%Qw`Ziue3`krOv8t7mdC;Q2ffwxo8WJ~&M(HZU9G(0c zs$cEpW2OTYjxx{q&u61@|IJiX#+NtmmYsLp8ph$!$FG9dvx?R}LcPYg6GyV1yQR@Q z3<__}AEjq~VRMR;)rRz$eiZcc;8_tUVwO8W&>#g9gZz$Y!t4y65=W%@R#DroUz#EZ zl)nNK$RCQ0ktE_8P|^jcwPBKU%SR8A67M}L?BnkJV`rhK>}TuGPGn*-ah7bfZ%Nm? zz1H)U>g7wEo}Spe3poUvj5}>`4*dd*dFfm1Z6tYA#)T2JXl_){5f*Qj{y-(tnz=2Z zBpP$T{=UT2t*M}GU+8=bv4?_hX0x@KaJIm`l(O4xJ{kSb6NUJ zoz9K!LL{?bjbTuzhVcQbG(xE64#R`y`cm)Q=fMmVKyX@9EN9e6yDc-1CUI#~cjO-S9mZ@&-UpA#nb3Zm1SIVrB z>vW46uj1RT?uE=Hk<{cf75fM!tBkgDhNMdC1HwsY-bdYhFP`(0hrD1rKpe zcv!AKi+aV4rXAKbbpf0SJG2({diATDI;ynW)NTv45=O$mTmB{rBd5!`3NPYxl1k`B z;V*nJW6-E`EG(aP?{3~OdG*p!g4l2hwM%Y4B0F-3_*K0kSBJi7T5h+ANNK-eidC`( zUeP*evxfI(>b_~hnDYu3TlmRmLge{v@D(M~MWoGhpbB<^r)VoebkbJ%$!j=&xzOJz8HWl10>oT#8gQ842M+?ohjeT1hAW!zN|@{u)q zaS!_QAa!vczEPO`o@WQv2h1)gVc(4hMV}3`e!$d)Y8G^B235b>TaKSA@s&aV4mDV; zwOoX-Reje32*DTB6YS+28eyve@G&=K`3>@m6rkO=UfzIxmt|h%1503KDP0wPWqMGx z6O_}aJz_9@C01=3ytPddd-!FP*Sbc2Q3Z3VN{hDNbeM>$gieCH509RYwNHmGrSkRQ z#kMprevkt_zvzWU^bNwnEd?%+2Z2%&_>4F~^o(6~&npO)q|qDhAA5MC%*4P3*4Qz> z4UQ>z#2Z28%#y2Rj<=j9{Y-yE?U}P`A0fMCT6@Dav5TT~!hEeV|GlT~E~sy**9W+H z{-US0>Ew?UvWs4&3fSeZkrs8v^iJ(R^dK zo;69s^t;1{WFA@OVypjLDL<-FoYbZ<0O^6I2H1x!6{1cP zXCkO&W3N(ZGk&n=b4>ygHl=K|TJuI+6^m4Mz{R)f&3a9L8ZSnZi>;kkhb|;( zc49_*@VOOT$Vk&zvrLomu|G6skr29TpoT7vd?*pOT9s`Ea@D!F4-@HmqBVaLm*1yM zoRMT@?O+vz$-(*_8M26CjRvhJr$6^(QHdYriSKageZHEu)D`QMIhR|D$*!?Uq7#(5 z0T>IA%qX}bD+CXrOT*X&$^kLy*0EFWt8JF$^P8guN`w6@Aj@iTfMCp&a2ry6E{Nds zj!=Rxn6Fn0{?@_*l1cXhgX0WG#J5A{E>I*9bNldEd%PY4q^@uR36y)#xlk+z8bEmK zfvFBKqj9P`+{YopvrukV!Qa?9`%J%adMe(}+Y)6jFZSn#1N@Hj7wcrMokkQHi%d74cEa0Z3%;c&ToY9>n4B6g?UKSmpDCg4Bzf z8HstFB4ID;VyBR=i4pPsXs+Cm-$Pjz%#RnoL=zQOBI96CGwMy=*EgqSt(kjv&n~N5 zFv|xzx}?KLrZLts`jjO`R*w2hX^7fMWLY3JRG-|pj-*U{t1qiMBEk~S%_Y$(tKNdK zz727|VW*wyRW%x;s3q=!$D)1o$*oN+B3n1kHl>@H{_FSwuU#YOWEDjtKSR-;#~9tb za-}q}7G+(dBpBVbWTe#Evbk@driIf~!7`=l&S%<{onn%pK5-sCgixHggu)=$UYDhT z-sXZd4++FTHCg;!oquTue9X^C{C@ppfz_o|gRdZ-WoHE;#*VlWn{P+H$6Ekr(VWi- z1-7Nuso>L_$xh?$;?0V0ptfwL%&-h?z=Z2&v+s3j$aZd9dmMdFHJBpr=_!`q_k9JX zO>phiHm-*FVuEN@sj+4i@|-+6-h(yTu8BT>x+zr*Wsef!BEj|uv$|fmY+=p>={F+v z0Imz4-(TvWt%4$5l#GzvcE_n{K2ELx?oYQutBrwX7>d(@Z`nbb*9eOZB>J+4T|Klj zzzBqG*@x3KH!g}6cj1X2Mdv}6KHwk~DL zcc5H`<)~shjr#24{s3GJ zCCEjhT*;7z`_b%*ZF`Mr zcZ@dSw0b$sXbO`TAY@rG1f!$;R;Fr-n-+uwG(<|q7?5Xz1Xfvsv9$5r>4nWrGLtUl zAB%N^_oR6ky&1snv<-Q^enaWG47;50LIU36&H@lW(u2Mxu;PA{K;CM(sHR<`W|zh$_fI2{^x6X zW5wTe%4}EkYZqIS&g~!NmUc94(^KR?H0L5#fjySF(=3HTQtZOIXD}Fy!kT9g>Z@80 zM^0<<5u|{;9}k{mBF=2iQa@?rDL99f4kr#ybVY=ld3dbuzsSll+bqukf^V|B;U#*q za(|8QiB-qC!sdd$QRm5Sm7n~rMUg>r>phWSR#i8tssQpt435|)Y0xYJz0F;A>~ck- zc~9SdQD(X8C&+o_1=ODn{+Ww<&l~x3J@SW`73Ve#qKE(7K-Iy`_#e$>?hWWjE!M{z z!K4{)O6L|y>&n|0`es4E>#)kt{35F3d?E0|u3X=~sAjT9`#g-TY7ObcF96%*i(CQ$ zgZ|`q)acSbWyNJ0-_zVi^H}xUPUWabA};nc@^UZEQpI%<7GfdwsjBfv6rZQigpb3s z)VUtsWSyPQ5F{R;y3yQZ_I%+B1Ci->P!F5ZYCpkJ_n0#yx~oF`;nI`ER`*DLgrN=} zci`3!?G7k*paw+4?x9?KQVH3O9Z}Pk>h~iMzSV(1dkkvZ@=n@X7G2Z_f6sN*6ny>V|e=GmW;;J->vRn|>-8VY` z&sI@zU>(6w74wqMlnAz746-9R-StN^4SxGrIi|@fwx(-fl?9#W5uaOGo=SOiXFP^h zr`v&&k{=_@tPO*pO^QFU#ct^2bKH~JZij0&>WiIq^{+cNykKP&0Ri}>JG#s$WpOGY z$f-MBo$$F~AHQIPEZpeE~IpKq8EX9#%tF-vB~FYth(R6Af`~3=Otu% z37KvDiZr!e9WP-ksGU>=63hr!mbeTZdZM@MDoV4{G>nYmeb&b0;rnOBS=4UvOFDGfBciqoCZE+5m9oKH~cy{)xR3r>nhzG_j{wUG8;!g*PE_8YApqCm%0} z`qOv8a}c(COog$2{{!(Y@wkd zpN(x3s~%(@da2$~j?t^u{OW^ES$KGwJ;ASPs7r>}KjNEyp4$q{)+TvKzH+2#D~Ak3 z)Y^g=d+w(*OoZi2F?@KG$9Y1+2xYHDaeVMcXJ2LeF;)&rkcBtu`FRGc*3!8;Y9X0- zk#Vi%fBX0_bm#Yl9Bg}*eH*)ZtN$kDVR&4ZWT5S9-+WzvyU=jfHP%gNvG!sp>|%J? zHS5`HuixH*>SptV?C^xTenGf?LE5y!{a3rJ3B0-vy6zqv69O|?gbn%vH$iWT!UzjK z-iQ$zeO)mB!2YvdztW+5g;_mr_*2(jfQ2>xlyHvVuyY8=%$+|KYu$Fw$enLF=H7mv zYbsDdI0tJ2d)3VU@oyVtjAJS;)&C8)0}BSG{lEJoq-N>v?BHeVWc~lz^Z%baX0-hi zF`3G|Dv1ycgzeO=M}*jk27pA&7^(DzbJ&)yI~X0TvwP8iPjj!)EOLKCOqRr2mvd5R zs;K8&<$tNz{nh7gH8-8nP#^@UBSId-yfsOY5XDH%W@)OonZ?LBt>?lxt($2*!T7$I zppD1(6H_1QTvHPpNl?-_T1~Ac@{Xzj3hSJKJ_A-?;PI&oUtxSRV8lc?O)abjJb^-V z%1t#qzlHLso`rJl>xJk+(y4^g+1jB@hwRxntkQ^OTk$h7i-z6p03CNai$rONps^3A zz7nVzR`LK1Go{()gOOs#X1ulUy9M9zm}}lR)a1g85>>*gTFN-C{v9z)+(}WkVu*6z zVb$DNmIRH>9p6Wp{o++<_-+y?J9t<}Cf zId2%>+1)*OX=Q(I&z8&Zo`2+up*DNT_1v?yc!hv{_v(xl5XQy8KNRN)S+li`a~b8) zhA`Z%rLuW?Kr?w9c&`67pQ#4mG1}E2?Q@3JtDC1BVXoK}9DUZ0jP6`h9p25=%;Ri2 zI7X4qxYgoHiW+%Ud){=9mf-NXPci(^UO)iansgqxyXX&q0?lwee0ltIV?OtTI?99_ z*7{@4;Bm9AHfrCXDCpis=@Kn~&QTMV&#bbNETr z9P4m;B;ELOR64JC8w+nV4DGXt4CXU0pMH){k*X9R&!x!4&G`qsYH=E!4@F-3On?Jl z51{Cl-7vT3gmKFhjiUCPxNij5K@!ZESC+|9$zU^)o929K{R*v(Y#vYFrIgK0fGyM# zd7ob8&H56?0m@dK!3;fVr1mt;8dl$E&SpQSUSO4qB_7R;iYL_F7Kv_rTa)bOiQmk+ z`Ztut3Jx~B=3gd)3-jMG5%zzY$hHy|8`cNH6be652<-t2SqP)BTHwJ7MHIQ2f>dO! zTN&>5IvB#J)!TJ zKiT3gg{)kL$HA-wr@cGQP4kO-s$1Rck^ZcN6N47JY~?^nDz=?jZ3K%#xN%{O=0#Bw z&6Ks@*9^@kud7qZfyHgDmr*k6vQs`b4%08C0VN&;bURATnH>32T5X2(CDZg@2h@vl zkRQ^=S2O=W8r?E}v$RY0FFOoj<|wASzS<@T#m&;#D=4JD`dWH?3UOtPP)H zN205vk$ha*EyIDYL-zcf#lxE0%l)<iiaYWTo6p?lFJBs=cvFxOcM7k{9xx@tELI z0(6atkJ3U(Q>M@9<)=s&Uhx0iCZJ8@LM;(jFpJj5?He>14|>O99*70!;Q7lO;daw> zi&qkzGM!wNl`Kchop@Hs8x~J>s7=&#Xz9?{3W=M?IzW!1!@z%ulDGVX{Z~z<2u;uW z4h{zP2lBt8$^T4CQdd;o6hZUP$?O0)!DGqw%Tj4j(GAILAzVNQV@;OX5g*TQjv3{K zTXO4d+P-SLRMJ1qlg^WBdR|G;(`zd80>4`AY;rq~Zxr&qq%{IX6Uvkfs~D|g06SY6PpW=^J&&Mqob zaN1FLx|IU6VL;?*1*Ov5ts2QjbFmGHw3(XX>Z!;Kdxz5ZBq{uyaWZs_@ zJ9|rvac$=5Pk&5gvxJ*dtcj;=%H&QA1zrj(evNtHh%zUpOTlYHno9e2nXyF-syPbV|xUNbU$UYkr zj?4qsd(M9wDAihTD7H99#{H}gg&X*W;Cb`fRS+~TKpngWb1o(}Ujd+1!Q1;u<#)t21< z4)HB#eg94Z4F>jt2nMG6Ur-;8|0i0|y41(g#QQ|A--Dhc zWQu7vkWeQC4YRWHTjyj+MolzNN%t>5Jl6FO%AwCN7rdvuq4iqBM2(jH4tfwcWxxI7 zwd4Nr`+d?GY-fy{?rQMejjzUxWwweZ0nx2%V!nccoE+XSQ9`Ny4@QbMTaY|msjD|4 zjA~{0?~A$Ic&-6sdJt>w5S&y>#}I4z0eyYdE7}^qQ)THT^{_M&C*>Tsdz89&VVS$v7AFW@z`~t85?!n{u!2;w}iNGOZF1}#lCx$z>`V3bjWG5Ei?ro zIxB-L5K>rQ8f(ON6iQpApv5qs1$VrY+ai`9YCRpZd_NX1pXpOO$Q2(KdN{|~e2P3; zY^z8Z8kXH2i5ZGy!KG5E+3RG6IY%*5{z7G4s8p(=14XcvbMpLSjVyK9G)tOQD=BVYQ`{pL0mLj$# zz3TzC7}y2x8i0;qHGGN20W{@QWyHIQu_|G ziFAV_bWSYA9h?-)wO9yE+&5pRr_Yme?g0eoqlKr8>YyPBU8Xchx7QIV$-59mph*f- znUqx~^~?Fk^_52Bi*8jaRSod)=GFh!VljUopQP5WF-o$)X|y zFZ)T3C3J*Sf3cHhmVQY&*S~C(z#N{9i!E9t4+_bj23_PQoVC$wkHYnSeV#{zue7yE z%)={8+h7+p<9IHDMtEMT$K@n1?PGdJlhc0}Uv3o?yON%ahUB1$M4LEd%Fcn4DxE0g z^pOguI1fmzOL76Ic!vNuh6D}8diwp*+kQP8C*t`+tu{p-wPPvj62z$BHd(@NEC4ql zX;D?A zIAeBHfu`vEU#V_LmFsW|xvEy6D>b1?189u(78KVIZ5YGs8Dqy}f8uw9@i0?L-cI6R zMY?6$NH2skoQ>o40vZ;fITZ{_I^-LDq2BXN63!LrN`T<8XU#U!u-7+rR~q;&D-%Ma zqGwoZJn8dM;ugvX!iTJbW)k;Iz2W?DU&CnM(e$Z3;Xz;Vb}V`1+;jH@O|m-d7zR}E z;+YPi)Y426iN|e(NUVBmiXwil6CbB=tc2+*w^@D;)JtCV8Mkq?x)kWS?m;0hO6^BC zzzK#g?QevBA!k-hdx5$3N)bPwKL%(zJR&c~^yI31{##D}AM6xntiR@S0r>yUoc@oL z%{EO(^(7Is02G8GHWBC%?U9V=GFsKaHHt_bsEVMCkVDvU*`*eu@(iV9ig@9H{*u|q z_*tHSXKCJ>GU7U$rZ4FhZtL5dzWKe|Qzx4*%En*{CT!780MADgg>l~8Tal-WI5VPh zcEfoWRcTyiI!m$PQY8jv`;Ov$`p_m?s+SX@sh3XWi}K@<0?krlIBmO*v3Wu3^-I4# zD>GoWYj1Jq;ITWozka4=g&JV;(NQ@SvS1Ti8Hz=5H#xkg+Y(1`ZQIY*s^&ucrhB$L zaWs+jP!IZMG>}q;%|i*EF2ok(XO_?@wT4IG?iBpx(JJg{DorilmzUu-v5e(#BfZ7S zTkM?E9H4`nTJA6fnNu1kO_Yppg^`79zr;=~k2AwpuxvBtC2zIh8H2(t%h<2Dsj4SBT2fc{hxA%w`@&%_i8pPktUSu1lmtlCKX@I) z`pL?6cFvTOp=s%dyoPVY2|&TG*;nL@eCrR)ls)R2#@-b62ogV3? zJS{a0rlYS~xojah#Kga(pkWe1OQN{^Ev9;+WD;aCRPv67=tUD2I_^ z>d3)8SVI_mVqwFi3qnB?{2or?@3HmclTE0RPU3+rkS@GXM77M-;Pkd|n*SFJQQXwr#!|}D$^36sCjSv=C-y20vtorO z$jjQ;6eSD{sHFRtP!6b7sg=p4$!Ni>wZ)1zQhsVYuHXT|ND%hhlwS{&& zpT|!(`Gf3{)N{*uC}ldmZ9{OOzLt28uEwq^$n#ZYxw{W*4V~1v?$<4{_uIwcOv$+v zU_K&HaXt)6t-hkxHpiYUIC?)2`+TY^X=R#pW>)jT7-A_jq@O&A@~AgZHehq@WUJ?5 z3J@^ZQa!dT#xrazi;l(fIcXl|?G4iR7da&qFA}uu%o|T+V2%}xaM;|WEkK1rZWq`x zUCoja4c^^hL&XH{(1OG9{UBMS;t}J%IIQH8Dc<->r{vWZAzG{3gC2y(!&;yz;Q-pkFu(4IPMy1X;aJRl_$Q3!cFUuB_##$IE&Dyr~z zTQ#O+sTKLF#?Ea!1d%vxXE+^ z&y?2v!q33@(^w_h1QuO}4z-`$4?j!g>66WyW& zomv=d!-;=>d=iB_zFv$b0hz<1Gi`GMSR7qEh(+oSku3dZu_W6Coi@fythh`&%=Lzf zvo<

    {mNZvi*pZ(tj;cAJ2w&98#~~4=ZK0SLE1jd{@~3R#$XeZM+}}9%g^^T-ZKq z40YC18;)n?XiJBZpDt$~-k*q)cYq%vb+3ua!&@c+%4rcx?4z>_WEi9huO zttu9wPHR%d8IZ6O(Ka@FT0+b_&DznK^YCkW8q^C zYq$!IjE#+`zEg5}Q8?>^Fus6pULJ9>9tPe|B6lyc0R|8AmWpf0bN1&K7iKom?M}-I z6l~iF_G-4NxTM}17M}f+%1eF75VuEK*hC}b(8OmJKjf#q%5!a5QrP(*5BS7M_^GBD zfY=SPK*K>op!y(kPuZSpPu-rX1a{AG#O;9~D19buZvHn5w$9-DYf$_)eLVKoNQ4H{ zH$KF8R}3Mw`FPs4r{66s`{vLEd+8y{t~GG^-JPd%Sbp&=c%b$lT_G&@X<-f6exc4W zU>|U>!nJ#h`Ai{CB}ksob9lqY#p&RDyfwd3_yYP)rKYwEto4GPJ)@JQ(xvoSO1c21gj7!SssVO(;$rq4JL}jGeXlmRfl!Q#n3;ATXTdTLU zC8(_PM*7*3>y8f2Q^f)?(a(&f;oXt*jX$L)sDMG$&nha-Kc2x>LW%o}yUw}JJs8b= z$A>WNsCi0>`x6J*!Oy`c9=F5gAT*>|yUyKnMMmI;WRu@q#6%n^|2P#N^W+0eplD!y zf;fEOI5NzD(P9@=xy)vSL%!Xk=Ug0vTm{#y?PVoIvgs; zusI~k$L!7#cF)fjMZ3?2Q)R`2Spaa*PIsGZ(NDGLNtdFU@+@EBMh1(wA?tGNjPLlG z$lGM=jEKM7-w8^j&ntxbo2mHHSl8$|j5ukia@`aOFbzTnn}_|3aMs~*%4Ph*$@;8mNqeErYku*KwS0tav)QlBW1p zjV8Qx*lo2@HqXh949NX{eIF3URccr_yi)~+Q+leM=HCY()KyevxqW}gsoCrCsL08Q zrY34jI8{OFfmOZC7os7d5i07$VFZc918{sU5KL2PpBR#`@({`7qH@tkLdA}*<5#n7 zhPBz1u>F=R*OcDj-qoemREC(eLOc151{s#pY&d1ATP->VU4mA4ofYww)^e|P=1hjV zr_;g<>xPhGm0-PeIv4#Gnd{28UCQZU5v&Ti>!eE9$Lho4Cx*S26GpD zq?EmE&@28?sZBjvW?!@5)wR~Y6VTb>s>3nrj=xH{J2H;-V&%qYr|2up_Mnv9XRiEWQJT<0NaUya4p_FvtWW4!rw{A^MgqRhe%C1Jjq_&)1Yh- zT+PC#Rw$6>aimg4eF&spTq0amV;i_fxMK@-kwA?R2%*bCOraL+<0XN(%tKcTO;)Bj z_|v+%L8uJElS7j}`!tPuG&MZPi2>1#s(3RP7Vzn!shtziTyTB+;ESF8I(YO`#wBkf zXQ8q^J_U*=pO=!SD2;2r-pw24>MnGlf^(~9oy~7qEkoLl2$@)Tz*v8l6?&l zVc6b$@OqOjb2WtX{hl@r@3jsMI25Hg@z~K&A<-2st8!-isiJacf^mcwYsrUGs!?o^ zrCr#f)Q;|AmUU1!qI`z?b8On@V7z>>#jCvw)dl~~_Zsq}Zdu6-rY>S#X`|i5Pn)?S zgXi!OC49X=n+?6ZpK_?=yN*14_#Uq+o!F|U1?dT;wp$j&f6|naUDC=5=C|JiC9T=} zd9H-Eo1>7pq~Jc!`bRMk}0vuLCXmyY>C zFUitI!noY4!v7lBdSGO*p#DW6|JCjPbG}Q$^6!s0yU9AbIIvhai`!V5+yDJ0v%80> zi-V;bgZwm|;tHM8w7l&11~9OH^{L(EA22e1QHpiiK@S8D67N7=L#Vl=a+KpW(o=hU;P^lq(7zF}?WM@8t}Rpr6J{y?L^$u$ zKpJ7R^5Gh68$)h@I4oQGhPU{^!t$^D=gT8|B>qLM{?&Ece!eA6f> zGBgWwW7}>_;enA4f1T^gXl0z_VwU`+-dMFU^=l6CU!@?ZF9VbI7u5>(zxa^!e@j75 zQb}G`Nu7y}lZ%yu+0I=swh1MK6-)AKUoj*+RgxB_=fDC_C47vr%4UVequ`RZ3G44} zxTvq)G`!OGs^hOGYW8zm5H+wShe?Q+*x}uMFcg zOdDtL;r`e5prJ{^@YkCX~bq06}rdX5i4J!Zd9} zXJeh?7&l^o*~&N-`g%gG`~kd!8+d;e3e|RD3&%&kwY2#RCJ<6E$04;$9H8PfSiN(Fv7NSL zO;BELOdQ1a%O0csp)08Ilv2x>w1!v9@0@-r#sG#b z$B(qqeATYaP1$W^3ie-PHJR9Q1+EG4Fka4^Pd9D{?LjIz6Jue0>d=*39} zeSR6vuEPgj+m%rHt(T?TI_?O_rd-_tcvf!8_KrtWag8VZ?dULz?9uUS)o${uKr-SS zzeY!pyec)d>+)Vvf5fuK)qp!=$zH30t>l{|Gn1ILR#_LfxiW*HN-e7e zDvdYow|&9FsmD(BX!D=Z;FpW4G3U*F(2f|v%oIN$ns;PnvOpzJ+bHXoYl zkWM8wQcrGzR6ZO4@5GxUb}jYM>YoxUKs)sjxbXY6*Bg0DIV>wjW@qygR#hLpPdRZS z=}usDXEZ;hKBpSCt6rc_eMxi9i=|+Vp}cgCsNz z!GPb@-|zGHe?CyGf1yI6Q*Se;NH5w6y~cffM|z84IVCoO5r4$26@tGISPr%!D#{c6 z!b$m9(2x53h3Sn|?ob5q8@j?~xf=m*m`7vN2OG2L-oqgm?`HE)j_rGpt=aA)XD&ID zbT(v3)e#2^Yx1s~7OSk(5! zEv?ehXSf8`6BalW$>JNjY@j$!{~;rZH0`WqQ8+n)(_mcfBUY6%4p8V`w`w!c;{$K;4;PQir3R&zeAR*K@6t{q_a@ z%p%{CT}nM+yO}*miKc=VmP%gkC)(`3DI?!g?ij=f%ZLCCo+XOpDBw<*~Y$Ks2?Z^vwSAAIr zgK7&RZkgAl0ZGc|IMSW8D6tGUidnI?4r+@>MEctbB0_#Zq)Nt;fzCsWmY>b;2x1=-Q(9}rFv<3@qrY^?3>sJSDmbq#i z^*?NB;9{o1q^khnJ z{c4n1N{PKob|tr*;h;=q=NSyR|Dj={ofVz!AP3Nxigr?}#7D7}3Qtp8(g0mV1MXXo zlv`;sy~40F`J!o^uNe5jZ|LN@z#y~fs&e6^ zJM{_$Vak1L4KJ$Sxr-`BU*v~apvrw^>oj*YicRv-)l=4G+WGNib&{R{HrDM^^tAF? zfPAWniWJffs?X#yvTJXzs#sW`=^(NWz-%r`0d)X}Z;oe2 z%cK6ogC|vxVwvh%v`!W?VO=_h7aX2P@P^RKp^u+g1y}}}HhNs2~0>#I2fT3N<f~;GXs=+tFAuxVWftD@XcS*R{jC@>V-ah=Bav>%Ag2 zFj#J_l)RNYt9)@dG2`C3XEf*e|IqeMQNA?Wns=?Tt5(^oYL#uxWkqvL1bK6QV>-nRDlE<^5D;gvY|*|>QKV( zkpdAZ1D-^2S$bg+kp%@CPOg~}4|}?JqPn}An@sku##)Lg!-W)BYD9eLU>Ajb=ba1@ z@n)kf;+v$A=7l)~+F$HR?2?oUCHBGV;b>K8K-n4;5reP$<(Eo~NDCM3#n*b{mt@ug zGM>XWli*;c{gOMV7Oq2A_ST#&3%edv!{JILU-CO$^${9UAwnm_*x~z{W)VvvhRMA=fW%_i#$XYXZDAy%1xiv>zLP5 zA-e^SYbOdvm+^Jw3kh;xg;7o0@1}ScWv&_}mkW@3NZ4nuugh%a2r$R+$$$a1PDD?4Y!JnvScMt&dk)c45T1g0ykoXEH21<7@$?3 z%27nBDmtKVl-vAvK9`oi<8^M59ITRGe=v*;!`#HQc%9hzR_|JRkV0sO>X(!l)nIRb zSu@4T3XsGhT+24>*vA2efndcPY*KPjgIYkPAJd&ZiW!B%C6|yhFD*(U_oBDJId`U) z{nOrRGFp(zoRn}OSDqpT>MyjW*QrD<6PkS93Yx>Y0>s%Tef#}p8#P;78DFnm1Vx{U z*W;kpkeB@CYXpV9tDP3v2=pQkNF}p4oGxy~kA^X;+T~1UoNI>WVVd6vP5H_*xTrRP{)_gNpXl0gJYvH{3YK;%2}! zf1Nv@Hm^Qv=KiRW?F#8eb9To2#S~sT~ zfwDegEA4jmhCNhp*9X*-z&Xej)^JlMXdm8EbvkZXBM$_99=-(0->wI&KenG$2>*o2 z1qBD*EfhwZx(>^d%N`mknVTtUW(_F_bUSC1$g6FUQ%f-Pvs0WHtA$`C_72y*w=-!8 zRj%TeOk2Vi$xAtssB7JEuS4Hq&U-7-k@Gc@9|`W=wICpPGa$n!w7B+Ha-%1KYmg_s zK9Dl7q%2;akUys#k#r2A8}y=elPmmX#cIoU@lBi)qZly0@R~W%Cb*Pn2G@eL&7wFF zA%b7yC~3ABn_SpYKB6!Z1(aKPq2}s?cUfGW4{Z!d?(F5({o-KGaO}l!5e%SH9=9f) z%a5D~$?}fnWiXDZ=9^k!VQPI`mTg&#!A;BSU5-+JUm%=_I!c;f;XJ3F<~N6wIW#|& zh{W=fp#^PBRIoV>Tz>JGd}*kfR(QBuETCbD`=tCtw7IkZ->{4EwPWs}mgM&`;;SVB zA5Y-8w$dvezO7Q7z`=S1by^+FyvnlFH9$VFAX<9>Q$Vc0r} zU-(5%qRDf_#xwcR$mN3;U zdxR|~kzT`7xHOUJOvrK8L}AQzAP1Z=$_AXp+Mt zpzK7bZWet9%EJdL7W*mE6VF?LM#{Q$h(C3m33YT(wLfpRBy(y3<8Hs?Z#gk@?Dyun zBh1>iHBPXH``{pzuSwq$f}SPYH2Rs5YKn38K58JI45oA@+^|c<+hqD7o{-CT!m5|~ z#a?!Fq*g-ryQKZ~Gu?(C-^CXrc7L(iYRgU|u;U>T%|PcE$tDMKws zN2g53)XdOS$H1%%X$K`w$3Vw)EHO1rNkdK{O*2Z{KrKTvMnyLJPw8()d`g^(hDMBP zbO-ovDxmFayj<8{Pvt=W^_3MRV@Id|^|}hihPDny#t#3ovO*oj58scEDEyoiFi}z& zaRp26bPV+XL=77f0*qN!--_Ps`|P8$Mc1?3rzc$waopW{QbMh?sJvG9P7(RRNsSSyh4LYnAp|*miW0VCx{FkuhiQwQHIc-i zu-Sf(Q59e?atvL-Jot0*0_0!+)VWxLxey2tPz*Q_ki`G%hySm7b;d^ghK9zDj>2yL zsHpZI{tiWLTVzobZe-3DLryOY0SP!`GTj!zhM6WgF*3Ly{DMA^5V4yXiv~QF#415Q)5Z|||ZKPL8uf0vBw zrIG)kzfVT0$#~6Vh|zO_4ZXF7+D^8c3|fwS=?TI%R-)G&nEZW&F@$soCQw>$R90;j zrW>>$+2^>%q2h1K;$%j?PbZDB;uzB31)cQezpG&Q{q&iBL?QTPvqJC!prnY4ox942 z$_-=QMhdC9Q5CM5;E+6e?D$<%oJ{wW1&3)>fp4FT{8}y=pV(F(;q-fxNyTGbTxdoe zrZsEWL9j!^1x-tpWVD-FRYW8BPu8FH7|52Dcnz}LINJ}(T?j2fnaavIRvfx>1hUpa$&6pF??9TJKO)cns(ems0 z(+CMP*I%T|W^?yS$mI|TuS1T7mR;4sov(62>BEOEX+t$y3MTevK-VU)ynq9pO;?3O zS0k-K&?8KlR!@X}apG8`4dslyHhA|8F`Yqbxmr4CoBb?yfm!(l3=y}$yGe?eT{ggS zClP{|u1CLaSa%a8$Yv_Q&XQBA-STrs`=_nLJbBl4?uTmeGC=8`D51uqURt4j2jsK+ z#n%Z%(7P}0Vumj{#~+q9j4XjaRNq6b^g_}&WiB(tX+?6E!nrNkeS*kC zzkTQWh zb5w^)Y;~>|`iBPxXjs0#iA$T{ zS@cqe{4JE|d9VHlNX4|pJ}28ly|Qm;M7zb5z*3bj@2s;Ai7V)zYB;UdtzMf=mtTav zlj1D=yGe`AuZ({)X+0l?5B`;_Pmn-BT>r1Rs%mELWNc;b_)ncBTg6%tTN%a2CZPc_ zph-`&+ze(h1Ks#s@@FOndA&J|WSEG)MQ@u`&1mqXjmdLO|Mx9K69?f^Ow2aNzU>(s zw`*fi5D~|r;MEp4?KJl>`|-vk@Aua&x?k+AGLoX2%${PE*^3n5l4x&47T>?0h(}c3 zyhpA<`O!lZpn;UAwj?)qLo7JfblD7WzPf-JHcexBn)oHn9oN6aI_Z+5UqFHtLk<$l z=(Sx-jp2#Q+kLpyUM))I4tT`!C~64WY6%QsxTBVBDxR+HL%x%HmM-l%$2;MaCTOSj zN~fK(Hw4>-R5uDMWn701Mm_$lmZQA3gW+=2pyi(?0``fq#dki{f^|g>!rIS@5rAyG(6`j*>V4-Qo4wl)e zd=1yeQO7uBNAH!DLslCm*nB~V6Z(}9<~8iNeB!0TN&@%<9uW&5@GYPX9+mM!0(Ee! z1jME{6?+!rFK_6ctEr|mQO!nV$*H$GFX8@6#7pp+M$3R&isFlYUQ zb3KjFOFOq6?UBKuRdZnq8qL6VL!*UZ)~EDS*2QLjjq9sGp@8IZ5D); zOoD1A)Dn8Z>Y=lsLVOzLAwN0T{zMqk73wxI=Jq4Z*$&kT=)+cU#^o64oui%INhb&Qy-PJ8E#yC$f8em%t zqy0Edb?icFk2UI-e!tw=h9c4a$|yHUl$+S@?;&$-(hayZ%mKIHxvc`CiZcX=h_c`Z zodU;GTX3E}q;6_NSN7i-^2U6Rk==63fJyWbvlql)!o~9JQOINVsI{T>Ac@m-uQ!n8 zy7W&(QujqE+jDQ?Y6+UOe=}3FG5B9v0s{eo|5r{icKzRYaHQooaCCCeH+1?NF{+cc zZISp9hfQBs4Jg+%nwmArReogZAUB%>o6nFiQ&{{iA$e=U(NyEQ8ri~I`yNHlmGZhl zxQS($!h_-7saX-ib1^k_;knP=)b#ns;eJ4Pe_X)#OW{u|!ltexoOzL+j4d0OinD9X!4AmYtv33JDKOei>reJ){KKyP_TzvPi(YI$?Jirh4O36Q?#B^9AR`ahc430< z8`(s-Z{`TqOL{TCk|~AdPn!2z45MA>n6VCMNiw)zm9`MdLGRGYmoRJuyH@Y3Y<{Lb zZU3wz3JutrSf|PrxF!{EPq^yLx|&DT;WN<3Ymo)^dEPR?9kh)ccdS@K8AA|CsT>hJ zYGPyXrd_|2?jiwKcJYF}M>!=d4l$5=qw9u7@yyjWtn=?g+>QBr}_q zuh~tv85!T7 zd40dYc95+oFaTb_g=&ZZ17KY}aegRQ6smp6cx`#IdI}TQK|5JkOM^Dr5C{XZVfpZV zg21zNcvtRWCN3SrG-}rDj+V*G8b@9=ei*@B|;8T;n z#|DG8Z45uuKJ0X6%PBQOTX5p-{lVTGu3dL z^n;FwQSNZ_#%?z@QM4RUH7X2Mb1<8`yDm{DJGN5YW*Z89x^vIt&1L zDE7w2Va&vtYepTmZqfniSEM{m260cVL? zaS3a?yPbO4#1_LV)LWgWUCZ>IFa`?@{7#%^elR3)UA?&8Q-8q$VZr&SNZTx4ImGH$ z{0ftLUIhU*I7t^{$ih}z7F0xKlDrmR^m9{f^*S>=l4FG5I_TGfmftFsqEM`m-ll)4 zAeWlNW7k#$Rvc94Qm_XnM>Q|ndyyD4^uiTY9^}XwsL$Uc?~%8MN>%pqgQ2*45gSBd zLS?m=94t*!xy3Y5-WZ||Y5u-^V4TRhxJqaR)z&=?(RIvhS<+V5pqa0)u~Au8r7*~Z zaogN+zSkT4V{OP{eaDGBW69}+s1EX@YHREWKle6 z6|_t45i)$(jKC+ZDo~eb6aI#xbD$G6Ovzy}7f$*Tjr8?HCDkER$nuIlscm@RR!2+B z?ZsG4>0^>H1iw~1km!Uxw!@2KA~Ctzq9Amhs<^LvRWf+T<4yAG3*+FB9yLRe*GdxSP6N3jmo3DzqlSOFvN zSP?min3}iLN`B?4+3i3{7lr?&a4D>g-HnvYVFsh9a`F=Hu(#-Fjr%TkK|p9+w9Y_= zr1BG{oGe%)^}J#v-_JgMTKo#~6m$V!p$~Ktm^n2lLwvOX8>N^L)oJ)jxfn;j81`&( z3`jY9%SkDina(~5)fu)Q$`1xfm{bHG*3zN;6c@gP zWrNg!D8OG=U2KtD5bS5Qh-MiG30GVUt|1MP{pPf~5hBUjnZDWC+)>T%tuf0qeb<;bCQAl&YJS_U`|q+gE?6-2-kLXlz!vfr;c0M8lcvo=UNSC+ zzg4*$n>_xaBIkjQ_pq-4?^d!z8-r4Q_nCXoUeWEk;Chn@8Kc^U^XIU}M{)WY-u7|J zcy|bHSZIx1Qui6#Z&+gU&dx^BkUOg25wDQbeH@b!hiSvM*WO^)hX{`R=miv4JnOrZ zQ8d(G{jyTgC0mH)`uNA?3+CPJJPqfIMJ$30fGvA zha_1#)%S4OEU|gc3nF)sxGg0Nf*nXtzSpE?EQ<{8P$`Qijy z$2LOlT%Q6A9r0b+GWnbDZJ5Tv8Na|{)KWXjOg)c>1Hh>bFwTWx^ZVr3{Z%*mtAE=V z3JPwK?@4F1+@JzBk?}lXBWr_KZcE%*3QgY@W*cLVBm~Q@Pz>4`AV41s;o(f9OPPnt|dQ;%{8d}pATWZ(NDb}5@>onG#WcDx&Nhf(TH zCoHgj#nFTy*ptJ1aKd*)W=reU5*vscOzW`>Ernqkgpx~p+Qz8diyVqJY53s|*DnE* zqxM~(Ras+L(4}R)yr>2&p!Y}@a7HKOwiPpRNSB`#96lKebg1Vfj^7y1VpyIY+sxk$ zw`G|uXjuxsICK`aaWNkYX443`*BYg`Vec8#%68zS{w{*Hi-6lS`~U*V|BEAX|5k?l z6CVoNvMBT@+{O@S!>9+6ph`MkYLGLCc*G<|g`*j8BxRNhr8cGd<1Xvc>*_bAuR_33 zynVlf+q9R>e$yhE%z4__PIEuY9DjB7v;o(JqAOD!>$znZB2g+=snid}F~5TV_Qgnu|xga&2HZXZF&&s0U>2E+RwNd0zbA5+(=nsBr zofdmkDuy5Wl5>(8kYT9ANMJkbHapKOOs17)!8;!L?dUoxJiz^ zoe+A|V2|bO35@knzQjY%T8b}{`#O+GiBsRMr}<1s8GOtXcWgo5laq~k`T9QJGkgwW zmE2OLAH!Z>$!6xeo6?)Z%LJ?Xnj1gXnimLsewXY|6Kyct7Dj6uF}Q zi(u$4o|LSkO?H$bnt+^^f8~cjb`xVE1#PJdvtAgf{FLY?oIo^5!=?Xa7-JVPbp(ZO zHW}9|W_gO8Hh`=os!<*s zLRFdd*oFXhCq%oAmam6f=kGIXNo{q$SsG$E$!Y#j(;LGCwr!EPqB-Bie@S(XcN@Y3 z1V2^myTyi}t66UlUvPErk+{+~{p|{8C0T;o3vwOcqF{b3CD6Y&=IJwV;RA3UpiWTc zg0hd=`5T0*TPBY1540l2dWejdri1o`l2GYajgl_GR6*-P)%*%yUfJ>tqH1A-jW%3N z`Io1}x-mc{yN6JfMlA}~Uoi_BIsLEI2Wf|&s7f6HJdT9zF0jWJgeS=hEcr=UmK7k> zJE%W_GyVIe8+5TGb3-ad=)jUcl)36~_40Zq0Y{cbFar&~_zKv>&*G&X+3;((hukq(b-W(*^Ncr}Nb+j4|J%&Bd|%V`j%l zY3SKNJwDY%X+o6L;|k*-B_jAMVeG)$;EzWR=Wv{iZ6%gH%@D$6dUvE8^GB+Mfv3tL zU#LeUEJ^>&wx-XrJhK>p=fZYTed;Sz`>yIEv4G|(_P{jyHYoB8&@TO^^gwRz;sHP! zNnLn{e2$PWlbe(dLPk_ka-`(OrCa#r`^dAddyZEy24c_qI}gExmb|{LKeJ_B(sP1f zxr5IQcbxVOx4q-@Hy^7}(G3A^%A$0&Mi-AC|D0oL4b$)ait&U->;8#*>44#Afsnlk zqQ=48DfpgY+IOU63k{|k1umDrw_}UQlNC|Zu2?7C?UGPOkaB05$9X><7)5%+kY zqdH(HV3rDrKTGmTJStUUJ4uv+J&^r*Ecv7&QK<>{a?jn26RDf_J!jrJznNBS>=Qb5 z5}M=p?RV0^+W39xZE2_}!DsTRB^!5K2OTGZPpG>c&~8Or=g66d?*X!oH+i|~x@is9 z%9CjTDQA1`>@Z2`xUy6Z2e0>r)D#JoaLW%QekMtF&vzO7dT8{)n=*4~l)$rCkFMaG ziW*v@1j=5>Pv1ZjW&~Zas%b|i8Ui<)#Pn1OxQ3I+U3VRw@6)Ysnh(#1zirlaTYt5; z00sgAf%q@1uKge25U|p>u{8eAI?w;Cb~ZfyAdnxRT&IjEe6xfGFbgwN2mnQ5DzY$u zBY9)mguhI>v|;4bJmh@cOAJTw0pd-%op8}?8K=QUdarXl((*axZT)(EKW7E9>QCe< z%1;QifM0@=vWAf?!v#KdI1J-6*|xC<4(_OX;cQ(#xFfX8I7zQv2WN~;L-DxcWUL^> z0SZHiEPv*{TK|vs- zqi+8*=qK5|;fSL}lW47VB_M=cb>n=!o>Ew3Nrp{7T{r;2u=k)tACQy`3h6(0aGLcb zO4{UNf9m24QE7@*U`>B0LFJa7r=~g(rgP((m!F0-bn=m-W9LJk7l6QlxM9WI_PY)n z?6&`>D?U~R_tGgSnxS|aqh86lAh3?*5A4*RdF!a8Ec;f(KYeQdICz0XkF#2-Vtw$P z2vUdSPKMBA2g4dcb-n7^#`i51%k2xefs=nk&i0z)YoPj++KJc`v6)oz`hV~dg0X+d zxKBZ(!P0R``C*RihavpAKiLyR2xd;d&L11rF?Pmyq-LJ4qCESdwRT#PTVMpo_#lgB(l;uu zo7(6*IXf8tZ~MBoDzYldCMzZe13VFEB41>@N&pdl;Acybl5kne&+2-)rnN088hyKe zlo~y@&~;P^xm0jH=SDb%ypd3zPVe>^#yrB6cB_? zHDHULK5Qt;52IyjNMZI0A9wH+)=*`x+R2DehQp#6HI}B)9X)(Qv>8dNJ-mw!L9(e( zQ?-{Iv<4sa-9UsLoul~<#%6J~5LtDlE^Qg0tFwmc9HUcf!Wq{!VdGuLUOFGOMZA#c zmZ-Z;I1GC{(h+WRbsvNeJ0odjZL=KpK2iF>9XG^KPxLabJhJOOP0;P#W$V|jZ$F0e z_|8*uwH&Ik!&8qj)er&LER8U5%{OE9kWQ3XzQ)4EaxO}EZ@)MX-vwK{_n203Omgo! zzlq2#D1708ted2<(Ff?aT{zy)N^e0r!+VAX*f=heZZ^L92+|)cud_@B zucU4ohfRS8y~8SMlb=62%x^%Qs60#0-c)xq3Kb6LIX~q@*qZCsuxcw-sVfW%^#U~F z>NrU3L`AVMarZ~c$j;j$uZ!MsQL!Bw^9+%<&sX}nQJozHC&Y2IPmsF z+dql`eZZ(Fupdb=Fi2!@H3bC#&`tk~^K*l?Z{8^qz>uc!H89?LYfX*837`4G)|qqR zXf>2?_VrKmn&*PY?B)rmc}Zk|w4&SA!{FfH_lJ7HPh4WCd_V5YeNL8xGQ# z<8k}%_EGCGG3Wcuii?CAAn!OSPeqKeF;odbwu(+dG5J62}cCN<8IY-&%k3JKfs9u zne=AJz^P_6sN z;J>3cRfA+%IG%);qlFYhHfUnJcF(V~C{yHFEGNR1r+wrFm#GRYNQGrYAshyJSdSA! zDj;Udgt|y@KrtpNbQQV*V78jwM76ta@Fm44J7x|w-u!4D`Mqyr+q9^huG5KvjM}(% z1gDI1y?eTQyPw%j9lrKf9f4GK?twDutASzcezC5DtoXkR2e~yc`u(`rGja7RK>)3T zK)8D#z#IJmE3i8&2So7{fdQX~wLV7Q-#w%$;@%XS3Ce0nHQz4okO-~E3u_~lqroM? zxZ?%{K}WEc7Zxha^_!1DNqBKc*>L?&5=CB#s}f#nA(Z;Bm?3Jx`A}7;o7nOtF$Xce zVk_9vVWmw2W5vEI?V7VnX6;4}FsC4iR7qkQjNMD%F3Uv013Bf|lFHB1dj2~(JgF5L z5>)njZ5E~+(_kG-A;Qp#H!IowwK^J2^FXvBJZ;tJW~f!B)qU=6aYHtainDj7O{FF? zig>&d`2{6dYUA{Z1Qw=qy@jO5{67am4}D=KBI`ZH7h9cuI?b^Xd2|xw5z%}=EUEciSc8)3h-sJiG3QUoZF@>F1p!B zG{+!^UycOG1UEC~P$|!ZER@~&{OCyXcE>1`Ozb69o*qZbsj3|)Pt{06^6NB5l{45$ zGK~QRNJ^w^=D=eDR`hi~0`iI}DGSm+V-@%$5}EWoOJ;2a`aS{5^+$^G zAaBxBtYvn(K|j;NlAT&WC!C{8tO*fiOW=ez^XMBrS}QdAHHm|6+k%44;_WCkPPXE1 zKy#AnAPIhk5^)8W65|XpR$=_SrIPF*;tD+`?(!r2t&?ayE1Kt^#Nw;ORs)@8U#EG;uSQNy(!e>Q{1NaGby;~OaeCr>C za2p>KdmA3MkHj-*3VftZ7zga~oeQDR_k<0t3&eGf)CkO?ENtFN87C7eD^BFx+~&gy z7o)JNNWVFw8ScPdrSW-(v1&l8stK;wv5 zdrZ8uPoJozTS@_yPP~dZ)j?&}u36XN#{292)0|x&4fn<>$??QVP|l3%c#!cjsZF~9 z-fL|RY1Rf1hmc_M($Hq4uPtOXN?WQ>A!Bdmn6Hx&a6zx7eBklNa|J`gv)TI3Y*ESe zOjW#_gi6}dkU~~g%X*|Is#&eEFIJOQpcsu^GRnRiM=@4JB6rU73CeYMj3Li>`1_%9 z3W$^BU~z_AsKepn;a;@lqRwS{JY)+kd(q{h5`evo`+=_AYUFH2uqge3NgYf3b4yOv z4e0)KzaqWoY3u|!s>T9K`QF<4*-2iJR$vDvV@)Lk^QKSIThItvpy&~nD}K;a4$XYL z?;Y36bca{C5|65+1)j&Em*{i8tPc}3o}as9Mr1B5Fo0MMe1))u=CA9zUi5+=6F&a) zkVyZ1zbV!goUnQx5xrHq&5oAIx z(T!}O1yaE?XqPeRJQQ+!=vD~{b4|+_rtu=N+Be88EC&IGm>|XN`cU^0%{fe`t0Xx6$SrYI zS&4kJ-Q~TPq6@-nW03Yp$~B!aFPPCU!DQbO z@ZTXiShj=LMV}B-ALfb|ME#d((a}oLzs0%Ru(@5TOYZ+j?&nxlm)O(hyFqdbvungx zFOqc9U=oU8#PFzA7orv_*JL1?Xz>Mx`A}p>mZ8*(Bhu*QP`^ODVD}8s4F~7te1*hjv-AdRKpw0CrU4g#(oBJa$UZQYRe5bo=@xBErQT0CB}HkY-x}!M`&_87w zWoeAFgUYjL(S{@DbaJiZ+R^SDrY3dTDq||;@FgUfeD#n^9CQ1oD&ax8@Iu{zb^lz( z2CzA5g-&I@mO}eEktHbZ(NVOcg3uKBsFieJ=WX-DRL~+ngpqT$sJJL2AGkX@T_Lz~ z)#VqsY;h^n_WS|SLhEv}Q5CtwkPwzbm4tk%h<~JO&(;j%^=b(LV`I&}WUZChawbkY zAwH~sqv%eVd4mFL3)-Usb9Rw0;Vv>#pQAOlh5%$=E+!1_kBp(;*%oSu-C8N4S zqj6!K*;qhk<~sdELX(zhf`$fP^`$qMt9h>Tc#i&*CXSfz0()$uE}ZFYeTeBTjy5M8 z2Mbfy5GGhi2#yrobDZgfUqApjOeS;-(+Zh|-3|rW?NJZk_HI{AGBu~Ehm~X`Liw(- zR(^=f#N`h-W3HJSq#C-%mJr+viWeb3=}Q!}w@^Q@<+o-pOcj^%wsTD9mvki!Du`3ql6Aw0IP-FpErMrhq-eMVR>Lk%DX^$? z(TumchKtG2{e!kZ43m+%Gj_f~Nlw`9^stB3d~=qvBw7RUrrsO&WZ#)^_=na9X=u&SzMCE}PK?ES~q{5z0wm} zLD+1rIV3h$Q=2moWf0Qfq~Q2#H~QFSRN57ZWaCy2qL_~!}GpBuYGL%=dw*- zQ7#_=HXjkk;*5w`hKT5Xd0u)Gj2y8PIxqipYkvAqs8}rgiFxv zu$sMePq{Z7afwff0w0SzlkkHpB%mjI*sb96Z;~2| zMJiGGU#yJ#t4ZtM&C36*JNyMo3?KN{l{g!7PytC8W|X)%zCxvPYLxZg)k=Aba%FqV zBo}>ib>W@Z#jP^exT2+uK2eM=BK7d-ZpgT%MqPLP+q>1zF@=lARw+zG^-lsc4m?}; zInVA>?>xs=KJDKCct6D%K-i7~M(S;VRSCfCPcog(Korj8*dqJtgv7e?sus*XaaR>S zHSQ5A)rgP$A2_gDNYG)pc{>tNuiHOhKfaB8)X~|P4Ab7U?70gy?=?t7CC{aJDi6_^ z#^W5PPh3^tH1pW4tI{$Qo6HnSr4?IA95bxXwL0h%TU8V+JKX2#F00}x4k9RViZ7~+ zQE)V;(Wn9K$qmeo?1|fA-JKR}9#eI=oXo~(%g&OHu#U#|%WJAXu#h3yUz-f=)$_P{ z*cwZ^3yU2x+7l==PiGraSSN%5>eA>#>cW(O(GmR99FeBKk`nSXOa=x8qha;RC*@Kno%-rWua6J-Whm4kg0$_$New+qVkKy?Wz%Y?eq?#D?>}Yo3B4+x2XKa+TP77NE=U#>iy+M+$4BHP2 zOV4cmC?COC9choej`27l-;k$$3)`H&L8Ac+0 zN&HAgWLtiuPpXk_nn5dyCIZn!b8;vtBZ)mEs#`+`o!AjdIwO1@3RxGM{qc_3XI zL`_%M;XvR9C}UQ$zA_pJDTI}I1(!kKP~@j8ePrIe;~|nQ?>eG)6?Sz4Z;kJ9k3Rae za!7L@6XRXChO+J{vn1KcqHa}?U!f}x!nqG7fBWT>9Ab`y_mPcHkop@L4KOx|LHi#( zv(;%MUyW15_(Kl^yAajEJSn8Zd*!!U;*Z~8!I z*Q~wdLND77Y=8Xiu$7Cru<`95KtMQuVbb*9j!37!8VvuhDy^cn?K~X{w@had^(wfr z+>q(;(AytTJU1MHfN=3diXbJXAU6j^+WV2LF&Dp2O1D#@xR)OvWCPA?{tHj`L=soG zkGI^dP_KbbiSX>)5J28!}mSsCRf|EecAXW@7(?tnS?1Gh>oJYBC zNe;UvIuP{HeAjrm+?%nmfqvl zQO`wUq2B3aHjdJbyFCg5*CiVm{8eqKr$iiRXN5{6DYImg$O7@<%{c0Wds>p7SeP{- zfftewuVNbU5{A+b5{IM$i9r%u*`BRv=2vY=GNQsAj)(MAxEw5kgC@woinp$)43^&1 zYYgj2wUf^yA9CB22`t(MX40Z>7*bE=%veAD-t@kT*I?_TRl-=X2xpWPm2(E?b) zXLuwYfcc+y3mNNd!Svhn5aX=@rqoq4ZS48_sE@y;{3_K7@!_=z4}O+RW`}soNBIvS zxLr@nRiqB9oyF`XViLZ*sSdd^Es+!Tk0z7{g<=o-;;N1;K`Gbyj|<~suLwN;Eyldg z6|d<)fPfGnfPl>Y|B12xyB4QpEsM;L=z~U~dPbn2OMZW1rfwhgRi;GK6`l|cW{Pgp$A6b1` z(B@HCYzYD|RYWmouWHcRY&FHmmqghME6#Z3a6O zg6rLRAFL~`r5Rkrl*0%ryv9$|Q8S{P$I+gPVQMf6#$v26 z2!3;Pn)k{%qZO!;rsC?z_dlPgK@jh9O|K`{Kf|I(P3-XqUh66_@o_7Ed@oPkyyONjwO?D!Iep7?$J@AlCfR3dm)pgz zSu%x0PJSx(5^f^=!Rm{mP9{-X0fc_-krZLzZWk5M;7tt7dh#3z9fP9^s-{<3ZrEFo zO1^SP(hTpgdTRcPKakbFBBQ+HWu{EmdWR9#iyuLJbp)QX zp7bGcVdFQg#&4V8C2?sw0lf?_=OD_a!fFqmyu)41rV?aktFfgS5kelG_A0@R#rBae?xDvam*ULJk2gjYh0EA)Eo5Or_#y{l*ssUGq$` zd7pnXOZqf^W1&F;0ga*q0V({Ov-Y3)nfmUCt%^F_YdNl=zV(92YgdO$N)%1aYeXuI zeZfRT?|iNzFM*piAfvH#H>4w~Ltd%8Cx-_%H-yy$n%`#IKB1x?z`OyruSLkj8Y-4gcm$wIEW3 z=X(ip&=BLjfw}-|laY_R45MtBqxlmPDFZn(cXdXGS!XtZr97!FLp(kDo@9o-4yyw> zcaHQix?1G1LP<`ug9e4|+0vn#4D-=31Ldt`WJhuFV^(q`uhl^+bK=2J|ER!qTF}s; zg2kEH)ukj=Ir^g8*4+GU5aaZNGhSCIS?5?4t4o+fD+balZ*5Iiw_dIRggLqQPp^;;6yq5XS~e)DyR<$%1M?`cg|nQr+IvZi3Ol%O)Je zu<@&=2NMbjjJLU6=9oa;CB9XeBn6SaIjKZFqHIAB7ALmY zN!f^UOEh_XG{ChndMA2OOKIMnqp^8l1v}-es!D(gLCqv_6ec5CDV5xgEyW&F=mZg ziXRpifFv4&_4F|HM$L|0CUESVAU@igM^%iP+Vob!K{6!~_WHQS0-vN z>tRoxo^?#(jOW*2q=kaD$jw+_K~a3@&>0dMT5eA@>)JjjAYKn0)|MH9OQFDu0|mWct%5y)4OJxWDrBc%Q`Cy3NaV z&D0fd@Sn)p@RcTQ3?z`pOi-er1AEGMth)+#V86)nyEl48?>#(p7zc0E!F>C3PXX#n zOM@42Yyk{5WNqQDZp&1<6Kt{MZs^zIZKY5e{UENyQ;l-nr7Cb=M``PzxINSHr=FEY zf_C>q#IAEl$b;NP`BZ~+tVvtJ?xypRjq4IMV_@V`sCNY!k`|OHpV3T&z#}$I4^OZ2 zjHa}eDi-oY%Qfp86hT?pt{r3H;?Z8G(WWVln1IQ(i5B7r81^=l(zN4#3zkaEE-+Yd zBDrtq=Yz2Is$}x4(4jkP2PHjr7C1}vJBy6urM%@uDKm)Pa8G<6O@i>Okz;&K`xK<{ zj1@Sg-bo46kQdnXY;PQJ6jR_3df*^|->D6xCez)ym!COLAXdC;EIxhnUqtJLTPzuL zA%Qd#*cVWZIN{ZV4>T4;Q6U^^P?BvSH1dhWAE@!O88I1O&I>w%(kE|cqTgjbQ^JcZ zZrJ?%$(&>yBFZ2rc*BBEB3H*N@C)_MBRR}_R--lE(4m*sF*}ekNP-JPx8@SwD=hqv zp=F8mv-dxSGj1U9jLGA+4akk)ezIE-l0s*4{B#lxA(yT()i7wry)K+qP}nwr$(C zZQE<@c~6|_?)lC{M@;lLKdLG!Bl1^eU3upn58^4fq@r<+YQ}L%-P-nDWpYXWI`AB1 zeyhU&O@;bKOzUe6-m|(J8~@<=Kt7gF{OgYSmk{4?U>DSv-v*g4h#Z*WiduoKJvj!I zvP{A$FgcI(L{cf2swn$WF?U}(Y%wB|VA5jpG0e{A_HWx1Y$uy=Esc(?lG81nwr^$Nl+{Q` z=oQGxlr^-~9hr$!;a|J(`bL)?az&<6v@w;nXhaG8Lfz49uTkZ6x1{#bN1VTn$7~=!KsF@j z_%2C)%F+8XTWW};KVy(RYAi)hMDD9kMCOA;u)tb%(UqH!8M^Q)ZNpM3+JigVA5dkdx{<|PlwS-g7=`4^eC~F#$~A{>cDwS> zzYY)0B7=+Q3^+z1l4`+gUHu2OZva9bK(WiA^qSB7AoKJ)(tZvt>yuio^*f>-6vrId zYuxdtc(3E?uTA{$r!>l6{r+n>ZEOmbJQ+LyfOQoBfaHHeg_xPxnmAe*(f-e&8J(%r zBMPLQHTCoC%pGSh#-6G_oIm4^ZTK$MW-HEf8A)Nu4YDP=vOa^|UQeXJ5{cKc9#aAZ%7Cri2n29c4?yzQwQSw1-o&&D9rSO?m_IkY%#)jta5p6g5Om5e~aFh zXkR8MMZ+=i-O)@`pFgAdfbu3BR;k+r`fl?iSMMzdep==Xzp%c-?V(3id=N zUk2_kKS=3E@|jgxg}Em!!Po#N?8xp$Z{ZyfB6tOrW>dfx%Mt=WZhEOL7QMc zl;`0k(pQ+AUj~rnX)R-4zYqgq$B`L(`*pQ&L7g1&cJWFmg7-HLqe z`OuCfJJ!iYDe8>^Viwcj{u%24N zYWnB^^eOI~iCY-i1x20YN?prZUx7vw(sS{!Xt2tHRQ#3H_Sx$#si4@3Vm@X4qjDm{ zg6#+gQqoX&dincv!W{B0R+wae1||M29vtM8A?GK5N_t|{FfXUHw=^U&FsF^>1|8|7 z!QTb1eD^HS?p(yYHK4dg>#iS?9+`-wDLTeYdje3Qlxa$N_*noVfZc!cQna+XN$I?o z-9dVa@q5E7-V0OFruGW2D&`ZYzqtqFXGO7p%jRZb#|XyDvT(Pof`LRT>XSL49EOfi z4_X6Zg&fASq(05{ev#{pR)i{jm?9e$6xHg&P-2>=9~HA5k;Mf^T5?6m5b-s)-A#Y| zczti=@x``pzd4SW%$q2)e!$~^&ANJ?4LJ-40{-@gokKqO`TeF%YRE^YkYD%Qb`*dH zGj)Fmeus@&1WyVXOoO`#gXE!%XT9K5ZUE8uTxuWQZL=q^WP6Sch zUG(3p2UsPq4`Yg3rXDyj7|sxNw`G9_a%{3n&--L&3OIH>x%cB{|y0+F9^5jto6>k>X z`d-746irx68zHsu!7|s4s#@P`kx$V>&nu?a2R(4!sj_vTBv+l>HF&70K^3RVcZ3|J z`ZY^o;e3XyD(AmYC*&_)?ojcQQQELXpdXIbK| zezEwbAfvc7Fw0PG9w<)}B;H^`1rrZo=eor0dS^2fP90I3w40;XsfWWg;718K5&yl_ zKM*TcOeQ`kCtmdMV#p&%uRM_#p{ z&xqd|HH*LL)uhbrMx3m(w!$QJKS90fzK;WdAoN!Y=B{M=9m-?y;AnF zX`bRCWAt4~X&()IDco0)P3CyhUmDeQ$wUcvg#KMCrT7>|*Hamx822c0;7wJ{&7UzC z*<5}}d7@PHnIt1?+2<$b+&7px%0ahvhnVB!wnKnufj>2=HdAUFBr3vtKKCV@bX7ji z6Ps4_(a$0xwJ?|TVQzxe7cy_|JGUbUz39G?2e0YhxqR;1oc(g9V=QA^jgVRWwIqq1 zh+sMJ(ebwgRy4Ai?T9|=q`c&;SOR*(v4LR*JY?9C>5D1n{vv1&Pv_IE=Ng{;YQk_g z&z1UKKhtdLjCy6*jN%}JtM){=Og!Z312jv$q>9wNJp-?KNRbCMr~-SInXM>1Cy`4^ zTtm0{@^*MOT1|Z<9UUG35{AW2I-BWD7jh1 z0*N(9hULRd#}vyd35Aj@v0?8};#6zQukCq=YeXX&47hlm z@{!c&7e1(u0~wvwI~*d(;qnsbg9m8EJsaij9vdHWdXJuQ28gUxp;2V7)NGKaidxNQ za~Nw?n+t8Ft~ouEKkPjaBaV!y14Z6>s}$_AQCmahKt-;tpX5=yllIj^J&F_l$=K7} zn{Fq4{W&Az#ovWk^r4bX1T1G>g;Y3p?1MB`}` zIAT7Ved!a-IdMofxxTGk-%q1&T`MQ2s7k^xK8W zT)NK*!)Way-{uI8FMJo_lb0;UsLEI2h-+|ZD|WtB!w5(#u%@RE(lRKmo7yskl0OG% z2FU1$H4)V{eY2W8e8JaEZZa+@zMqd?do-11^}G zy}AhGWsFq_eO5^v@|@Mh+5T2}4D4*X**YHX1+**{=$M(xzNlJpKw~Ub}e@rm9s#v0;%KtAOBzZ`*J&qK-tt7XNH2g1f^U_Rksg2L66S@ z_cM%AIOUtGvV2Q+r9l^aht0);HVZ@Nt`S-_REAjfDU_9^Q#BQYD!$ApgxXQrk`>b?ay=`Tai?F0s1&K4$QmoF4z& zoGxea+C2|v@mznJb9j$O?U4e-n4vXF8NGlZh+^tm#EY_nn!#&w1DXSA=_;G>p>q}D zDsEcB-KW&N8gJ}_@^#4eJEX>3JLSMq#XZm={EbSwu#dPh5jeRN6-a?#dg4Ux3+&4C zg!3Z^u;*Cf*2^L*)n~7Ik?k2xO~{n?1lD}P4-6AQY)acV+%y$KpJ*m*@BGi5iz7C3 z%(3DDX)0ZzyAG7psBN3XN+9v4^7^I-}b8^CtGv52mUB!!J_PM6|2ESNO3uH2a)T3AZo z1_=Mu%5PZSuUNyDP609rAp@JMXTw1SwWWtb7VlK@q-x1;%lhrAjEIXT>(M-gYe5f4 zChsrFB{~^GB5u9ij@yR&AQb{A6(mAc2Pu6E8OmP!(%OoppifxGq0jJE5g3s!;ll>b zm$^{SGnqZzYy(ibMvh4T%0cScYi?Z9yy08}#5vz(1=7?|2FFVidNQRC5g-1}FU3}& z)_=F^K}&r2R>BrLel3HkHE3A^OWDS50yO5DD*AJCwWNOx2EZPBk1c>HH4M1ZE;gWx*tVf?D_r^&U zy658eW@gUqtn}7rPTvsp1oG+LLUTtl*US*5m-dzpH_tdV2x}x%(dMG}3`m-iGgc=uxnng~+_EmNI>+Zav3fq34 zi_MIRwa;B^h!)i&wwZ(F)9GcpWb#8|HJ)_+bTWu>J!G!rEOmi>qlPrl;2K+X$8(C<&soWy*amGdESyZ92ySd6&Qe~wY^LwJ&B zX*O74yEN4z6;z#<#`ynb8AThs*{MlwWpDg4xIack*~c-$oJ2uI804$qP+ zr<}V>*O+OODEqufWPo*KuX!V-k>6g-<+>{5>^3o8R4;w}-~SO$V?|iEhXe!wU;^^r zOHmX5_hI}$Q`D4o{*|J}yOe6HLnYBbQCRriLb?#a_yB5Xh=8z^Z&4C@-@FsMRj`#| zYSx;XcM^q#`~&z)akPU5jihV7J#(Go^_uH-yUYD`_4bwrkZZ6qP#A;_O4HFEp+#qF zv=I`-4R_0QyjGI<3M#x>#e(68-ARgJTBQ`RQ@n-p;MIqTs8o5`ABW{jqx=_RdZU#E z#;%eJ?xR2fw?c-%gI-E#(*b$7(JTfcQDc+X)_cYB67cVGs+!26#!y=bQUeU%x9BRy z_KYBbr<$b+PIbKAnj}s*Z3a~898xaMr%#?iLn;MzS0Q0c@uV(QNe%yWg4(W}rWkcE zXT1<6kfg~)dVSI7nL=t9{_=uNk~7F#P=1wGH(4~P1-0gI-sP8~5a`1b(b3U33+CQR zg>Ch>`5c5HU!OwVDE{RArypyE0UeHD+?XQVEvf@PJPr@dA!0WXR5^pSY~=^tYEY4K z2bg21pRlJ=@Z^5j5*q~BT7_phgLnS$)%Vh=B8Wphw zG3)A*N1mkfCjMk}&oNkG~7d|Z}p^eqrw!d>DUl*eO0 z$jUZ+{f{-J@}#feEq3XpUcz4)JEru~GHEoHT_zqB3w4|ZdrW3yGm8_5jzF5z&}^jB ziPDa`MB1gyf*+t7D{y_l+W%)N*}tqHtd8MjPe1?wAJG4v74(14`CnF0O45|v5Cekf z+Ygj<{VaIB6S6wUVi-~wtz(jf;XQFAuEH-~mqNLe3On}HWA;_X!^?KTV!r{xIpi|8 zI@p0uB+0tk+NnXoK5~n&O><=Ur@KMVAMY`_GNm;FqHgw9vlJaT?d5X7EB#$7Qo%n> z45@!~Ysj6=Fe*Lu(QGGDHRJgvVqfGqBC3BnclrcPcr)ymAlYowgYzuaaE3$3z?mVU z?j5s~6G1vq<(int1#O#3>ZX`x%;RY$Imkp2k0)gXADD=fa?XMtaqGYcXS;HRavpvn zV1+Dv5ZO|q4^u0^n08R?z0K~XOU#tFnbilb+6fz*inhSUy}UU%@(Oev0LSLkoutQZ z8}ssQ$E>jz5lp@ohRzNZrv9q(t-!48xBr^*tSUX$8~6pbSe({tP~P-XOiKyKjC5im zO_H_?#_t^3^Fip^KcXLybRfw}h*5L^#gmjEzkHACpduR?w4jj{+RCO-D?qSUe{wj* zd4hR&e?b3Nyp*>)G9mo`fETHM&-vfsh0gl_jTe-~|ACjA`5T!gX055YN70ywUjV=4 z2V1TG4PItO5~Hv=X}tcy3!bgfNKh0v-7S-hodoF(bZE6+B_l^xCn3h^q*6pru`>F@ zS3Vx162%omxTPSCvL6UnD=F-d?VmAOUlJ89DrBkMm_)Lhc1WX*7O`MSnj1#;0Lx{U zJU>fGY9fp3gD&COu)cAwptcow(KV@>=qT04Dy|6O23qBT!#{&bMfvCqG^$7@CgRJ* z2`U@XCC#v4oQsoMl0)VrZJ%ls!UPpGaS+^9rGVi)^=h9*~0Ahevje44TdDIKdWPl3T%MgfT3Y1l*#dnfrF2ajT_Mu`=)4bMEB~NB_$_8 z`1|62UabWZ6%GcZT&+Ai&AjCDa&K}r_WXQ*ff(XaAV%XOhytxq+!%|7c}Z`n8LNWv z#OR`VhRW9vY^lcT)mn2pdxuWI33ZguyHfSYW!+@OuRZD5eZ<|tJFUhwS9aIlum+D( z?RnMx4%WZG>te4-2er|Z+(AQMocjcrw_pkib&g;f8sobU*{ilUm@3g?NG4k?G`2#C zlYKZalp+|-=ZcTz7MQftl0LfSgOxlcnRkzMhiYviJ6U-46yGD9OS_G{nVk2KQ*~`? z#Eyyjc9 zgh6wV>cnYJ#~@!}dC|A4T9tM6+qW(=j^8`X2Xh01C6j9}WEWT~l37MW>VKH`N z8(%PW<7q%Lb>r%{FeHCIO=5_Z4|2RHU$i>XZ>EmO%C9r6(}pcciNP)%)7ji}ZXWJE z+i~-COyG)6R!NADYIia2+0q<4rk5H~36Y~kjj4_#u}R4eahz|F)%X*32AI2^Vr#r| zhxW#^25an3NTec=XIOh2;E?$XD4RQj?DLRaWDI5!Yt3Lx`GS509L(uRSOF%kK5~jk zf$=E^J5`{|m1CRrkk6_PcC1316=RnJ=)MkjNOH7!9t3wew8{-&ic!tm z9>Kj3e)dQZ3uZ<#gs!>->^_THrd8+^;FMPM1&sekCB7g`@A?l&0DxJX|AWRq|J$Yi zKhhyT+>%!?eQ%A)^q4-zfsmp%!y!mB{2}+nkdz6lvFrno0Nd!3;*NtRWU@hpu9VVN zODwepNL#21*~;4${zSJDMYt|(;$Pe8#GXB; zylP>imYQ{vgBU@U>1q-<{+=>j+LX}`==hmaji`axGpgg)=SW08y&{|KT)vf}K zO%!21uX`fFos(d7j>KqW$eEgMo^(KknL25o0aInqJ7-)^6D3x&12jHtNK>H2l*eUb zHHJmKI@M}ih)XS_9kxP7Cs-6vL>3!S%Bp;n+Ytq_(M1;ekQ?wO`;FFT1kLgQ7@PHu zK#oF}rva^;z9=ow&0%PWf*+wQKHNimc#i1s5k6R1@emdeC;N@($M7KjJ<(5o_<05E zZFxBNE~7odu^qZ4`<%Zff1%|RhQH0I$+f`erc+g^GCh?Cr9}l zaYTAaGN`V0%9e{#$(l#34pqwzzCDh#@-ei zwB=WX=WZ9fYY071se5R*Cd*kBj*PD5SYyeH>M2uOMqzB*f2NAsnecRJSoC|UJ=1SG zt)JKizT1e>qaI?ZM62{fno=*7R8P{wB6VBpqq6(X=FcP3E*NxPv)h<>E)yhD!HHRRNMeebB;)hu<}j_D^9Te zGk4Bzjc};WeI70YJVDk4P+I2!Dy2R^b$w(?ouQl+IvVXLIDt7dtG1e>?c=(xMhaPL z-oH&vifEeZ3W>ZW0sRKa#$ZWV_476s!-$WF>b+j?6h)tkD;E&TKG7V1KhUIHH|Uz*el~OsRCG zS7vsx?jWbJhGkiI;Tfwo$Um*T#T{-LEZr5V!`*c?X^>uVU{OB=o z1N%CR2h3$B9)zR+IpipUV{`#u=6^FKB7K#mJNCcn?^}-gV460nS^mL)`a>vN9aADR zWR;<@Wsf|#9u3mE4`@Atw;lb{4jN`pJ+m7s@4(F$7D7K?f)ZGXvNJi%=0fMW{Bf=d z$YDv@TE)6vz=f!^HD87ktQx3(0dT7@M^yPrSgI?e-6Kk!7-khtC3wZYFZN#Ug2tv# zyv8@C>z&teQS=LyIwt1sv0Jv*otRUBbU;%l@*J5gL9VW8;2VMsyKSuhly=Tue?mRY zUauO}MEMJULf&voGATFsL^|2wi*#1UVE-hEQWkZs>v^7PoIraNDUSYUs4LwI=IJrY z+F!JlyDC$9X+>K~zfDOtrJFbL@>iIP^!sN1Wc3;svUn+xGBx@KCxdh;abmMx|3J~( z4S`f9cp*r*>~}PeFqzS)r-*g*p)ly0kMx>is=|#Dn7(i7&;HbsPem#n*zIv`mqJ|E zM#$~Ktk*^DSNOMY%&$^sS8j}QwjK?{5)W58h)!pKGeNlROd~KGa=8R|4_0p-!;s4- z&!tqv&N2{1XD;5(v1n;6QgWJI|(eWmTo=FR@Rw|Yz3T|MHWh%n>ILKWidCC;=36?==b?bY=A9iBjS z2dbMq@q+HbAF_U@Wqa|4d%Oo&|8o1|Pu~3nxhMYk*#q#U>El|2N4B!tN(+C5>u$lCT-j#LuU)MpOFs{aq4fbIDAR)Y2P6f zJc1K(B-Y^2T=4ms&J~?e2rRSh1Lf7?HgVtCS66sR+n0PvB1# zB760W_Ts)ZE7AdAgfetVLx{7Vf(!#ExKYNqQFb z$S9nPg+w-Q?#%7U^|5}j4%{(f&qcL#0UhDo)Gg$Obi4%S*3fSyfVE|J`UGUqOs<4! zbL!)cBGzMaP8du^=`tyGR(I>CO$Qx4E?dkZ zPxbF~11ug5EuP%?HFGOlmo_}vu%)5}`(cn^94LI5oMwZJ8pxPI<_#+SR!JY{B?Sp-JOUk6S4Jw2up5bOL$Jwxw5As+9oeH!P zCsnVB(p^yX>l{j$*9sruisABpe`tbQtpgpO9lL}5q#j3CLaWY#=da6ZY}b(_i>R>G z7DdBOS;Q<<2kxHw0tZJWX<4FQe&fnv8R(qk=-An2k+|f-(&r%*`_|#>@wKc}}2JRfTR7w;Zgrxo~*WbXnY#dry$ip7>_w zL5&LMVX5L$MM}f~(lvz9b+;GP8rerVcs<}|ZC0ChYFwL_vlT@kRJes!cEB0vI9~Rc zOKd~9T&3v#Ztrxu+DVd|F-vLKCDp@JnXb}l%a1>EbSXVeo86LA7|)3r#>b9T+0)S) z;Lr_RG;PH(>B{jcsCN#vEuKj9lIr#macBXf6YV)(ua%aDkhGnoqmyExpm-$&_Z+H5 zANr=ch;9(oG`dWpm>y58O&)$+8OAG?uC^ORC5PMOs)zigBa1ZdrgnGy%|7DdcLl#W zKDai#!JZs8nxpZE3)!Ew9>a}^r=#}kGR4Y>KH;ZDb*YkM8A+z>SM1f$(BAn5o-o#* z*U#+&XU>dSCg~em%Vks)8Utxr9qLm^TJH(N;Te2|VJ#BIEQlo#qhP{NglqL{k;z?y zy@R_VV-B0{+EF)5CaTEhF~i&JLKZ>Q`j3m>uQ>X>cYj#lw$Rrs#y?vt%WXAi+bTFB z&oq2ar)y|a7Ujmh%MS_QD#9mCHQ0z7IRk~O4STMN0Us+TY2n(pH|Mb5lxOABql}{9>gyM2bcp$X1yO?&D|gR<2w*$X#Uy z-r_qRe*R+8WF9xXF$`pUM2KVsD5L+~QaBM+d&qjH6}>L`6UC>tP>B&w|H@rf5p9PMqoXsO)4|}9W*W=D%%oQt9$=M}c&E5f7MaYTYQVU>Z{Z!V!4gIj z)fUA+#MGSr;Gf%wN_bKRTKWRpkD~Ps4oN$8bqAD6$LW|xx-p)$!OL8yG@9_eX6Oqt zv!N4eS!Hu3LFLf*L_Sd+*cqHQG_qj(#h+6NHup)cZw#-ZtgvPbH_18~Zu)vTM$28Y zQ0DF6r7-UBmCw>6o3WiBtw!@R$$cwpUoiCk+kcI`Ac{Zy@}&63bKu!PGJ})KJ-CHN(bi z1Qw%QWu9m-9yJZE5FkNhYZ3Zwr^xD&wP>Lf!4lRR>hro+uDHXZ;bxkYAyew;E7&`4 zy7#kLKD*iYc#>uC|P@4ux!5urNLx9Zu)l%F* zKsIxH&YC{QwC`K+aK_58;5XIoL{oQ^Pl{gHqwQ-kUS$bh zYrCg=T%aDXuZ#gFDaY%BF4Z~3?wO`2b|2|?DtpLmfg{e-+-4NXZ9cC9JWw}bMAhh@ zbWvrwjXUzAY=3`mmkv$5eandYS_>G}t#y^Wz)h7u%%Ph}_fgEUW{sgr7J6fRcpm)l z4)iy#`Ly$#hB-A5>*CJ^#hUv;*C`IS?C(GE&lJ5g_Xe&&jY6()WPBpxN$z@fmD&uU zowo`Xo0Ff6+HOd)1yLq(0zLjAz|H=mX>|Gz0^@CL$9mXQ&^kO}r1JU~9JTz!ZZu79 z+X$WK^xF;F8wpyygSx$rP=HlDAlv$P=JjqgC$Vv8*XGV{(M1!w=$VE(POdsr88n<# z8$R7E`tgOAu3-*k2)W=M5i?8W;g->o)CkF}?S?x1Vb8k$<_s_I(oRkmumCCTGu z9ae$#Pt-Ttg`l;!7QZ-ZIU=e{M-*1RKvlEE+JU@fi~^sAVYkFS4xzhZo=w8=@w?dt z?1{VQ^Qep1%+DSq?~)p~2#yPutW@j0Ib!c{Si8a+B3HVCc3mO6t5Wz^177t>d?iaR zGMgTw$`RaGh^aLYYH%t?l;3o7FZnj3ztkGV`)`(3R!=Spv3q0U+PK|WV6-{Yu=^mzM6C!NR|-{K|P^H^SIy7ftozi93KX=eA#ZI z)b1YmCu?z}@4(*$UZN=o$l4l(`-q@^AO-tzPxl|^q4-V@A%xau&(c466QSWuS?*MG zT0gZ+*&0s{Oc9<;59rJvAck`RNmR zIw6vrzsXN!Yi-2rxL)*BO*$*@CRkK{g2tYs@b?$3r$$%l;Ftj zj&kK>M~9P#CDZ{8-pQK|X!2O2+lPC@E}u_K6t8M=TZ-L_A-{CX&&iy9gAs-sVDAj* zdID(s%#k6sBS054;Hx(5>KVk<1dj%;%GF0z|K~KTKI#?i^V5K5WPPshQhnefJ`wur z*$3^BTM%`7S4G*!;pw7FujO8$cztunW}9mfKa1N1hUZ)GwMT8xbrCYXO zDNc3Uhj+xtWE!S%-iYHjtmb+jq+gmW6?6vg+@r3E#-4vvJSJ~{-~%MpD&^neB#x@w z>Dte3uSCs15VIkA1A3J2R;__%KZvOvVqYlmuPnU+9F20CLL5l(Y8|DHUl^{|Nvypx ze6lHT>l5VT&dX!OZ&M`3=s#7O8jCNjOnDIW^(?*# zUV4TG-$ZV`$vzazMom8y2-_`ibM>*5g1~NEBToJ%gU7JHH4|6Rll(}Wgs`W0ZND`hC|%|L zJ7*1L!KCu}6YQZq9wT?wU7_wd^g;OfQ2f)(vsm6Cpz5m}RpFQT52|Z;cCH)Xq+VHX zLs$t28?+kSZnlW>f~)=zOb?Qr*C|EoN5RD6vV%#zN#R;Q2*6Z z;DjcrkpTn%fC2jNMT7iLGr`38-*!TXqO3d+BSKDNKh-;+?`Y6My2P^~JRUEm1`%Z7 zA2iu}l~f1XRJsjU#otVDKY#v74lf0sc=z+?ndvO{nX9w4FK__u!Cq)?y6evRvHaxx zh6otiSPTxcGhnb4s~RKf>S!0>Jdw(3VIzTU-E1;Kwh|$*lrS?3MM>c#bR$M&7`Sg0 zEzSWii(`cYp-~17YcrhM;Mn$en|lEg#7%0jkPHRNiCmg94=D0-8-_fhgs3)M`z0F& zinXpB-Tk*K!8P(2(`M@!gH3!;mzj_n5xlcB6OvqVd*|-CO(U0u%e@XG-Wd%joX8g^AvP z6U^fRfjHh6m zx+keoF3kel8`T4j*RjD`;gtz;;l%$z)Yfa5k}pD}ZbM2f3>H~wbV_W<*uby$T=#sx zxoX=pD3#sYmbef?(BU<;CcTm46aqWZr>@z?*&k5LNt1=BNkNis7+7^oO!MHb}o{gE*48%A=-c>tXJSXZ^uFd(;<7=1TkGd9lSAJ3*yN3iOFrATI`a=4Zl59le;B;*uiUc5AOHXv z{|pKJH-GJaW4J`+e-@rhq@hKC5Q{gIrX``=(ri(yPaHM0Nb(0M--o7~l50d@Yu~6D zS$JsTAJChxTc>PZ!Q@}X^hpXHr<%67W_{h7P-18N6k4%FR- zDgwrLU>=!i4vZsi-i13to)$?M$bo2lX9?^|KRIn;xHX6k+m1T~bVTGj?VzMcH-qqC zT{1YIvbl(Jt|{CitvTmP)yXuJpz&_U z#@cF9E!$V6a^^PfIE53o-`X}h&u+Dz>Y1^(`es@D`yf-TFlz0xWXzG*WZkrXW1T*m zDA&#LRau~}<~(+2b^+625*5BXtD2M8YO=YNs>4iU({`9*UbX@=w&`M3dcc|5rOt1e zv7=M=DN$!!)oR7(DmgZiq^dEG>oevodnUrk1v|E^Bb0-g$3d;X_Li!pi)Qo3TDgY# z?2fLpvms~AO|-z=7llSamD6(731`XG$NrRY*wMtUNo;1PU{RgrcPu$@4l_BCa_Cdi zA?pB<>U0NU zkQ!Cb>5QFfwW08kh%SK+b&sevz8Oq8&p$6pjyBB@S={`Qy&#?=ZqF$|-c!$HxhgaI&G<1@LLm(^9sM zlgn1Va-h6t7fSE~_1W$0?!yhWjo#fw#<0sL*V`gFpG1;K6G$c-m&+oTfE7M|jdJgR zaxVmsx9fPXWEoTqq2^c8+(2RlFi7GBFi4U*Ji=$pHy}(x3^J2_3K2PCXsA=Wm`9ya zdbi|XzTH6x0#`ov-lJ$B!!ZvKhk)0He3C`$A&il&=CfovNCtvWa4t1Wqq&6+dZ4gk zsC}|OnWhy7=$-wFqwgVQ3c7_+gf8Ltbqedq!AcWm#pjqpOv?3?OVRTPZ+Gl0(UP!6 z&GLlyBG153bky$P0d)Sp;@xbEsLwjyQ=nAUgf~*c%X7i~jG=l2iPELIghC)i@(4aq zs`tDiZ;Wl>;XUFhIaY`4G#?@5en;u?-*isCyooYM(W}IVO8je*N}}#5Gveb# zlCmOx?xEprt4~-n*>TxrDQA&SyS+kl(Aj5Q!p{gqR-)Q=izsQ~NM(8UyEcgKMU0wO zO8S8yqgOzj3YYxNDOt^=SernMoDE)*~5q+4&; zNp({aW%Au&d=Ig29R4MD{&IbcQvoFw9adx$!Le4qsYP&x_^$O45N z0K|MZI)U9vv98yxuPJJO-s%;_0Y_MB)@Dl$ zlX5(Ynr}p56RWROLJn5miFt^W)UypA^-Ze<+3-IQvLuOG2CA{*#`{Jm7_q`eU)}^S zIg4~kJhF{^s@tM!CA$+8O_T8ag#41qR(K*vRA(iru6YHv&J&Y(!cNpU;ot+v1z+7q zHH$w#-mR-?=PZb`C4*x&H#hMHPjRV<3yV)#RrL%Ih6jhH`=*MKr^F zS>~L1N3aOxG8FOlJixpP4PlT`sz5seBqNtf09<4M0f~8p)7iw0j2*St58DQ84eE9A zQo%%7!#)nPb8~F_+!&w3CZMceI>@&K$2iTRu;MO(pe4-sv8l-c0Y;+q?1Wwu#{5 z7=PF)!o(8u7y{!8`7=2@Ea(OhRIM^~X;PT(HjS3F7>K(e0wu`WaO;7apY=d=3+x@O^WcUY8r(4WMq1Uo zOott{M+$LJaQtuVz~UZ3@LDGNykPf9Vc(^v(LHrKF{6nhkefZ zu-2+s^`&Z!-}u)Uqh`(VCs55>hU2V#NU|=+(>BpLESVo|lPe~4-EEd zL{Ssf{-8x&qMzNO)Ry8BJ4K6CItBYd13{m5*ScX;k%qhlB*$OIWtS034IYAt6Z)f( zl+wYytg!-wCbHvg0ddaYIz?M!U9G==5Y0LJ9h8dI0?+W z@NMQPzWQ7E({0Ld0m8$LtyB{rF}Z%4%?Q<)Mo+9Wei)%%Kqq?-%giG&fknO#I#wmx zEY?PN@d!JuKTwor_swMZe$BBq5fx+&!g40HlS0Cr$|-GRZk#dnJyM!LxAX<=b&Tm;IKFX;)g*-sC$HU&hzLsp z5n~ZH%9^z+1FE$Bk*-Kx2+9(X5F?h{m$?UrDPs$Xsnc&D$Opnu$`?85Cr^dQw3q40 zjm>J5`?b&aAKiz0*1=`Bme+8Tn@R^cctMX_SyY-b*4ud9+#t_c%WWRpoyHZ#MS>9ef{}J z{SQyU!qmor_8&t!3m01((YSe-0S18RF8J5Jum34R_%}XEBBqn3AzI^r_0(dycdtcg zn2=_6@^1DEwUKG>R?)&OVyfC@B+fvGHMLWdy(4e6CnNp7*JoUEg%}*m=X8R)?U+^UkNg^~{8AVMh-CBdN)d|4z@$ zfBCD+4o;RghQ6;6U1YoDeBiI06!xt{~bs^HKms zK)S#7nyihzla)KGkAJ>S?M1Lk`#B#9l3-&{9Nvzz9F@@|q>T1sjgDFEy){T4 zcZvxhBTYID5U%Cv8HukGlkInCq)X9|>qH3Y86^lEqFgTW53|;h_PS>LRZ5Ezit>vH zx;a|57^Bm1Y>$qL)P?m>lIXNYFV1Dsk48njk|f?2x0H6{rBf;bN{M!QWM_W%!N_id z)%0OxP4EECX%*Q_kIZq5J@tE7_I~#+36y7{R+z7TC$_H~FLhJ#Sw_TCuHVp$YhEjYK$O|6 zLVrOPaUsfBB52xfL#3=WcW{I(s!Rs8;!bsx8|6;TYg)v4#f_DjgLg@ICBINYvSPrt zU+yVms$^!(T+zVbbAl zV;6rWTZ<;rXT6DunIC9~FR`$=l&4_XDSzP)${dA%v&cpR;Ra&L*H4RE2LEEDg7CnF zT{cPQahbxs+m1Kt7wM$QBo?yP?%(i5dD`Iev=SFTA|1uh8-@BHzf1yc+7`RO;(8Im zn*X%kH0WI@MKHI-8%y8a$E~@p1=1U~7Qv!3YA)khSDs0EWz$#q@XfQj?l>paA)~B% zg}Wbuw3EC1_CyOI{et&ke{Habl?{oqNnvB?;C%MV4B%Z0)yCc#h=BHNb8v#Jvq`Kl zWcrRI%4C$2OX}T+@X+>_VCP$yl?Q!9j|CNHA_g5OE_vES=jCvD4O5#~!??kHilgUt z3$fD-iLfbtRloldC# z((C^-8diI8M_EPt9N8Q*Vi3D*mn=<2HpVF>lvGYFW^4sQnuIVx4=bIK@N6B$pQdO6 zDwUK@ZIx^bHnWt$$*grLsF4iHFSww8Dfpnjcc07kwP#^#takE*A04&Ndb|F(c26tw z`#dS?1@XZ2lN{)Sm9!aD-{kbBjezw~9F~Ue$`+y4Q@nd5L3a8SLze6!3bUWsUnbET zY*X>(j!^X=47eG4>4f%{pQ)SVwkL5=sqCa+ubbfBRlh4`c}(5vqK(r}t8za)hlK8; zE;&JNCpjUI>L(cA=S539dN;OYRB}_S-ASu@+Pp(~X9>t7y>osS8m3p(QJiC>cn>sB z0bQL;o@fQ3J>VF?sh8cw=m|WYJ1`ubYyLjMf{TPX8V7f!T7RZ-3Z^ERUGGh8ALTph z2+CZq6Q<54K5znXzMI2z!y|9D)w9_&=Ty554s_jC&1pT8!S3j5${z3Dl&7%3a)=V@ zuOysA8Z@M&?@~-cM=cKB3bf2);FZ2U7)&*oC>$RR`;%>@qt8SiJ%dkDZ0?h&i!HP2 zqYBN!m|?Vv9qO(FN*Cnzrjb>nwj@v{zfnq*&Z3EM{;nZsVmXj|*^g#;@1xlqV1}6P z+qwyrbMzMqz(5}(v$xZfT0mN4mL@{nL2lhBIa5q)z84~vJFieDLq9O=7y+I{zrW1} z`G_@HX(rdHBZ=&4gvv9bb`H=?AJ04r=R2lzPC>*xP*k^4a`1an^4d((GE$=(BZ#2b zT+r~P4539yZT?LSZcW2a zp5l2V_z3maezAVgCcBei$0hVGfBjeS8K=nQqTL17B76owH)wt8`LiG)pp0n{aX!zzN zgr>c<%V0x#Z!)-XN`T)1Pm{)au}o}nuw!b>!LIA;x!=aqgN1jjc}6oS7AsgOKtn-+ z2yydtwyAnrNWnJsyCzSg_H}1j>#txNI!^Wnm_jWt>r|M_WZ>0XOb|Fw%%x{A-T|V8 zTbyi@f}QR)v`emRe6uYlZhYU%`R&f9(?S(^v&VrzXx+I`QtzCO{3P$EY6>Z1{XG-~ zZ3fuQc2J9FS~;usNb_`GY~Qd&7`7#hz0bMEsDY|S3MTd{Kc{_NxZY6$)XTTSfowe& zW1!ZHnbTHQVi!YjEQ)


    ^v7e>L((hxz&GsuhDi7w} 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%0A~2TOX$v8!iDM-U5$dQi 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 OfXNR-ju5&-<x%8#`G#xX?M9 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 Date: Wed, 3 Jan 2018 20:59:11 +0100 Subject: [PATCH 0216/1380] [JENKINS-48761] - Address review comments from @jglick --- .../jenkins/slaves/remoting-info.properties | 4 ++-- core/src/main/java/hudson/Launcher.java | 3 +-- .../java/hudson/TcpSlaveAgentListener.java | 2 +- core/src/main/java/hudson/model/Computer.java | 4 +--- .../main/java/hudson/slaves/ChannelPinger.java | 2 +- .../main/java/hudson/slaves/SlaveComputer.java | 3 +-- .../security/MasterToSlaveCallable.java | 18 ++++++++---------- .../jenkins/slaves/RemotingVersionInfo.java | 11 +++++------ .../jenkins/slaves/StandardOutputSwapper.java | 2 +- .../slaves/RemotingVersionInfoTest.java | 6 +++--- pom.xml | 4 ++-- test/pom.xml | 2 +- .../jenkins/slaves/OldRemotingAgentTest.java | 9 ++++----- 13 files changed, 31 insertions(+), 39 deletions(-) diff --git a/core/src/filter/resources/jenkins/slaves/remoting-info.properties b/core/src/filter/resources/jenkins/slaves/remoting-info.properties index 5a3a6ef14a..6ac78f4ad4 100644 --- a/core/src/filter/resources/jenkins/slaves/remoting-info.properties +++ b/core/src/filter/resources/jenkins/slaves/remoting-info.properties @@ -2,5 +2,5 @@ # This version MAY differ from what is really classloaded (see the "Pluggable Core Components", JENKINS-41196) remoting.embedded.version=${remoting.version} -# Minimal Remoting version on external agents which is supported by the core -remoting.minimal.supported.version=${remoting.minimal.supported.version} +# Minimum Remoting version on external agents which is supported by the core +remoting.minimum.supported.version=${remoting.minimum.supported.version} diff --git a/core/src/main/java/hudson/Launcher.java b/core/src/main/java/hudson/Launcher.java index 1c5e884a4d..432a5afe24 100644 --- a/core/src/main/java/hudson/Launcher.java +++ b/core/src/main/java/hudson/Launcher.java @@ -44,7 +44,6 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import javax.annotation.CheckForNull; -import javax.annotation.concurrent.GuardedBy; import java.io.BufferedOutputStream; import java.io.File; import java.io.IOException; @@ -1289,7 +1288,7 @@ public abstract class Launcher { } public RemoteProcess call() throws IOException { - final Channel channel = _getOpenChannelOrFail(); + final Channel channel = getOpenChannelOrFail(); Launcher.ProcStarter ps = new LocalLauncher(listener).launch(); ps.cmds(cmd).masks(masks).envs(env).stdin(in).stdout(out).stderr(err).quiet(quiet); if(workDir!=null) ps.pwd(workDir); diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index e6e2a7807d..03455a7753 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -290,7 +290,7 @@ public final class TcpSlaveAgentListener extends Thread { try { Writer o = new OutputStreamWriter(s.getOutputStream(), "UTF-8"); - //TODO: expose version about minimal supported Remoting version (JENKINS-48766) + //TODO: expose version about minimum supported Remoting version (JENKINS-48766) if (header.startsWith("GET / ")) { o.write("HTTP/1.0 200 OK\r\n"); o.write("Content-Type: text/plain;charset=UTF-8\r\n"); diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index b802f9e4d6..43fa2d31d7 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -32,7 +32,6 @@ import hudson.Launcher.ProcStarter; import hudson.slaves.Cloud; import jenkins.util.SystemProperties; import hudson.Util; -import hudson.cli.declarative.CLIMethod; import hudson.cli.declarative.CLIResolver; import hudson.console.AnnotatedLargeText; import hudson.init.Initializer; @@ -85,7 +84,6 @@ import org.kohsuke.stapler.HttpRedirect; import org.kohsuke.stapler.WebMethod; import org.kohsuke.stapler.export.Exported; import org.kohsuke.stapler.export.ExportedBean; -import org.kohsuke.args4j.Option; import org.kohsuke.stapler.interceptor.RequirePOST; import javax.annotation.OverridingMethodsMustInvokeSuper; @@ -1426,7 +1424,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces private static final class DumpExportTableTask extends MasterToSlaveCallable { public String call() throws IOException { - final Channel ch = _getChannelOrFail(); + final Channel ch = getChannelOrFail(); StringWriter sw = new StringWriter(); try (PrintWriter pw = new PrintWriter(sw)) { ch.dumpExportTable(pw); diff --git a/core/src/main/java/hudson/slaves/ChannelPinger.java b/core/src/main/java/hudson/slaves/ChannelPinger.java index b791df2813..85d6d78675 100644 --- a/core/src/main/java/hudson/slaves/ChannelPinger.java +++ b/core/src/main/java/hudson/slaves/ChannelPinger.java @@ -134,7 +134,7 @@ public class ChannelPinger extends ComputerListener { @Override public Void call() throws IOException { // No sense in setting up channel pinger if the channel is being closed - setUpPingForChannel(_getOpenChannelOrFail(), null, pingTimeoutSeconds, pingIntervalSeconds, false); + setUpPingForChannel(getOpenChannelOrFail(), null, pingTimeoutSeconds, pingIntervalSeconds, false); return null; } diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java index 029565f137..9b87bf5dec 100644 --- a/core/src/main/java/hudson/slaves/SlaveComputer.java +++ b/core/src/main/java/hudson/slaves/SlaveComputer.java @@ -44,7 +44,6 @@ import hudson.remoting.VirtualChannel; import hudson.security.ACL; import hudson.slaves.OfflineCause.ChannelTermination; import hudson.util.Futures; -import hudson.util.IOUtils; import hudson.util.NullStream; import hudson.util.RingBufferLogHandler; import hudson.util.StreamTaskListener; @@ -869,7 +868,7 @@ public class SlaveComputer extends Computer { } try { - _getChannelOrFail().setProperty("slave",Boolean.TRUE); // indicate that this side of the channel is the slave side. + getChannelOrFail().setProperty("slave",Boolean.TRUE); // indicate that this side of the channel is the slave side. } catch (ChannelClosedException e) { throw new IllegalStateException(e); } diff --git a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java index 9dbe3904a4..60ac25bfbc 100644 --- a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java +++ b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java @@ -7,8 +7,6 @@ import org.jenkinsci.remoting.RoleChecker; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; -import javax.annotation.Nonnull; - /** * Convenient {@link Callable} meant to be run on agent. @@ -25,9 +23,9 @@ public abstract class MasterToSlaveCallable implements C checker.check(this,Roles.SLAVE); } - //TODO: replace by Callable#getChannelOrFail() once Minimal supported Remoting version is 3.15 or above - @Restricted(NoExternalUse.class) - protected static Channel _getChannelOrFail() throws ChannelClosedException { + //TODO: remove once Minimum supported Remoting version is 3.15 or above + @Override + public Channel getChannelOrFail() throws ChannelClosedException { final Channel ch = Channel.current(); if (ch == null) { throw new ChannelClosedException(new IllegalStateException("No channel associated with the thread")); @@ -35,11 +33,11 @@ public abstract class MasterToSlaveCallable implements C return ch; } - //TODO: replace by Callable#getOpenChannelOrFail() once Minimal supported Remoting version is 3.15 or above - @Restricted(NoExternalUse.class) - protected static Channel _getOpenChannelOrFail() throws ChannelClosedException { - final Channel ch = _getChannelOrFail(); - if (ch.isClosingOrClosed()) { // TODO: Since Remoting 2.33, we still need to explicitly declare minimal Remoting version + //TODO: remove once Callable#getOpenChannelOrFail() once Minimaumsupported Remoting version is 3.15 or above + @Override + public Channel getOpenChannelOrFail() throws ChannelClosedException { + final Channel ch = getChannelOrFail(); + if (ch.isClosingOrClosed()) { // TODO: Since Remoting 2.33, we still need to explicitly declare minimum Remoting version throw new ChannelClosedException(new IllegalStateException("The associated channel " + ch + " is closing down or has closed down", ch.getCloseRequestCause())); } return ch; diff --git a/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java b/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java index e4ac3b71c7..e2c2211591 100644 --- a/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java +++ b/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java @@ -37,9 +37,8 @@ import java.util.logging.Logger; // TODO: Make the API public (JENKINS-48766) /** - * Provides information about Remoting versions used withing the core. + * Provides information about Remoting versions used within the core. * @author Oleg Nenashev - * @since TODO */ @Restricted(NoExternalUse.class) public class RemotingVersionInfo { @@ -51,7 +50,7 @@ public class RemotingVersionInfo { private static VersionNumber EMBEDDED_VERSION; @CheckForNull - private static VersionNumber MINIMAL_SUPPORTED_VERSION; + private static VersionNumber MINIMUM_SUPPORTED_VERSION; private RemotingVersionInfo() {} @@ -66,7 +65,7 @@ public class RemotingVersionInfo { } EMBEDDED_VERSION = tryExtractVersion(props, "remoting.embedded.version"); - MINIMAL_SUPPORTED_VERSION = tryExtractVersion(props, "remoting.minimal.supported.version"); + MINIMUM_SUPPORTED_VERSION = tryExtractVersion(props, "remoting.minimum.supported.version"); } @CheckForNull @@ -98,7 +97,7 @@ public class RemotingVersionInfo { } @CheckForNull - public static VersionNumber getMinimalSupportedVersion() { - return MINIMAL_SUPPORTED_VERSION; + public static VersionNumber getMinimumSupportedVersion() { + return MINIMUM_SUPPORTED_VERSION; } } diff --git a/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java b/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java index 5c0966e8a8..9a8d10a12f 100644 --- a/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java +++ b/core/src/main/java/jenkins/slaves/StandardOutputSwapper.java @@ -39,7 +39,7 @@ public class StandardOutputSwapper extends ComputerListener { private static final class ChannelSwapper extends MasterToSlaveCallable { public Boolean call() throws Exception { if (File.pathSeparatorChar==';') return false; // Windows - Channel c = _getOpenChannelOrFail(); + Channel c = getOpenChannelOrFail(); StandardOutputStream sos = (StandardOutputStream) c.getProperty(StandardOutputStream.class); if (sos!=null) { swap(sos); diff --git a/core/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java b/core/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java index 3e894b404c..5f629028ed 100644 --- a/core/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java +++ b/core/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java @@ -40,8 +40,8 @@ public class RemotingVersionInfoTest { } @Test - public void shouldLoadMinimalSupportedVersionByDefault() { - assertThat("Remoting Minimal supported version is not defined", - RemotingVersionInfo.getMinimalSupportedVersion(), notNullValue()); + public void shouldLoadMinimumSupportedVersionByDefault() { + assertThat("Remoting Minimum supported version is not defined", + RemotingVersionInfo.getMinimumSupportedVersion(), notNullValue()); } } diff --git a/pom.xml b/pom.xml index d0f11bbe36..c5f550a5af 100644 --- a/pom.xml +++ b/pom.xml @@ -104,9 +104,9 @@ THE SOFTWARE. 3.0.0 - + 3.15 - 2.60 + 2.60 diff --git a/test/pom.xml b/test/pom.xml index 931dbb0562..f6e20856ea 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -239,7 +239,7 @@ THE SOFTWARE. org.jenkins-ci.main remoting - ${remoting.minimal.supported.version} + ${remoting.minimum.supported.version} jar ${project.build.outputDirectory}/old-remoting remoting-minimal-supported.jar diff --git a/test/src/test/java/jenkins/slaves/OldRemotingAgentTest.java b/test/src/test/java/jenkins/slaves/OldRemotingAgentTest.java index adb17cf9ce..9dff0ae75c 100644 --- a/test/src/test/java/jenkins/slaves/OldRemotingAgentTest.java +++ b/test/src/test/java/jenkins/slaves/OldRemotingAgentTest.java @@ -44,7 +44,6 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.SimpleCommandLauncher; -import javax.annotation.CheckForNull; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; @@ -70,7 +69,7 @@ public class OldRemotingAgentTest { @Before public void extractAgent() throws Exception { agentJar = new File(tmpDir.getRoot(), "old-agent.jar"); - FileUtils.copyURLToFile(getClass().getResource("/old-remoting/remoting-minimal-supported.jar"), agentJar); + FileUtils.copyURLToFile(OldRemotingAgentTest.class.getResource("/old-remoting/remoting-minimal-supported.jar"), agentJar); } @Test @@ -81,7 +80,7 @@ public class OldRemotingAgentTest { boolean isUnix = agent.getComputer().isUnix(); assertThat("Received wrong agent version. A minimal supported version is expected", agent.getComputer().getSlaveVersion(), - equalTo(RemotingVersionInfo.getMinimalSupportedVersion().toString())); + equalTo(RemotingVersionInfo.getMinimumSupportedVersion().toString())); // Just ensure we are able to run something on the agent FreeStyleProject project = j.createFreeStyleProject("foo"); @@ -122,8 +121,8 @@ public class OldRemotingAgentTest { try { monitorMethod = AbstractAsyncNodeMonitorDescriptor.class.getDeclaredMethod("monitor", Computer.class); } catch (NoSuchMethodException ex) { - System.out.println("Cannot invoke monitor " + monitor + ", no monitor(Computer.class) method in the Descriptor. It will be ignored. " + ex.getMessage()); - return; + //TODO: make the API visible for testing? + throw new AssertionError("Cannot invoke monitor " + monitor + ", no monitor(Computer.class) method in the Descriptor. It will be ignored. ", ex); } try { monitorMethod.setAccessible(true); -- GitLab From 5707ff176621702fa6222c9e9ef1dbbef5ece635 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 3 Jan 2018 15:48:04 -0800 Subject: [PATCH 0217/1380] [maven-release-plugin] prepare release jenkins-2.100 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index ab9aaa34df..5f2b3906a0 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.100-SNAPSHOT + 2.100 cli diff --git a/core/pom.xml b/core/pom.xml index 5be417297b..636d756131 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.100-SNAPSHOT + 2.100 jenkins-core diff --git a/pom.xml b/pom.xml index e53805951d..775a4765ce 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.100-SNAPSHOT + 2.100 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.100 diff --git a/test/pom.xml b/test/pom.xml index 03fa7fba20..c2350085a3 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.100-SNAPSHOT + 2.100 test diff --git a/war/pom.xml b/war/pom.xml index 54d046e418..eacdc2a3b8 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.100-SNAPSHOT + 2.100 jenkins-war -- GitLab From c4e912900b88b9bbd19e8e0154ee190144ad63a7 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 3 Jan 2018 15:48:04 -0800 Subject: [PATCH 0218/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 5f2b3906a0..d9c428b59a 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.100 + 2.101-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 636d756131..ed9e193cf7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.100 + 2.101-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index 775a4765ce..c3f30f1cc0 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.100 + 2.101-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.100 + HEAD diff --git a/test/pom.xml b/test/pom.xml index c2350085a3..0454104fac 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.100 + 2.101-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index eacdc2a3b8..40cde82335 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.100 + 2.101-SNAPSHOT jenkins-war -- GitLab From 3a5189ebc861832ce5189f1104fd0beff2e07233 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Thu, 4 Jan 2018 12:12:56 +0100 Subject: [PATCH 0219/1380] Introduce flags to disable force per instance or globally The goal here is to: * be able to selectively choose performance over integrity in some very specific cases * be able to globally disable AtomicFileWriter integrity, and basically revert to previous behaviour by setting a system property. This flag is meant as an emergency one in case something goes very wron on some production system. --- .../java/hudson/util/AtomicFileWriter.java | 36 ++++++++++++++++++- .../java/hudson/util/FileChannelWriter.java | 12 +++++-- .../hudson/util/FileChannelWriterTest.java | 2 +- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index e67eafbb64..968c061668 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -23,6 +23,8 @@ */ package hudson.util; +import jenkins.util.SystemProperties; + import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.io.File; @@ -52,6 +54,15 @@ public class AtomicFileWriter extends Writer { private static final Logger LOGGER = Logger.getLogger(AtomicFileWriter.class.getName()); + private static /* final */ boolean LOSE_INTEGRITY = SystemProperties.getBoolean( + AtomicFileWriter.class.getName() + ".LOSE_DATA_INTEGRITY_FOR_PERFORMANCE"); + + static { + if (LOSE_INTEGRITY) { + LOGGER.log(Level.SEVERE, "LOSE_DATA_INTEGRITY_FOR_PERFORMANCE flag used, YOU RISK LOSING DATA."); + } + } + private final Writer core; private final Path tmpPath; private final Path destPath; @@ -94,6 +105,21 @@ public class AtomicFileWriter extends Writer { * @param charset File charset to write. */ public AtomicFileWriter(@Nonnull Path destinationPath, @Nonnull Charset charset) throws IOException { + // See FileChannelWriter docs to understand why we do not cause a force() call on flush() from AtomicFileWriter. + this(destinationPath, charset, false, true); + } + + /** + * DO NOT USE THIS METHOD, OR YOU WILL LOSE DATA INTEGRITY. + * + * @param destinationPath the destination path where to write the content when committed. + * @param charset File charset to write. + * @param integrityOnFlush do not force writing to disk when flushing + * @param integrityOnClose do not force writing to disk when closing + * @deprecated use {@link AtomicFileWriter#AtomicFileWriter(Path, Charset)} + */ + @Deprecated + public AtomicFileWriter(@Nonnull Path destinationPath, @Nonnull Charset charset, boolean integrityOnFlush, boolean integrityOnClose) throws IOException { if (charset == null) { // be extra-defensive if people don't care throw new IllegalArgumentException("charset is null"); } @@ -117,7 +143,15 @@ public class AtomicFileWriter extends Writer { throw new IOException("Failed to create a temporary file in "+ dir,e); } - core = new FileChannelWriter(tmpPath, charset, false, StandardOpenOption.WRITE); + if (LOSE_INTEGRITY) { + // We should log it in WARN, but as this code is called often, this would create huge logs in prod system + // Hence why a similar log is logged once above in a static block. + LOGGER.log(Level.FINE, "WARNING: YOU SET A FLAG THAT COULD LEAD TO DATA LOSS."); + integrityOnFlush = false; + integrityOnClose = false; + } + + core = new FileChannelWriter(tmpPath, charset, integrityOnFlush, integrityOnClose, StandardOpenOption.WRITE); } @Override diff --git a/core/src/main/java/hudson/util/FileChannelWriter.java b/core/src/main/java/hudson/util/FileChannelWriter.java index 591009e8a5..8d623740d1 100644 --- a/core/src/main/java/hudson/util/FileChannelWriter.java +++ b/core/src/main/java/hudson/util/FileChannelWriter.java @@ -46,6 +46,11 @@ public class FileChannelWriter extends Writer { */ private boolean forceOnFlush; + /** + * See forceOnFlush. You probably never want to set forceOnClose to false. + */ + private boolean forceOnClose; + /** * @param filePath the path of the file to write to. * @param charset the charset to use when writing characters. @@ -53,9 +58,10 @@ public class FileChannelWriter extends Writer { * @param options the options for opening the file. * @throws IOException if something went wrong. */ - FileChannelWriter(Path filePath, Charset charset, boolean forceOnFlush, OpenOption... options) throws IOException { + FileChannelWriter(Path filePath, Charset charset, boolean forceOnFlush, boolean forceOnClose, OpenOption... options) throws IOException { this.charset = charset; this.forceOnFlush = forceOnFlush; + this.forceOnClose = forceOnClose; channel = FileChannel.open(filePath, options); } @@ -79,7 +85,9 @@ public class FileChannelWriter extends Writer { @Override public void close() throws IOException { if(channel.isOpen()) { - channel.force(true); + if (forceOnClose) { + channel.force(true); + } channel.close(); } } diff --git a/core/src/test/java/hudson/util/FileChannelWriterTest.java b/core/src/test/java/hudson/util/FileChannelWriterTest.java index 59ac01ff96..1b1307d8b6 100644 --- a/core/src/test/java/hudson/util/FileChannelWriterTest.java +++ b/core/src/test/java/hudson/util/FileChannelWriterTest.java @@ -27,7 +27,7 @@ public class FileChannelWriterTest { @Before public void setUp() throws Exception { file = temporaryFolder.newFile(); - writer = new FileChannelWriter(file.toPath(), StandardCharsets.UTF_8, true, StandardOpenOption.WRITE); + writer = new FileChannelWriter(file.toPath(), StandardCharsets.UTF_8, true, true, StandardOpenOption.WRITE); } @Test -- GitLab From 1a8b8f46ba9259feb274edb91eb23013958474e5 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 4 Jan 2018 14:07:38 -0500 Subject: [PATCH 0220/1380] Noting target version for merge of https://github.com/jenkinsci/git-client-plugin/pull/290. --- .../src/main/resources/jenkins/security/whitelisted-classes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 3356a8ac22..b815062353 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -78,7 +78,7 @@ org.acegisecurity.userdetails.User org.apache.commons.fileupload.disk.DiskFileItem org.apache.commons.fileupload.util.FileItemHeadersImpl -# TODO remove when https://github.com/jenkinsci/git-client-plugin/pull/290 is widely adopted +# TODO remove when git-client 2.7.1+ is widely adopted org.eclipse.jgit.lib.ObjectId org.eclipse.jgit.lib.ObjectIdOwnerMap$Entry org.eclipse.jgit.lib.PersonIdent -- GitLab From 6d49377c8d816bffaea42fa414ae188067953652 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 4 Jan 2018 14:32:40 -0500 Subject: [PATCH 0221/1380] EmptyImmutableList needed for Workflow 1.12- prior to https://github.com/jenkinsci/pipeline-plugin/pull/213 as demonstrated by CoberturaPublisherPipelineTest with https://github.com/jenkinsci/cobertura-plugin/pull/90. --- core/src/main/resources/jenkins/security/whitelisted-classes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index b815062353..f49b4570fa 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -1,4 +1,5 @@ com.google.common.collect.AbstractSetMultimap +com.google.common.collect.EmptyImmutableList com.google.common.collect.EmptyImmutableSet com.google.common.collect.EmptyImmutableSortedSet com.google.common.collect.HashMultimap -- GitLab From 52ff066b71a1df2f565e46e4f67e4ec8cc5eac3e Mon Sep 17 00:00:00 2001 From: Alon Bar-Lev Date: Wed, 3 Jan 2018 20:35:03 +0200 Subject: [PATCH 0222/1380] [JENKINS-47043] Introduce exit lifecycle to exit instead of restart If hudson.lifecycle.ExitLifecycle is selected, jenkins will exit when restart is requested. Exit code is taken out of jenkins.model.Jenkins.exitCodeOnRestart, default is 5. This is usable when there is an external watchdog such as SystemD. SystemD service example: ExecStart=@/usr/bin/java jenkins $JAVA_OPTIONS -DJENKINS_HOME=${JENKINS_HOME} -Dhudson.lifecycle=hudson.lifecycle.ExitLifecycle -Djenkins.model.Jenkins.exitCodeOnRestart=55 -jar $JENKINS_WAR --debug=${JENKINS_DEBUG_LEVEL} --httpPort=${JENKINS_PORT} --httpListenAddress=${JENKINS_LISTEN_ADDRESS} $JENKINS_ARGS Signed-off-by: Alon Bar-Lev --- .../java/hudson/lifecycle/ExitLifecycle.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 core/src/main/java/hudson/lifecycle/ExitLifecycle.java diff --git a/core/src/main/java/hudson/lifecycle/ExitLifecycle.java b/core/src/main/java/hudson/lifecycle/ExitLifecycle.java new file mode 100644 index 0000000000..4d15a575a3 --- /dev/null +++ b/core/src/main/java/hudson/lifecycle/ExitLifecycle.java @@ -0,0 +1,75 @@ +/* + * The MIT License + * + * Copyright 2018 Alon Bar-Lev + * + * 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.lifecycle; + +import hudson.Extension; + +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import jenkins.model.Configuration; +import jenkins.model.Jenkins; + +/** + * {@link Lifecycle} that delegates the responsibility to restart Jenkins to an external + * watchdog such as SystemD or OpenRC. + * + *

    + * Restart by exit with specific code. + * + * @author Alon Bar-Lev + */ +@Restricted(NoExternalUse.class) +@Extension +public class ExitLifecycle extends Lifecycle { + + private static final Logger LOGGER = Logger.getLogger(ExitLifecycle.class.getName()); + + private static final String EXIT_CODE_ON_RESTART = "exitCodeOnRestart"; + private static final String DEFAULT_EXIT_CODE = "5"; + + private Integer exitOnRestart; + + public ExitLifecycle() { + exitOnRestart = Integer.parseInt(Configuration.getStringConfigParameter(EXIT_CODE_ON_RESTART, DEFAULT_EXIT_CODE)); + } + + @Override + public void restart() { + Jenkins jenkins = Jenkins.getInstanceOrNull(); // guard against repeated concurrent calls to restart + + try { + if (jenkins != null) { + jenkins.cleanUp(); + } + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Failed to clean up. Restart will continue.", e); + } + + System.exit(exitOnRestart); + } +} -- GitLab From dbc8b71f5942280392c230a612ccc52bc6cb84c4 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 5 Jan 2018 13:10:51 -0500 Subject: [PATCH 0223/1380] hudson.plugins.mercurial.PipelineTest.multipleSCMs failed to call FilePath.toURI. --- core/src/main/resources/jenkins/security/whitelisted-classes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index f49b4570fa..3e4d61134e 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -28,6 +28,7 @@ java.lang.Object java.lang.StackTraceElement java.lang.String java.lang.reflect.Proxy +java.net.URI java.net.URL java.security.KeyRep java.util.ArrayDeque -- GitLab From 358f6e86f44e7baae9dc3bdd9d0af2415dc4b6bb Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 5 Jan 2018 13:39:14 -0500 Subject: [PATCH 0224/1380] UncaughtExceptionHandler should avoid reporting EOFException. --- .../impl/InstallUncaughtExceptionHandler.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java index d8412f6ad4..f15dd429ba 100644 --- a/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java +++ b/core/src/main/java/hudson/init/impl/InstallUncaughtExceptionHandler.java @@ -1,6 +1,7 @@ package hudson.init.impl; import hudson.init.Initializer; +import java.io.EOFException; import jenkins.model.Jenkins; import org.kohsuke.stapler.WebApp; import org.kohsuke.stapler.compression.CompressionFilter; @@ -17,7 +18,7 @@ import java.util.logging.Logger; import org.kohsuke.stapler.Stapler; /** - * @author Kohsuke Kawaguchi + * Deals with exceptions that get thrown all the way up to the Stapler rendering layer. */ public class InstallUncaughtExceptionHandler { @@ -25,23 +26,19 @@ public class InstallUncaughtExceptionHandler { @Initializer public static void init(final Jenkins j) throws IOException { - CompressionFilter.setUncaughtExceptionHandler(j.servletContext, new UncaughtExceptionHandler() { - @Override - public void reportException(Throwable e, ServletContext context, HttpServletRequest req, HttpServletResponse rsp) throws ServletException, IOException { + CompressionFilter.setUncaughtExceptionHandler(j.servletContext, (e, context, req, rsp) -> { if (rsp.isCommitted()) { - LOGGER.log(Level.WARNING, null, e); + LOGGER.log(isEOFException(e) ? Level.FINE : Level.WARNING, null, e); return; } req.setAttribute("javax.servlet.error.exception",e); try { - WebApp.get(j.servletContext).getSomeStapler() - .invoke(req,rsp, Jenkins.getInstance(), "/oops"); + WebApp.get(j.servletContext).getSomeStapler().invoke(req, rsp, Jenkins.get(), "/oops"); } catch (ServletException | IOException x) { if (!Stapler.isSocketException(x)) { throw x; } } - } }); try { Thread.setDefaultUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler()); @@ -57,6 +54,16 @@ public class InstallUncaughtExceptionHandler { } } + private static boolean isEOFException(Throwable e) { + if (e == null) { + return false; + } else if (e instanceof EOFException) { + return true; + } else { + return isEOFException(e.getCause()); + } + } + /** An UncaughtExceptionHandler that just logs the exception */ private static class DefaultUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { -- GitLab From adba0d391780ce33760969d415c8e70e7d30cd2d Mon Sep 17 00:00:00 2001 From: Gentle Yang Date: Sun, 7 Jan 2018 02:07:51 -0800 Subject: [PATCH 0225/1380] translate into Chinese (simplified) under install/SetupWizard/ (#3204) * Create pluginSetupWizard_zh_CN.properties * Create client-scripts_zh_CN.properties * Create setupWizardFirstUser_zh_CN.properties * Create proxy-configuration_zh_CN.properties * Update setupWizardFirstUser_zh_CN.properties * Create authenticate-security-token_zh_CN.properties * Update client-scripts_zh_CN.properties * Update pluginSetupWizard_zh_CN.properties * Update client-scripts_zh_CN.properties * Update authenticate-security-token_zh_CN.properties * Update setupWizardFirstUser_zh_CN.properties * Update proxy-configuration_zh_CN.properties * Update pluginSetupWizard_zh_CN.properties * Update pluginSetupWizard_zh_CN.properties * Update pluginSetupWizard_zh_CN.properties * fixed build error --- ...thenticate-security-token_zh_CN.properties | 32 ++++++ .../proxy-configuration_zh_CN.properties | 24 +++++ .../setupWizardFirstUser_zh_CN.properties | 24 +++++ .../client-scripts_zh_CN.properties | 26 +++++ .../pluginSetupWizard_zh_CN.properties | 98 +++++++++++++++++++ 5 files changed, 204 insertions(+) create mode 100644 core/src/main/resources/jenkins/install/SetupWizard/authenticate-security-token_zh_CN.properties create mode 100644 core/src/main/resources/jenkins/install/SetupWizard/proxy-configuration_zh_CN.properties create mode 100644 core/src/main/resources/jenkins/install/SetupWizard/setupWizardFirstUser_zh_CN.properties create mode 100644 core/src/main/resources/jenkins/install/UpgradeWizard/client-scripts_zh_CN.properties create mode 100644 core/src/main/resources/jenkins/install/pluginSetupWizard_zh_CN.properties diff --git a/core/src/main/resources/jenkins/install/SetupWizard/authenticate-security-token_zh_CN.properties b/core/src/main/resources/jenkins/install/SetupWizard/authenticate-security-token_zh_CN.properties new file mode 100644 index 0000000000..9f59b21975 --- /dev/null +++ b/core/src/main/resources/jenkins/install/SetupWizard/authenticate-security-token_zh_CN.properties @@ -0,0 +1,32 @@ +# The MIT License +# +# Copyright (c) 2017-, vivo.com +# Chinese (Simplified) translated by Gentle Yang +# +# 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. + +authenticate-security-token.getting.started=\u5165\u95e8 +authenticate-security-token.unlock.jenkins=\u89e3\u9501jenkins +jenkins.install.findSecurityTokenMessage=\u4e3a\u4e86\u786e\u4fdd\u7ba1\u7406\u5458\u5b89\u5168\u5730\u5b89\u88c5jenkins\uff0c\ +\u5bc6\u7801\u5df2\u5199\u5165\u5230\u65e5\u5fd7\u4e2d\uff08\u4e0d\u77e5\u9053\u5728\u54ea\u91cc\uff1f\uff09\u8be5\u6587\u4ef6\u5728\u670d\u52a1\u5668\u4e0a\uff1a

    {0}

    +authenticate-security-token.copy.password=\u8bf7\u4ece\u672c\u5730\u590d\u5236\u5bc6\u7801\u5e76\u7c98\u8d34\u5230\u4e0b\u9762\u3002 +authenticate-security-token.error=\u9519\u8bef\uff1a +authenticate-security-token.password.incorrect=\u8f93\u5165\u7684\u5bc6\u7801\u6709\u8bef\uff0c\u8bf7\u68c0\u67e5\u6587\u4ef6\u4ee5\u786e\u8ba4\u5bc6\u7801\u6b63\u786e +authenticate-security-token.password.administrator=\u7ba1\u7406\u5458\u5bc6\u7801 +authenticate-security-token.continue=\u7ee7\u7eed diff --git a/core/src/main/resources/jenkins/install/SetupWizard/proxy-configuration_zh_CN.properties b/core/src/main/resources/jenkins/install/SetupWizard/proxy-configuration_zh_CN.properties new file mode 100644 index 0000000000..75ddbfd752 --- /dev/null +++ b/core/src/main/resources/jenkins/install/SetupWizard/proxy-configuration_zh_CN.properties @@ -0,0 +1,24 @@ +# The MIT License +# +# Copyright (c) 2017-, vivo.com +# Chinese (Simplified) translated by Gentle Yang +# +# 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. + +HTTP\ Proxy\ Configuration=\u914d\u7f6eHTTP\u4ee3\u7406 diff --git a/core/src/main/resources/jenkins/install/SetupWizard/setupWizardFirstUser_zh_CN.properties b/core/src/main/resources/jenkins/install/SetupWizard/setupWizardFirstUser_zh_CN.properties new file mode 100644 index 0000000000..3fc0ba88b1 --- /dev/null +++ b/core/src/main/resources/jenkins/install/SetupWizard/setupWizardFirstUser_zh_CN.properties @@ -0,0 +1,24 @@ +# The MIT License +# +# Copyright (c) 2017-, vivo.com +# Chinese (Simplified) translated by Gentle Yang +# +# 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. + +Create\ First\ Admin\ User=\u521b\u5efa\u7b2c\u4e00\u4e2a\u7ba1\u7406\u5458\u7528\u6237 diff --git a/core/src/main/resources/jenkins/install/UpgradeWizard/client-scripts_zh_CN.properties b/core/src/main/resources/jenkins/install/UpgradeWizard/client-scripts_zh_CN.properties new file mode 100644 index 0000000000..f5bd72ea5e --- /dev/null +++ b/core/src/main/resources/jenkins/install/UpgradeWizard/client-scripts_zh_CN.properties @@ -0,0 +1,26 @@ +# The MIT License +# +# Copyright (c) 2017-, vivo.com +# Chinese (Simplified) translated by Gentle Yang +# +# 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. + +msg.before=\u6b22\u8fce\u4f7f\u7528jenkins {0}%21\u0020 +msg.link=\u73b0\u5728\u66f4\u65b0 +msg.after=\u0020\u4ee5\u83b7\u5f97\u65b0\u7279\u6027 diff --git a/core/src/main/resources/jenkins/install/pluginSetupWizard_zh_CN.properties b/core/src/main/resources/jenkins/install/pluginSetupWizard_zh_CN.properties new file mode 100644 index 0000000000..06da10006b --- /dev/null +++ b/core/src/main/resources/jenkins/install/pluginSetupWizard_zh_CN.properties @@ -0,0 +1,98 @@ +# The MIT License +# +# Copyright (c) 2017-, vivo.com +# Chinese (Simplified) translated by Gentle Yang +# +# 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. + +installWizard_welcomePanel_title=\u65b0\u624b\u5165\u95e8 +installWizard_welcomePanel_banner=\u81ea\u5b9a\u4e49jenkins +installWizard_welcomePanel_message=\u63d2\u4ef6\u901a\u8fc7\u9644\u52a0\u7279\u6027\u6765\u6269\u5c55jenkins\u4ee5\u6ee1\u8db3\u4e0d\u540c\u7684\u9700\u6c42\u3002 +installWizard_welcomePanel_recommendedActionTitle=\u5b89\u88c5\u63a8\u8350\u7684\u63d2\u4ef6 +installWizard_welcomePanel_recommendedActionDetails=\u5b89\u88c5jenkins\u793e\u533a\u63a8\u8350\u7684\u63d2\u4ef6\u3002 +installWizard_welcomePanel_customizeActionTitle=\u9009\u62e9\u63d2\u4ef6\u6765\u5b89\u88c5 +installWizard_welcomePanel_customizeActionDetails=\u9009\u62e9\u5e76\u5b89\u88c5\u6700\u9002\u5408\u7684\u63d2\u4ef6\u3002 +installWizard_jenkinsVersionTitle=Jenkins +installWizard_offline_title=\u79bb\u7ebf +installWizard_offline_message=\u8be5jenkins\u5b9e\u4f8b\u4f3c\u4e4e\u5df2\u79bb\u7ebf\u3002\ +

    \ +\u53c2\u8003 \u79bb\u7ebfjenkins\u5b89\u88c5\u6587\u6863\u4e86\u89e3\u672a\u63a5\u5165\u4e92\u8054\u7f51\u65f6\u5b89\u88c5jenkins\u7684\u66f4\u591a\u4fe1\u606f\u3002

    \ +\u53ef\u4ee5\u901a\u8fc7\u914d\u7f6e\u4e00\u4e2a\u4ee3\u7406\u6216\u8df3\u8fc7\u63d2\u4ef6\u5b89\u88c5\u6765\u9009\u62e9\u7ee7\u7eed\u3002\ +

    +installWizard_error_header=\u51fa\u73b0\u4e00\u4e2a\u9519\u8bef +installWizard_error_message=\u5b89\u88c5\u8fc7\u7a0b\u4e2d\u51fa\u73b0\u4e00\u4e2a\u9519\u8bef\uff1a +installWizard_error_connection=\u65e0\u6cd5\u8fde\u63a5\u5230jenkins +installWizard_error_restartNotSupported=\u4e0d\u652f\u6301\u81ea\u52a8\u91cd\u542f\uff0c\u8bf7\u624b\u52a8\u91cd\u542f\u8be5\u5b9e\u4f8b +installWizard_installCustom_title=\u65b0\u624b\u5165\u95e8 +installWizard_installCustom_selectAll=\u5168\u90e8 +installWizard_installCustom_selectNone=None +installWizard_installCustom_selectRecommended=\u5efa\u8bae +installWizard_installCustom_selected=\u5df2\u9009\u62e9 +installWizard_installCustom_dependenciesPrefix=\u4f9d\u8d56 +installWizard_installCustom_pluginListDesc=\u6ce8\u610f\uff0c\u8fd9\u91cc\u5e76\u672a\u663e\u793a\u5b8c\u6574\u7684\u63d2\u4ef6\u5217\u8868\u3002\u4e00\u65e6\u521d\u59cb\u5b89\u88c5\u5b8c\u6210\u540e\uff0c\u53ef\u901a\u8fc7\u63d2\u4ef6\u7ba1\u7406\u5668\u5b89\u88c5\u5176\u4ed6\u63d2\u4ef6\u3002\u67e5\u770bwiki\u4e86\u89e3\u66f4\u591a\u3002 +installWizard_goBack=\u540e\u9000 +installWizard_goInstall=\u5b89\u88c5 +installWizard_installing_title=\u65b0\u624b\u5165\u95e8 +installWizard_installing_detailsLink=\u8be6\u60c5\u3002\u3002\u3002 +installWizard_installComplete_title=\u65b0\u624b\u5165\u95e8 +installWizard_installComplete_banner=Jenkins\u5df2\u5c31\u7eea\uff01 +installWizard_installComplete_bannerRestart=Jenkins\u5373\u5c06\u5c31\u7eea\uff01 +installWizard_pluginsInstalled_message=\u63d2\u4ef6\u5b89\u88c5\u5df2\u5b8c\u6210\u3002 +installWizard_installComplete_message=jenkins\u5b89\u88c5\u5df2\u5b8c\u6210\u3002 +installWizard_installComplete_finishButtonLabel=\u5f00\u59cb\u4f7f\u7528jenkins +installWizard_installComplete_installComplete_restartRequiredMessage=jenkins\u5b89\u88c5\u5df2\u5b8c\u6210\uff0c\u4f46\u90e8\u5206\u63d2\u4ef6\u9700\u8981\u91cd\u542fjenkins\u3002 +installWizard_installComplete_installComplete_restartRequiredNotSupportedMessage=jenkins\u5b89\u88c5\u5df2\u5b8c\u6210\uff0c\u4f46\u90e8\u5206\u63d2\u4ef6\u9700\u8981\u91cd\u542fjenkins\uff0c\u8be5\u5b9e\u4f8b\u4f3c\u4e4e\u4e0d\u652f\u6301\u81ea\u52a8\u91cd\u542f\u3002\u8bf7\u73b0\u5728\u624b\u52a8\u91cd\u542f\u5b9e\u4f8b\u4ee5\u5b8c\u6210\u5b89\u88c5\u3002 +installWizard_installComplete_restartLabel=\u91cd\u542f +installWizard_installIncomplete_title=\u6062\u590d\u5b89\u88c5 +installWizard_installIncomplete_banner=\u6062\u590d\u5b89\u88c5 +installWizard_installIncomplete_message=jenkins\u5728\u5b89\u88c5\u8fc7\u7a0b\u5df2\u91cd\u542f\uff0c\u90e8\u5206\u63d2\u4ef6\u4f3c\u4e4e\u672a\u5b89\u88c5\u3002 +installWizard_installIncomplete_resumeInstallationButtonLabel=\u6062\u590d +installWizard_saveFirstUser=\u4fdd\u5b58\u5e76\u5b8c\u6210 +installWizard_skipFirstUser=\u4f7f\u7528admin\u8d26\u6237\u7ee7\u7eed +installWizard_firstUserSkippedMessage=
    \ +\u4f60\u5df2\u8df3\u8fc7\u521b\u5efaadmin\u7528\u6237\u7684\u6b65\u9aa4\u3002\u8981\u767b\u5f55\u8bf7\u4f7f\u7528\u7528\u6237\u540d\uff1a'admin' \ +\u53ca\u7528\u4e8e\u8bbf\u95ee\u5b89\u88c5\u5411\u5bfc\u7684\u7ba1\u7406\u5458\u5bc6\u7801\u3002\ +
    +installWizard_addFirstUser_title=\u65b0\u624b\u5165\u95e8 +installWizard_configureProxy_label=\u914d\u7f6e\u4ee3\u7406 +installWizard_configureProxy_save=\u4fdd\u5b58\u5e76\u7ee7\u7eed +installWizard_gettingStarted_title=\u65b0\u624b\u5165\u95e8 +installWizard_saveSecurity=\u4fdd\u5b58\u5e76\u7ee7\u7eed +installWizard_skipPluginInstallations=\u8df3\u8fc7\u63d2\u4ef6\u5b89\u88c5 +installWizard_installIncomplete_dependenciesLabel=\u4f9d\u8d56 +installWizard_installingConsole_dependencyIndicatorNote=** - \u9700\u8981\u4f9d\u8d56 +installWizard_websiteLinkLabel=\u7f51\u7ad9 +installWizard_pluginInstallFailure_title=\u5b89\u88c5\u5931\u8d25 +installWizard_pluginInstallFailure_message=\u90e8\u5206\u63d2\u4ef6\u5b89\u88c5\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5\u6216\u7ee7\u7eed +installWizard_continue=\u7ee7\u7eed +installWizard_retry=\u91cd\u8bd5 +installWizard_upgradePanel_title=\u66f4\u65b0 +installWizard_upgradePanel_banner=\u6b22\u8fce\u4f7f\u7528jenkins {0} \uff01 +installWizard_upgradePanel_message=jenkins {0} \u5305\u62ec\u4e00\u4e9b\u4e0d\u9519\u7684\u65b0\u7279\u6027\uff0c\u6211\u4eec\u8ba4\u4e3a\u4f60\u53ef\u80fd\u4f1a\u559c\u6b22\uff0c\u5b89\u88c5\u8fd9\u4e9b\u9644\u52a0\u63d2\u4ef6\u6765\u4f53\u9a8c\uff01 +installWizard_upgradePanel_skipRecommendedPlugins=\u4e0d\uff0c\u8c22\u8c22 +installWizard_upgradeComplete_title=\u66f4\u65b0 +installWizard_pluginsInstalled_banner=\u6b22\u8fce\u4f7f\u7528jenkins {0} \uff01 +installWizard_upgradeComplete_message=\u606d\u559c\uff01\u4f60\u5df2\u66f4\u65b0\u5230jenkins {0} \u3002

    \u8bbf\u95eejenkins\u7f51\u7ad9\u4e86\u89e3\u66f4\u591a\u65b0\u7279\u6027\u7684\u4fe1\u606f\u3002 +installWizard_upgradeSkipped_title=\u66f4\u65b0 +installWizard_upgradeSkipped_banner=\u7279\u6027\u672a\u5b89\u88c5 +installWizard_upgradeSkipped_message=

    \u5efa\u8bae\u7684\u63d2\u4ef6\u5c06\u4e0d\u88ab\u5b89\u88c5\u3002
    \ +

    \u5982\u679c\u4f60\u6539\u53d8\u4e3b\u610f\u4e86\uff0c\u8fd8\u53ef\u4ee5\u4ece\u63d2\u4ef6\u7ba1\u7406\u5668\u5b89\u88c5\u65b0\u7279\u6027\u3002

    \ +

    \u8bbf\u95eejenkins\u5b98\u7f51\uff0c\u4e86\u89e3\u8fd9\u4e9b\u65b0\u7279\u6027\u5982\u4f55\u63d0\u5347jenkins\u4f53\u9a8c\u3002

    +installWizard_upgrading_title=\u6b63\u5728\u5b89\u88c5\u63d2\u4ef6 +installWizard_upgradeComplete_finishButtonLabel=\u5b8c\u6210 -- GitLab From ac2a1aaf895020bc80fd951ced748820975df6aa Mon Sep 17 00:00:00 2001 From: godfath3r Date: Sun, 7 Jan 2018 15:39:10 +0200 Subject: [PATCH 0226/1380] =?UTF-8?q?[JENKINS-46911]=20createProjectFromXM?= =?UTF-8?q?L=20not=20recognizing=20unsafe=20character=E2=80=A6=20(#3057)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [JENKINS-46911] createProjectFromXML not recognizing unsafe character '/' * Better place for testCreateProjectCheckGoodName() * Fix failed test * Make changes suggested on PR review. * Remove Failure exception, instead throw IOException. Add javadoc * [JENKINS-46911] - Add TODO according to the comment from @jtnord. --- .../java/hudson/model/ItemGroupMixIn.java | 12 ++++++ core/src/main/java/jenkins/model/Jenkins.java | 10 ++++- .../java/hudson/model/ItemGroupMixInTest.java | 39 +++++++++++++++++++ .../test/java/jenkins/model/JenkinsTest.java | 38 ++++++++++++++++++ .../triggers/ReverseBuildTriggerTest.java | 2 +- 5 files changed, 99 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java index 13f72c3f07..07bfc42206 100644 --- a/core/src/main/java/hudson/model/ItemGroupMixIn.java +++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java @@ -258,6 +258,12 @@ public abstract class ItemGroupMixIn { } public synchronized TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException { + try { + Jenkins.checkGoodName(name); + } catch (Failure e){ + throw new IOException("Not a valid project name", e); + } + acl.checkPermission(Item.CREATE); Jenkins.getInstance().getProjectNamingStrategy().checkName(name); @@ -309,6 +315,12 @@ public abstract class ItemGroupMixIn { public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify ) throws IOException { + try { + Jenkins.checkGoodName(name); + } catch (Failure e){ + throw new IOException("Not a valid project name", e); + } + acl.checkPermission(Item.CREATE); type.checkApplicableIn(parent); acl.getACL().checkCreatePermission(parent, type); diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 4a9410e7bc..ce4429a78b 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -3810,6 +3810,14 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve } /** + * Creates a new project from the given xml. + * + * @param name The name of new project. + * @param xml Projects xml. + * + * @throws IOException if the given name is not good or could not create project. + * + * @see #checkGoodName(String) * @since 1.319 */ public TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException { @@ -3842,7 +3850,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve * if the given name is not good */ public static void checkGoodName(String name) throws Failure { - if(name==null || name.length()==0) + if(name==null || name.trim().length()==0) throw new Failure(Messages.Hudson_NoName()); if(".".equals(name.trim())) diff --git a/test/src/test/java/hudson/model/ItemGroupMixInTest.java b/test/src/test/java/hudson/model/ItemGroupMixInTest.java index 79ea318b8b..ff24a5039e 100644 --- a/test/src/test/java/hudson/model/ItemGroupMixInTest.java +++ b/test/src/test/java/hudson/model/ItemGroupMixInTest.java @@ -33,6 +33,7 @@ import hudson.tasks.BuildWrapperDescriptor; import hudson.tasks.Builder; import hudson.tasks.Publisher; import hudson.triggers.Trigger; +import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; import java.io.ByteArrayInputStream; @@ -49,6 +50,7 @@ import org.jvnet.hudson.test.TestExtension; import org.jvnet.hudson.test.recipes.LocalData; import java.io.File; +import java.io.InputStream; import java.util.Collection; import java.util.List; @@ -207,4 +209,41 @@ public class ItemGroupMixInTest { assertThat(Items.getConfigFile(foo).asString(), containsString("")); } + @Issue("JENKINS-46911") + @Test + public void testCreateProjectCheckGoodName() throws Exception{ + String xmlFile = "\n" + + "\n" + + "\n" + + "false\n" + + "\n" + + "\n" + + "false\n" + + "\n" + + "\n" + + "\n" + + ""; + String[] illegalNames = {"ab\\c", "abc/", "ab/c", "", " ", " ", "..", ".", "?", "*", "6%", + "x!", "-@", "#", "$", "^", "&", "|", "<", ">", "[", "]", ":", ";", "../.", null}; + Jenkins j = Jenkins.getInstance(); + + int created = 0; + for (String name : illegalNames){ + try{ + InputStream is = new ByteArrayInputStream(xmlFile.getBytes()); + j.createProjectFromXML(name, is); + created++; + } catch (IOException e){} + } + + for (String name : illegalNames){ + try{ + j.createProject(FreeStyleProject.class, name); + created++; + } catch (IOException e){} + } + // Checks that no illegal named project has been created. + assertEquals(0, created); + } + } diff --git a/test/src/test/java/jenkins/model/JenkinsTest.java b/test/src/test/java/jenkins/model/JenkinsTest.java index a68ee0a4d6..ce7f4212cb 100644 --- a/test/src/test/java/jenkins/model/JenkinsTest.java +++ b/test/src/test/java/jenkins/model/JenkinsTest.java @@ -72,6 +72,7 @@ import org.jvnet.hudson.test.ExtractResourceSCM; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule.WebClient; import org.jvnet.hudson.test.TestExtension; +import org.jvnet.hudson.test.WithoutJenkins; import org.kohsuke.stapler.HttpResponse; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -683,4 +684,41 @@ public class JenkinsTest { VersionNumber nullVersion = Jenkins.getStoredVersion(); assertNull(nullVersion); } + + @Issue("JENKINS-46911") + @WithoutJenkins + @Test + public void testCheckGoodName() throws Exception{ + String[] illegalNames = {"ab\\c", "abc/", "ab/c", " ", " ", "", "..", ".", "?", "*", "6%", + "x!", "-@", "#", "$", "^", "&", "|", "<", ">", "[", "]", ":", ";", "../.", null}; + String[] legalNames = {"abc", "a bc", "abc ", " abc", "a.bc", ". ."}; + String msg = "checkGoodNameTest found a legal name. Please check."; + int exceptions = 0; + int goodNames = 0; + + for (String name : illegalNames){ + try { + Jenkins.checkGoodName(name); + goodNames++; + } catch (Failure e){ + exceptions++; + } + } + assertEquals(msg, illegalNames.length, exceptions); + assertEquals(0, goodNames); + + exceptions = 0; + goodNames = 0; + for (String name : legalNames){ + try { + Jenkins.checkGoodName(name); + goodNames++; + } catch (Failure e){ + exceptions++; + } + } + assertEquals(msg, 0, exceptions); + // TODO: store the name in an list and then assert the size of that list is zero and get a view of what failed. rather than 2 things failed. + assertEquals(legalNames.length, goodNames); + } } diff --git a/test/src/test/java/jenkins/triggers/ReverseBuildTriggerTest.java b/test/src/test/java/jenkins/triggers/ReverseBuildTriggerTest.java index 91b5f5ba75..6319115451 100644 --- a/test/src/test/java/jenkins/triggers/ReverseBuildTriggerTest.java +++ b/test/src/test/java/jenkins/triggers/ReverseBuildTriggerTest.java @@ -97,7 +97,7 @@ public class ReverseBuildTriggerTest { .grant(Computer.BUILD).everywhere().to("alice", "bob") .grant(Jenkins.ADMINISTER).everywhere().to("admin"); r.jenkins.setAuthorizationStrategy(auth); - String upstreamName = "upstr3@m"; // do not clash with English messages! + String upstreamName = "upstr3am"; // do not clash with English messages! final FreeStyleProject upstream = r.createFreeStyleProject(upstreamName); String downstreamName = "d0wnstr3am"; FreeStyleProject downstream = r.createFreeStyleProject(downstreamName); -- GitLab From 6df06fc19a4b7ed015ab5213e2dc8d25beb2f607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20A=2E=20F=C3=BCrbacher?= Date: Sun, 7 Jan 2018 14:43:49 +0100 Subject: [PATCH 0227/1380] [JENKINS-48447] Fixed HTTP 404 error when clicking on newView sidebar link from an other view. (#3178) * [JENKINS-48447] Fixed HTTP 404 error when clicking on newView sidebar link from an other view. * [JENKINS-48447] Fixed unit tests for NewViewLink * [JENKINS-48447] Load url name each time object gets initialized and adapted tests. * [JENKINS-48447] Rewrite tests for url name; fixed indenting. * [JENKINS-48447] Fixed more identing. * [JENKINS-48447] Added @Restricted to NewViewLink; undo URL_NAME renaming. --- .../main/java/jenkins/model/NewViewLink.java | 7 ++++- .../java/jenkins/model/NewViewLinkTest.java | 29 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/jenkins/model/NewViewLink.java b/core/src/main/java/jenkins/model/NewViewLink.java index 9ce71cffc8..5f4e404167 100644 --- a/core/src/main/java/jenkins/model/NewViewLink.java +++ b/core/src/main/java/jenkins/model/NewViewLink.java @@ -8,7 +8,11 @@ import hudson.model.View; import java.util.Collections; import java.util.List; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; + @Extension +@Restricted(DoNotUse.class) public class NewViewLink extends TransientViewActionFactory { @VisibleForTesting @@ -40,7 +44,8 @@ public class NewViewLink extends TransientViewActionFactory { @Override public String getUrlName() { - return URL_NAME; + String urlName = Jenkins.getInstance().getRootUrl() + URL_NAME; + return urlName; } private boolean hasPermission(View view) { diff --git a/core/src/test/java/jenkins/model/NewViewLinkTest.java b/core/src/test/java/jenkins/model/NewViewLinkTest.java index 07dbbb6f5d..febd23a618 100644 --- a/core/src/test/java/jenkins/model/NewViewLinkTest.java +++ b/core/src/test/java/jenkins/model/NewViewLinkTest.java @@ -9,13 +9,36 @@ import static org.mockito.Mockito.when; import hudson.model.Action; import hudson.model.View; import java.util.List; + +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +@RunWith(PowerMockRunner.class) +@PrepareForTest({NewViewLink.class, Jenkins.class}) public class NewViewLinkTest { + + @Mock + private Jenkins jenkins; + + @Mock + private final String rootUrl = "https://127.0.0.1:8080/"; - private NewViewLink newViewLink = new NewViewLink(); + private NewViewLink newViewLink; private View view = mock(View.class); + + @Before + public void initTests() throws Exception { + PowerMockito.mockStatic(Jenkins.class); + PowerMockito.when(Jenkins.getInstance()).thenReturn(jenkins); + PowerMockito.when(jenkins.getRootUrl()).thenReturn(rootUrl); + newViewLink = new NewViewLink(); + } @Test public void getActionsHasPermission() throws Exception { @@ -27,7 +50,7 @@ public class NewViewLinkTest { final Action action = actions.get(0); assertEquals(Messages.NewViewLink_NewView(), action.getDisplayName()); assertEquals(NewViewLink.ICON_FILE_NAME, action.getIconFileName()); - assertEquals(NewViewLink.URL_NAME, action.getUrlName()); + assertEquals(rootUrl + NewViewLink.URL_NAME, action.getUrlName()); } @Test @@ -40,7 +63,7 @@ public class NewViewLinkTest { final Action action = actions.get(0); assertNull(action.getDisplayName()); assertNull(action.getIconFileName()); - assertEquals(NewViewLink.URL_NAME, action.getUrlName()); + assertEquals(rootUrl + NewViewLink.URL_NAME, action.getUrlName()); } } \ No newline at end of file -- GitLab From a79fdaa4b34b8f7fddb39bed3eabf4763940d11b Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sun, 7 Jan 2018 18:26:57 +0100 Subject: [PATCH 0228/1380] =?UTF-8?q?Revert=20"[JENKINS-46911]=20createPro?= =?UTF-8?q?jectFromXML=20not=20recognizing=20unsafe=20character=E2=80=A6"?= =?UTF-8?q?=20(#3218)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "[JENKINS-48447] Fixed HTTP 404 error when clicking on newView sidebar link from an other view. (#3178)" This reverts commit 6df06fc19a4b7ed015ab5213e2dc8d25beb2f607. * Revert "[JENKINS-46911] createProjectFromXML not recognizing unsafe character… (#3057)" This reverts commit ac2a1aaf895020bc80fd951ced748820975df6aa. --- .../java/hudson/model/ItemGroupMixIn.java | 12 ------ core/src/main/java/jenkins/model/Jenkins.java | 10 +---- .../java/hudson/model/ItemGroupMixInTest.java | 39 ------------------- .../test/java/jenkins/model/JenkinsTest.java | 38 ------------------ .../triggers/ReverseBuildTriggerTest.java | 2 +- 5 files changed, 2 insertions(+), 99 deletions(-) diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java index 07bfc42206..13f72c3f07 100644 --- a/core/src/main/java/hudson/model/ItemGroupMixIn.java +++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java @@ -258,12 +258,6 @@ public abstract class ItemGroupMixIn { } public synchronized TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException { - try { - Jenkins.checkGoodName(name); - } catch (Failure e){ - throw new IOException("Not a valid project name", e); - } - acl.checkPermission(Item.CREATE); Jenkins.getInstance().getProjectNamingStrategy().checkName(name); @@ -315,12 +309,6 @@ public abstract class ItemGroupMixIn { public synchronized TopLevelItem createProject( TopLevelItemDescriptor type, String name, boolean notify ) throws IOException { - try { - Jenkins.checkGoodName(name); - } catch (Failure e){ - throw new IOException("Not a valid project name", e); - } - acl.checkPermission(Item.CREATE); type.checkApplicableIn(parent); acl.getACL().checkCreatePermission(parent, type); diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index ce4429a78b..4a9410e7bc 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -3810,14 +3810,6 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve } /** - * Creates a new project from the given xml. - * - * @param name The name of new project. - * @param xml Projects xml. - * - * @throws IOException if the given name is not good or could not create project. - * - * @see #checkGoodName(String) * @since 1.319 */ public TopLevelItem createProjectFromXML(String name, InputStream xml) throws IOException { @@ -3850,7 +3842,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve * if the given name is not good */ public static void checkGoodName(String name) throws Failure { - if(name==null || name.trim().length()==0) + if(name==null || name.length()==0) throw new Failure(Messages.Hudson_NoName()); if(".".equals(name.trim())) diff --git a/test/src/test/java/hudson/model/ItemGroupMixInTest.java b/test/src/test/java/hudson/model/ItemGroupMixInTest.java index ff24a5039e..79ea318b8b 100644 --- a/test/src/test/java/hudson/model/ItemGroupMixInTest.java +++ b/test/src/test/java/hudson/model/ItemGroupMixInTest.java @@ -33,7 +33,6 @@ import hudson.tasks.BuildWrapperDescriptor; import hudson.tasks.Builder; import hudson.tasks.Publisher; import hudson.triggers.Trigger; -import jenkins.model.Jenkins; import org.apache.commons.io.FileUtils; import java.io.ByteArrayInputStream; @@ -50,7 +49,6 @@ import org.jvnet.hudson.test.TestExtension; import org.jvnet.hudson.test.recipes.LocalData; import java.io.File; -import java.io.InputStream; import java.util.Collection; import java.util.List; @@ -209,41 +207,4 @@ public class ItemGroupMixInTest { assertThat(Items.getConfigFile(foo).asString(), containsString("")); } - @Issue("JENKINS-46911") - @Test - public void testCreateProjectCheckGoodName() throws Exception{ - String xmlFile = "\n" + - "\n" + - "\n" + - "false\n" + - "\n" + - "\n" + - "false\n" + - "\n" + - "\n" + - "\n" + - ""; - String[] illegalNames = {"ab\\c", "abc/", "ab/c", "", " ", " ", "..", ".", "?", "*", "6%", - "x!", "-@", "#", "$", "^", "&", "|", "<", ">", "[", "]", ":", ";", "../.", null}; - Jenkins j = Jenkins.getInstance(); - - int created = 0; - for (String name : illegalNames){ - try{ - InputStream is = new ByteArrayInputStream(xmlFile.getBytes()); - j.createProjectFromXML(name, is); - created++; - } catch (IOException e){} - } - - for (String name : illegalNames){ - try{ - j.createProject(FreeStyleProject.class, name); - created++; - } catch (IOException e){} - } - // Checks that no illegal named project has been created. - assertEquals(0, created); - } - } diff --git a/test/src/test/java/jenkins/model/JenkinsTest.java b/test/src/test/java/jenkins/model/JenkinsTest.java index ce7f4212cb..a68ee0a4d6 100644 --- a/test/src/test/java/jenkins/model/JenkinsTest.java +++ b/test/src/test/java/jenkins/model/JenkinsTest.java @@ -72,7 +72,6 @@ import org.jvnet.hudson.test.ExtractResourceSCM; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule.WebClient; import org.jvnet.hudson.test.TestExtension; -import org.jvnet.hudson.test.WithoutJenkins; import org.kohsuke.stapler.HttpResponse; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -684,41 +683,4 @@ public class JenkinsTest { VersionNumber nullVersion = Jenkins.getStoredVersion(); assertNull(nullVersion); } - - @Issue("JENKINS-46911") - @WithoutJenkins - @Test - public void testCheckGoodName() throws Exception{ - String[] illegalNames = {"ab\\c", "abc/", "ab/c", " ", " ", "", "..", ".", "?", "*", "6%", - "x!", "-@", "#", "$", "^", "&", "|", "<", ">", "[", "]", ":", ";", "../.", null}; - String[] legalNames = {"abc", "a bc", "abc ", " abc", "a.bc", ". ."}; - String msg = "checkGoodNameTest found a legal name. Please check."; - int exceptions = 0; - int goodNames = 0; - - for (String name : illegalNames){ - try { - Jenkins.checkGoodName(name); - goodNames++; - } catch (Failure e){ - exceptions++; - } - } - assertEquals(msg, illegalNames.length, exceptions); - assertEquals(0, goodNames); - - exceptions = 0; - goodNames = 0; - for (String name : legalNames){ - try { - Jenkins.checkGoodName(name); - goodNames++; - } catch (Failure e){ - exceptions++; - } - } - assertEquals(msg, 0, exceptions); - // TODO: store the name in an list and then assert the size of that list is zero and get a view of what failed. rather than 2 things failed. - assertEquals(legalNames.length, goodNames); - } } diff --git a/test/src/test/java/jenkins/triggers/ReverseBuildTriggerTest.java b/test/src/test/java/jenkins/triggers/ReverseBuildTriggerTest.java index 6319115451..91b5f5ba75 100644 --- a/test/src/test/java/jenkins/triggers/ReverseBuildTriggerTest.java +++ b/test/src/test/java/jenkins/triggers/ReverseBuildTriggerTest.java @@ -97,7 +97,7 @@ public class ReverseBuildTriggerTest { .grant(Computer.BUILD).everywhere().to("alice", "bob") .grant(Jenkins.ADMINISTER).everywhere().to("admin"); r.jenkins.setAuthorizationStrategy(auth); - String upstreamName = "upstr3am"; // do not clash with English messages! + String upstreamName = "upstr3@m"; // do not clash with English messages! final FreeStyleProject upstream = r.createFreeStyleProject(upstreamName); String downstreamName = "d0wnstr3am"; FreeStyleProject downstream = r.createFreeStyleProject(downstreamName); -- GitLab From bd2cb36a87a36877a75182019808c12b98306a50 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Sun, 7 Jan 2018 23:26:51 +0100 Subject: [PATCH 0229/1380] Be less dramatic with the dataloss criteria As requested especially by Sam --- .../src/main/java/hudson/util/AtomicFileWriter.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 968c061668..1da78f70dc 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -54,12 +54,12 @@ public class AtomicFileWriter extends Writer { private static final Logger LOGGER = Logger.getLogger(AtomicFileWriter.class.getName()); - private static /* final */ boolean LOSE_INTEGRITY = SystemProperties.getBoolean( - AtomicFileWriter.class.getName() + ".LOSE_DATA_INTEGRITY_FOR_PERFORMANCE"); + private static /* final */ boolean DISABLE_FORCED_FLUSH = SystemProperties.getBoolean( + AtomicFileWriter.class.getName() + ".DISABLE_FORCED_FLUSH"); static { - if (LOSE_INTEGRITY) { - LOGGER.log(Level.SEVERE, "LOSE_DATA_INTEGRITY_FOR_PERFORMANCE flag used, YOU RISK LOSING DATA."); + if (DISABLE_FORCED_FLUSH) { + LOGGER.log(Level.WARNING, "DISABLE_FORCED_FLUSH flag used, this could result in dataloss if failures happen in your storage subsystem."); } } @@ -143,10 +143,7 @@ public class AtomicFileWriter extends Writer { throw new IOException("Failed to create a temporary file in "+ dir,e); } - if (LOSE_INTEGRITY) { - // We should log it in WARN, but as this code is called often, this would create huge logs in prod system - // Hence why a similar log is logged once above in a static block. - LOGGER.log(Level.FINE, "WARNING: YOU SET A FLAG THAT COULD LEAD TO DATA LOSS."); + if (DISABLE_FORCED_FLUSH) { integrityOnFlush = false; integrityOnClose = false; } -- GitLab From 235a9bad68152fbe18c1e94620563ba43fb224f4 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 7 Jan 2018 18:20:10 -0800 Subject: [PATCH 0230/1380] [maven-release-plugin] prepare release jenkins-2.101 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index d9c428b59a..9bc580d2fa 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.101-SNAPSHOT + 2.101 cli diff --git a/core/pom.xml b/core/pom.xml index ed9e193cf7..a7328aef6b 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.101-SNAPSHOT + 2.101 jenkins-core diff --git a/pom.xml b/pom.xml index c3f30f1cc0..bda578e50b 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.101-SNAPSHOT + 2.101 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.101 diff --git a/test/pom.xml b/test/pom.xml index 0454104fac..555c51e13d 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.101-SNAPSHOT + 2.101 test diff --git a/war/pom.xml b/war/pom.xml index 40cde82335..96d7f71fe2 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.101-SNAPSHOT + 2.101 jenkins-war -- GitLab From 4dbe772a0b75494ca39d2645a42eec1f6235e9a8 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 7 Jan 2018 18:20:10 -0800 Subject: [PATCH 0231/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 9bc580d2fa..be0be03f03 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.101 + 2.102-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index a7328aef6b..1186c73e13 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.101 + 2.102-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index bda578e50b..b75c775c41 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.101 + 2.102-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.101 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 555c51e13d..23e7bc0e62 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.101 + 2.102-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 96d7f71fe2..e930a6dd80 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.101 + 2.102-SNAPSHOT jenkins-war -- GitLab From 08ff433b3eb304277b012961b05e8bccf69e2214 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 8 Jan 2018 10:49:07 -0500 Subject: [PATCH 0232/1380] Bump. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index bd85560703..ffd1d5d5b8 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ THE SOFTWARE. 3.0.0 - 3.16-20180103.225323-4 + 3.16-20180108.154819-5 2.60 -- GitLab From afa209afea983411f43930bcb5360ed39721fb2c Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Mon, 8 Jan 2018 14:16:25 -0500 Subject: [PATCH 0233/1380] Add new ToolDescriptor constructor to allow subclasses to be in a separate file from their describable --- core/src/main/java/hudson/tools/ToolDescriptor.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/main/java/hudson/tools/ToolDescriptor.java b/core/src/main/java/hudson/tools/ToolDescriptor.java index 3c944d86dc..3389d1bf8f 100644 --- a/core/src/main/java/hudson/tools/ToolDescriptor.java +++ b/core/src/main/java/hudson/tools/ToolDescriptor.java @@ -54,6 +54,14 @@ public abstract class ToolDescriptor extends Descrip private T[] installations; + protected ToolDescriptor() { + super(); + } + + protected ToolDescriptor(Class clazz) { + super(clazz); + } + /** * Configured instances of {@link ToolInstallation}s. * -- GitLab From 08a2b1e4410cb5713f43227096780f0933b909d7 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Mon, 8 Jan 2018 20:52:30 +0100 Subject: [PATCH 0234/1380] Update 'since FIXME' and 'since TODO' Javadoc tags --- core/src/main/java/hudson/ExtensionList.java | 2 +- core/src/main/java/hudson/XmlFile.java | 2 +- core/src/main/java/hudson/model/Cause.java | 2 +- core/src/main/java/hudson/model/ItemGroup.java | 8 ++++---- .../main/java/hudson/model/StringParameterDefinition.java | 2 +- core/src/main/java/hudson/model/StringParameterValue.java | 2 +- core/src/main/java/hudson/model/User.java | 6 +++--- core/src/main/java/hudson/security/AccessControlled.java | 2 +- core/src/main/java/hudson/slaves/JNLPLauncher.java | 4 ++-- core/src/main/java/hudson/util/ArgumentListBuilder.java | 2 +- core/src/main/java/hudson/util/AtomicFileWriter.java | 2 +- core/src/main/java/hudson/util/PluginServletFilter.java | 2 +- core/src/main/java/hudson/util/TimeUnit2.java | 6 +----- core/src/main/java/hudson/util/XStream2.java | 4 ++-- core/src/main/java/jenkins/AgentProtocol.java | 2 +- core/src/main/java/jenkins/install/SetupWizard.java | 4 ++-- core/src/main/java/jenkins/model/Jenkins.java | 2 +- .../jenkins/security/csrf/CSRFAdministrativeMonitor.java | 2 +- .../jenkins/slaves/DeprecatedAgentProtocolMonitor.java | 2 +- .../main/java/jenkins/slaves/RemotingWorkDirSettings.java | 2 +- core/src/main/java/jenkins/util/TimeDuration.java | 2 +- 21 files changed, 29 insertions(+), 33 deletions(-) diff --git a/core/src/main/java/hudson/ExtensionList.java b/core/src/main/java/hudson/ExtensionList.java index 2420f05057..9a4c77d2f5 100644 --- a/core/src/main/java/hudson/ExtensionList.java +++ b/core/src/main/java/hudson/ExtensionList.java @@ -428,7 +428,7 @@ public class ExtensionList extends AbstractList implements OnMaster { * @return the singleton instance of the given type in its list. * @throws IllegalStateException if there are no instances, or more than one * - * @since TODO + * @since 2.87 */ public static @Nonnull U lookupSingleton(Class type) { ExtensionList all = lookup(type); diff --git a/core/src/main/java/hudson/XmlFile.java b/core/src/main/java/hudson/XmlFile.java index bde1ac12a4..243318e229 100644 --- a/core/src/main/java/hudson/XmlFile.java +++ b/core/src/main/java/hudson/XmlFile.java @@ -164,7 +164,7 @@ public final class XmlFile { /** * Variant of {@link #unmarshal(Object)} applying {@link XStream2#unmarshal(HierarchicalStreamReader, Object, DataHolder, boolean)}. - * @since FIXME + * @since 2.99 */ public Object unmarshalNullingOut(Object o) throws IOException { return unmarshal(o, true); diff --git a/core/src/main/java/hudson/model/Cause.java b/core/src/main/java/hudson/model/Cause.java index 7b16918f04..bdac357b6b 100644 --- a/core/src/main/java/hudson/model/Cause.java +++ b/core/src/main/java/hudson/model/Cause.java @@ -416,7 +416,7 @@ public abstract class Cause { /** * Constructor. * @param userId User ID. {@code null} if the user is unknown. - * @since TODO + * @since 2.96 */ public UserIdCause(@CheckForNull String userId) { this.userId = userId; diff --git a/core/src/main/java/hudson/model/ItemGroup.java b/core/src/main/java/hudson/model/ItemGroup.java index 12e4c19ee4..2397587a0b 100644 --- a/core/src/main/java/hudson/model/ItemGroup.java +++ b/core/src/main/java/hudson/model/ItemGroup.java @@ -93,7 +93,7 @@ public interface ItemGroup extends PersistenceRoot, ModelObject /** * Gets all the {@link Item}s recursively in the {@link ItemGroup} tree * and filter them by the given type. - * @since FIXME + * @since 2.93 */ default List getAllItems(Class type) { return Items.getAllItems(this, type); @@ -102,7 +102,7 @@ public interface ItemGroup extends PersistenceRoot, ModelObject /** * Gets all the {@link Item}s unordered, lazily and recursively in the {@link ItemGroup} tree * and filter them by the given type. - * @since FIXME + * @since 2.93 */ default Iterable allItems(Class type) { return Items.allItems(this, type); @@ -110,7 +110,7 @@ public interface ItemGroup extends PersistenceRoot, ModelObject /** * Gets all the items recursively. - * @since FIXME + * @since 2.93 */ default List getAllItems() { return getAllItems(Item.class); @@ -118,7 +118,7 @@ public interface ItemGroup extends PersistenceRoot, ModelObject /** * Gets all the items unordered, lazily and recursively. - * @since FIXME + * @since 2.93 */ default Iterable allItems() { return allItems(Item.class); diff --git a/core/src/main/java/hudson/model/StringParameterDefinition.java b/core/src/main/java/hudson/model/StringParameterDefinition.java index f8308d8397..3573199a01 100644 --- a/core/src/main/java/hudson/model/StringParameterDefinition.java +++ b/core/src/main/java/hudson/model/StringParameterDefinition.java @@ -92,7 +92,7 @@ public class StringParameterDefinition extends SimpleParameterDefinition { * @return trim - {@code true}, if trim options has been selected, else return {@code false}. * Trimming will happen when creating {@link StringParameterValue}s, * the value in the config will not be changed. - * @since TODO + * @since 2.90 */ public boolean isTrim() { return trim; diff --git a/core/src/main/java/hudson/model/StringParameterValue.java b/core/src/main/java/hudson/model/StringParameterValue.java index 14c2e947cb..a1bf1516e8 100644 --- a/core/src/main/java/hudson/model/StringParameterValue.java +++ b/core/src/main/java/hudson/model/StringParameterValue.java @@ -76,7 +76,7 @@ public class StringParameterValue extends ParameterValue { /** * Trimming for value - * @since TODO + * @since 2.90 */ public void doTrim() { if (value != null) { diff --git a/core/src/main/java/hudson/model/User.java b/core/src/main/java/hudson/model/User.java index 418e49aa0d..7f132023b7 100644 --- a/core/src/main/java/hudson/model/User.java +++ b/core/src/main/java/hudson/model/User.java @@ -587,7 +587,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr * * @param idOrFullName User ID or full name * @return User instance. It will be created on-demand. - * @since TODO + * @since 2.91 */ public static @Nonnull User getOrCreateByIdOrFullName(@Nonnull String idOrFullName) { return get(idOrFullName,true, Collections.emptyMap()); @@ -1172,7 +1172,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr /** * Gets all extension points, sorted by priority. * @return Sorted list of extension point implementations. - * @since TODO + * @since 2.93 */ public static List all() { List resolvers = new ArrayList<>(ExtensionList.lookup(CanonicalIdResolver.class)); @@ -1185,7 +1185,7 @@ public class User extends AbstractModelObject implements AccessControlled, Descr * @param idOrFullName ID or full name of the user * @param context Context * @return Resolved User ID or {@code null} if the user ID cannot be resolved. - * @since TODO + * @since 2.93 */ @CheckForNull public static String resolve(@Nonnull String idOrFullName, @Nonnull Map context) { diff --git a/core/src/main/java/hudson/security/AccessControlled.java b/core/src/main/java/hudson/security/AccessControlled.java index 5a2b246a2b..9aa084df2b 100644 --- a/core/src/main/java/hudson/security/AccessControlled.java +++ b/core/src/main/java/hudson/security/AccessControlled.java @@ -56,7 +56,7 @@ public interface AccessControlled { /** * Convenient short-cut for {@code getACL().hasPermission(a, permission)} - * @since FIXME + * @since 2.92 */ default boolean hasPermission(@Nonnull Authentication a, @Nonnull Permission permission) { if (a == ACL.SYSTEM) { diff --git a/core/src/main/java/hudson/slaves/JNLPLauncher.java b/core/src/main/java/hudson/slaves/JNLPLauncher.java index d0852590c3..eeafc2bb7f 100644 --- a/core/src/main/java/hudson/slaves/JNLPLauncher.java +++ b/core/src/main/java/hudson/slaves/JNLPLauncher.java @@ -115,7 +115,7 @@ public class JNLPLauncher extends ComputerLauncher { /** * Returns work directory settings. * - * @since TODO + * @since 2.72 */ @Nonnull public RemotingWorkDirSettings getWorkDirSettings() { @@ -172,7 +172,7 @@ public class JNLPLauncher extends ComputerLauncher { * By default the configuration is displayed only for {@link JNLPLauncher}, * but the implementation can be overridden. * @return {@code true} if work directories are supported by the launcher type. - * @since TODO + * @since 2.73 */ public boolean isWorkDirSupported() { // This property is included only for JNLPLauncher by default. diff --git a/core/src/main/java/hudson/util/ArgumentListBuilder.java b/core/src/main/java/hudson/util/ArgumentListBuilder.java index fb488b2471..4388a7abb4 100644 --- a/core/src/main/java/hudson/util/ArgumentListBuilder.java +++ b/core/src/main/java/hudson/util/ArgumentListBuilder.java @@ -135,7 +135,7 @@ public class ArgumentListBuilder implements Serializable, Cloneable { } /** - * @since TODO + * @since 2.72 */ public ArgumentListBuilder add(@Nonnull Iterable args) { for (String arg : args) { diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index d63be2648c..7ab9f09383 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -216,7 +216,7 @@ public class AtomicFileWriter extends Writer { /** * Until the data is committed, this file captures * the written content. - * @since TODO + * @since 2.93 */ public Path getTemporaryPath() { return tmpPath; diff --git a/core/src/main/java/hudson/util/PluginServletFilter.java b/core/src/main/java/hudson/util/PluginServletFilter.java index aa2c53973e..7f7172240a 100644 --- a/core/src/main/java/hudson/util/PluginServletFilter.java +++ b/core/src/main/java/hudson/util/PluginServletFilter.java @@ -117,7 +117,7 @@ public class PluginServletFilter implements Filter, ExtensionPoint { * Checks whether the given filter is already registered in the chain. * @param filter the filter to check. * @return true if the filter is already registered in the chain. - * @since FIXME + * @since 2.94 */ public static boolean hasFilter(Filter filter) { Jenkins j = Jenkins.getInstanceOrNull(); diff --git a/core/src/main/java/hudson/util/TimeUnit2.java b/core/src/main/java/hudson/util/TimeUnit2.java index a70c69599c..36b465764d 100644 --- a/core/src/main/java/hudson/util/TimeUnit2.java +++ b/core/src/main/java/hudson/util/TimeUnit2.java @@ -65,13 +65,12 @@ import java.util.concurrent.TimeUnit; * implementation will be able to notice the passage of time at the * same granularity as the given TimeUnit. * - * @since 1.5 * @author Doug Lea * @deprecated use {@link TimeUnit}. (Java 5 did not have all the units required, so {@link TimeUnit2} was introduced * because it had better conversion until Java 6 went out.) */ @Deprecated -@RestrictedSince("TODO") +@RestrictedSince("2.80") @Restricted(NoExternalUse.class) public enum TimeUnit2 { NANOSECONDS { @@ -286,7 +285,6 @@ public enum TimeUnit2 { * or Long.MIN_VALUE if conversion would negatively * overflow, or Long.MAX_VALUE if it would positively overflow. * @see #convert - * @since 1.6 */ public long toMinutes(long duration) { throw new AbstractMethodError(); @@ -299,7 +297,6 @@ public enum TimeUnit2 { * or Long.MIN_VALUE if conversion would negatively * overflow, or Long.MAX_VALUE if it would positively overflow. * @see #convert - * @since 1.6 */ public long toHours(long duration) { throw new AbstractMethodError(); @@ -310,7 +307,6 @@ public enum TimeUnit2 { * @param duration the duration * @return the converted duration * @see #convert - * @since 1.6 */ public long toDays(long duration) { throw new AbstractMethodError(); diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 9d4ef9b99b..b77bb52d18 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -131,7 +131,7 @@ public class XStream2 extends XStream { * false to use the stock XStream behavior of leaving unmentioned {@code root} fields untouched * @see XmlFile#unmarshalNullingOut * @see JENKINS-21017 - * @since FIXME + * @since 2.99 */ public Object unmarshal(HierarchicalStreamReader reader, Object root, DataHolder dataHolder, boolean nullOut) { // init() is too early to do this @@ -207,7 +207,7 @@ public class XStream2 extends XStream { * Specifies that a given field of a given class should not be treated with laxity by {@link RobustCollectionConverter}. * @param clazz a class which we expect to hold a non-{@code transient} field * @param field a field name in that class - * @since TODO + * @since 2.85 this method can be used from outside core, before then it was restricted since initially added in 1.551 / 1.532.2 */ public void addCriticalField(Class clazz, String field) { reflectionConverter.addCriticalField(clazz, field); diff --git a/core/src/main/java/jenkins/AgentProtocol.java b/core/src/main/java/jenkins/AgentProtocol.java index f4917b3996..587fdefa69 100644 --- a/core/src/main/java/jenkins/AgentProtocol.java +++ b/core/src/main/java/jenkins/AgentProtocol.java @@ -68,7 +68,7 @@ public abstract class AgentProtocol implements ExtensionPoint { /** * Checks if the protocol is deprecated. * - * @since TODO + * @since 2.75 */ public boolean isDeprecated() { return false; diff --git a/core/src/main/java/jenkins/install/SetupWizard.java b/core/src/main/java/jenkins/install/SetupWizard.java index f15b468eed..6c70482369 100644 --- a/core/src/main/java/jenkins/install/SetupWizard.java +++ b/core/src/main/java/jenkins/install/SetupWizard.java @@ -519,7 +519,7 @@ public class SetupWizard extends PageDecorator { /** * Called upon install state update. * @param state the new install state. - * @since FIXME + * @since 2.94 */ public void onInstallStateUpdate(InstallState state) { if (state.isSetupComplete()) { @@ -531,7 +531,7 @@ public class SetupWizard extends PageDecorator { /** * Returns whether the setup wizard filter is currently registered. - * @since FIXME + * @since 2.94 */ public boolean hasSetupWizardFilter() { return PluginServletFilter.hasFilter(FORCE_SETUP_WIZARD_FILTER); diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 4a9410e7bc..a8787e050c 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -740,7 +740,7 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve * Gets the {@link Jenkins} singleton. * @return {@link Jenkins} instance * @throws IllegalStateException for the reasons that {@link #getInstanceOrNull} might return null - * @since FIXME + * @since 2.98 */ @Nonnull public static Jenkins get() throws IllegalStateException { diff --git a/core/src/main/java/jenkins/security/csrf/CSRFAdministrativeMonitor.java b/core/src/main/java/jenkins/security/csrf/CSRFAdministrativeMonitor.java index d942c5cab5..dbdb38e337 100644 --- a/core/src/main/java/jenkins/security/csrf/CSRFAdministrativeMonitor.java +++ b/core/src/main/java/jenkins/security/csrf/CSRFAdministrativeMonitor.java @@ -33,7 +33,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Monitor that the CSRF protection is enabled on the application. * - * @since TODO + * @since 2.85 */ @Extension @Symbol("csrf") diff --git a/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java b/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java index f519e4ecb6..96886fc996 100644 --- a/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java +++ b/core/src/main/java/jenkins/slaves/DeprecatedAgentProtocolMonitor.java @@ -41,7 +41,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; * Monitors enabled protocols and warns if an {@link AgentProtocol} is deprecated. * * @author Oleg Nenashev - * @since TODO + * @since 2.75 * @see AgentProtocol */ @Extension diff --git a/core/src/main/java/jenkins/slaves/RemotingWorkDirSettings.java b/core/src/main/java/jenkins/slaves/RemotingWorkDirSettings.java index c82a040145..6e998bc74d 100644 --- a/core/src/main/java/jenkins/slaves/RemotingWorkDirSettings.java +++ b/core/src/main/java/jenkins/slaves/RemotingWorkDirSettings.java @@ -46,7 +46,7 @@ import org.kohsuke.stapler.DataBoundConstructor; * See Remoting Work Dir Documentation. * * @author Oleg Nenashev - * @since TODO + * @since 2.72 */ public class RemotingWorkDirSettings implements Describable { diff --git a/core/src/main/java/jenkins/util/TimeDuration.java b/core/src/main/java/jenkins/util/TimeDuration.java index 61fa51f99d..8b0fff3f5e 100644 --- a/core/src/main/java/jenkins/util/TimeDuration.java +++ b/core/src/main/java/jenkins/util/TimeDuration.java @@ -43,7 +43,7 @@ public class TimeDuration { /** * Returns the duration of this instance in seconds. - * @since TODO + * @since 2.82 */ public int getTimeInSeconds() { return (int) (millis / 1000L); -- GitLab From cb308188430c3ae5bbf8864f9c704e6b7d660cb6 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Mon, 8 Jan 2018 15:02:28 -0500 Subject: [PATCH 0235/1380] Address review feedback --- core/src/main/java/hudson/tools/ToolDescriptor.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/hudson/tools/ToolDescriptor.java b/core/src/main/java/hudson/tools/ToolDescriptor.java index 3389d1bf8f..ad4ac1734a 100644 --- a/core/src/main/java/hudson/tools/ToolDescriptor.java +++ b/core/src/main/java/hudson/tools/ToolDescriptor.java @@ -54,10 +54,11 @@ public abstract class ToolDescriptor extends Descrip private T[] installations; - protected ToolDescriptor() { - super(); - } + protected ToolDescriptor() { } + /** + * @since FIXME + */ protected ToolDescriptor(Class clazz) { super(clazz); } -- GitLab From cf9885bdb16263fab1619c2233ef78ef7f12b2b5 Mon Sep 17 00:00:00 2001 From: Jez Halford Date: Tue, 9 Jan 2018 11:40:35 +0000 Subject: [PATCH 0236/1380] Revert the logo --- war/src/main/webapp/favicon.ico | Bin 9662 -> 17542 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/war/src/main/webapp/favicon.ico b/war/src/main/webapp/favicon.ico index 20ef7451138299b72d3f89b9b4dd5dffb7ab9efe..e6d7748d5576066255e271a3c16ebff7849637d6 100644 GIT binary patch literal 17542 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 Date: Tue, 9 Jan 2018 18:56:19 +0100 Subject: [PATCH 0237/1380] [JENKINS-22088] - Upgrade Extras Executable War from 1.36 to 1.37 --- war/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/war/pom.xml b/war/pom.xml index e930a6dd80..bbcbf954ad 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -49,7 +49,7 @@ THE SOFTWARE. org.jenkins-ci executable-war - 1.36 + 1.37 provided -- GitLab From e50ce3c7ee46f693e09d6d530d839dc53d136912 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 9 Jan 2018 14:52:51 -0500 Subject: [PATCH 0238/1380] Bump. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ffd1d5d5b8..81f543bb3a 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ THE SOFTWARE. 3.0.0 - 3.16-20180108.154819-5 + 3.16-20180109.195120-6 2.60 -- GitLab From 475ef3acd04ef13d3af1bdc28d37392c318ed612 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 9 Jan 2018 15:08:14 -0500 Subject: [PATCH 0239/1380] war/work/plugins/*.jpl broke mvn -f war hudson-dev:run. --- core/src/main/java/jenkins/security/ClassFilterImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/jenkins/security/ClassFilterImpl.java b/core/src/main/java/jenkins/security/ClassFilterImpl.java index 1cb6238d67..5f8ca82111 100644 --- a/core/src/main/java/jenkins/security/ClassFilterImpl.java +++ b/core/src/main/java/jenkins/security/ClassFilterImpl.java @@ -209,7 +209,7 @@ public class ClassFilterImpl extends ClassFilter { LOGGER.log(Level.WARNING, "problem checking " + loc, x); } } - if (Main.isUnitTest) { + if (Main.isUnitTest || Main.isDevelopmentMode) { if (loc.endsWith("/target/classes/")) { LOGGER.log(Level.FINE, "{0} seems to be current plugin classes, OK", loc); return true; -- GitLab From e8c86a374f0892c444719d6c90ef4901276cb029 Mon Sep 17 00:00:00 2001 From: Daniel Trebbien Date: Tue, 9 Jan 2018 12:21:59 -0800 Subject: [PATCH 0240/1380] Use fileToPath() (#3219) * Use fileToPath() This is a follow-up to PR 3210 on GitHub. See: https://github.com/jenkinsci/jenkins/pull/3210#discussion_r160045107 * Switch two File.toPath() calls to Util.fileToPath() * Delete an unused import --- core/src/main/java/hudson/Util.java | 4 +--- core/src/main/java/hudson/model/Queue.java | 5 +---- core/src/main/java/hudson/util/TextFile.java | 6 +++--- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index 0ab2f52713..f2f242a4a1 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -193,13 +193,11 @@ public class Util { StringBuilder str = new StringBuilder((int)logfile.length()); - try (BufferedReader r = Files.newBufferedReader(logfile.toPath(), charset)) { + try (BufferedReader r = Files.newBufferedReader(fileToPath(logfile), charset)) { char[] buf = new char[1024]; int len; while ((len = r.read(buf, 0, buf.length)) > 0) str.append(buf, 0, len); - } catch (InvalidPathException e) { - throw new IOException(e); } return str.toString(); diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index 17440221a7..6ad10d5f81 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -64,7 +64,6 @@ import hudson.model.queue.WorkUnitContext; import hudson.security.ACL; import hudson.security.AccessControlled; import java.nio.file.Files; -import java.nio.file.InvalidPathException; import jenkins.security.QueueItemAuthenticatorProvider; import jenkins.util.Timer; import hudson.triggers.SafeTimerTask; @@ -377,15 +376,13 @@ public class Queue extends ResourceController implements Saveable { // first try the old format File queueFile = getQueueFile(); if (queueFile.exists()) { - try (BufferedReader in = Files.newBufferedReader(queueFile.toPath(), Charset.defaultCharset())) { + try (BufferedReader in = Files.newBufferedReader(Util.fileToPath(queueFile), Charset.defaultCharset())) { String line; while ((line = in.readLine()) != null) { AbstractProject j = Jenkins.getInstance().getItemByFullName(line, AbstractProject.class); if (j != null) j.scheduleBuild(); } - } catch (InvalidPathException e) { - throw new IOException(e); } // discard the queue file now that we are done queueFile.delete(); diff --git a/core/src/main/java/hudson/util/TextFile.java b/core/src/main/java/hudson/util/TextFile.java index 401d275ea7..0ac1326ce7 100644 --- a/core/src/main/java/hudson/util/TextFile.java +++ b/core/src/main/java/hudson/util/TextFile.java @@ -25,6 +25,8 @@ package hudson.util; import com.google.common.collect.*; +import hudson.Util; + import java.nio.file.Files; import java.nio.file.InvalidPathException; import javax.annotation.Nonnull; @@ -69,12 +71,10 @@ public class TextFile { public String read() throws IOException { StringWriter out = new StringWriter(); PrintWriter w = new PrintWriter(out); - try (BufferedReader in = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) { + try (BufferedReader in = Files.newBufferedReader(Util.fileToPath(file), StandardCharsets.UTF_8)) { String line; while ((line = in.readLine()) != null) w.println(line); - } catch (InvalidPathException e) { - throw new IOException(e); } return out.toString(); } -- GitLab From 4f3ede2c5da0420731df504b4f886bef2d4b9bc0 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 9 Jan 2018 15:39:13 -0500 Subject: [PATCH 0241/1380] https://github.com/jenkinsci/xtrigger-lib/pull/9 --- .../main/resources/jenkins/security/whitelisted-classes.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 3e4d61134e..30062e29d7 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -12,7 +12,7 @@ com.google.common.collect.RegularImmutableSortedSet com.google.common.collect.SingletonImmutableList com.google.common.collect.SingletonImmutableSet -# TODO remove when https://github.com/jenkinsci/lib-jenkins-maven-embedder/pull/15 is widely adopted in maven-plugin +# TODO remove when maven-plugin 3.1 is widely adopted hudson.maven.MavenInformation java.io.File @@ -93,6 +93,10 @@ org.eclipse.jgit.transport.URIish org.jboss.marshalling.TraceInformation$FieldInfo org.jboss.marshalling.TraceInformation$ObjectInfo +# TODO remove when https://github.com/jenkinsci/xtrigger-lib/pull/9 is widely adopted in fstrigger-plugin, urltrigger-plugin, etc. +org.jenkinsci.lib.xtrigger.XTriggerCause +org.jenkinsci.lib.xtrigger.XTriggerCauseAction + org.jvnet.hudson.MemoryUsage org.jvnet.localizer.Localizable org.jvnet.localizer.ResourceBundleHolder -- GitLab From 88e756de6fe6c3333658e6d4be6aad2323a63e09 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 10 Jan 2018 12:30:02 +0100 Subject: [PATCH 0242/1380] [JENKINS-47736] - Use the released version of Remoting 3.16 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 81f543bb3a..94602108c4 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ THE SOFTWARE. 3.0.0 - 3.16-20180109.195120-6 + 3.16 2.60 -- GitLab From 780050dfc0a1a9033d19b04e980e6f831f465c50 Mon Sep 17 00:00:00 2001 From: Alexander Akbashev Date: Thu, 11 Jan 2018 13:43:02 +0100 Subject: [PATCH 0243/1380] Do not search for last build if concurrent build is allowed The switch of order in check allows to reduce memory consumption in high loaded systems. --- core/src/main/java/hudson/model/AbstractProject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java index ffee6413d8..6f5965fe54 100644 --- a/core/src/main/java/hudson/model/AbstractProject.java +++ b/core/src/main/java/hudson/model/AbstractProject.java @@ -1068,7 +1068,7 @@ public abstract class AbstractProject

    ,R extends A @Override public CauseOfBlockage getCauseOfBlockage() { // Block builds until they are done with post-production - if (isLogUpdated() && !isConcurrentBuild()) { + if (!isConcurrentBuild() && isLogUpdated()) { final R lastBuild = getLastBuild(); if (lastBuild != null) { return new BlockedBecauseOfBuildInProgress(lastBuild); -- GitLab From 7ba2ea401661e235947df51d722b1f564f314f65 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 11 Jan 2018 10:15:10 -0500 Subject: [PATCH 0244/1380] https://github.com/jenkinsci/monitoring-plugin/pull/6 --- .../jenkins/security/whitelisted-classes.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 30062e29d7..799146109c 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -76,6 +76,19 @@ java.util.regex.Pattern # TODO remove when https://github.com/jenkinsci/job-dsl-plugin/pull/1092 is widely adopted javaposse.jobdsl.dsl.GeneratedJob +# TODO remove when https://github.com/jenkinsci/monitoring-plugin/pull/6 is widely adopted +net.bull.javamelody.internal.model.CacheInformations +net.bull.javamelody.internal.model.HeapHistogram +net.bull.javamelody.internal.model.HeapHistogram$ClassInfo +net.bull.javamelody.internal.model.JavaInformations +net.bull.javamelody.internal.model.JobInformations +net.bull.javamelody.internal.model.MBeanNode +net.bull.javamelody.internal.model.MBeanNode$MBeanAttribute +net.bull.javamelody.internal.model.MemoryInformations +net.bull.javamelody.internal.model.ProcessInformations +net.bull.javamelody.internal.model.ThreadInformations +net.bull.javamelody.internal.model.TomcatInformations + org.acegisecurity.userdetails.User org.apache.commons.fileupload.disk.DiskFileItem org.apache.commons.fileupload.util.FileItemHeadersImpl -- GitLab From 04cbd5d38cadae5942d9f7716312090888a6745f Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 11 Jan 2018 10:50:31 -0500 Subject: [PATCH 0245/1380] Add the remaining primitive wrappers. PerformancePublisherTest.testRelativeThresholdFailedPositive showed a rejection of Character. In fact the field was of type char, but TreeMarshaller.convertAnother boxes primitives; the field type has been discarded by this point. --- .../main/resources/jenkins/security/whitelisted-classes.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 799146109c..e719dfffe6 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -17,6 +17,8 @@ hudson.maven.MavenInformation java.io.File java.lang.Boolean +java.lang.Byte +java.lang.Character java.lang.Class java.lang.Double java.lang.Enum @@ -25,6 +27,7 @@ java.lang.Integer java.lang.Long java.lang.Number java.lang.Object +java.lang.Short java.lang.StackTraceElement java.lang.String java.lang.reflect.Proxy -- GitLab From f0138b568daa8786729c2a22bf63fe4ac5392e5d Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 11 Jan 2018 11:47:40 -0500 Subject: [PATCH 0246/1380] Including permalink in BlacklistedTypesConverter messaging. --- core/src/main/java/hudson/util/XStream2.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 9f45815def..633bc66d9a 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -526,12 +526,12 @@ public class XStream2 extends XStream { private static class BlacklistedTypesConverter implements Converter { @Override public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { - throw new UnsupportedOperationException("Refusing to marshal " + source.getClass().getName() + " for security reasons"); + throw new UnsupportedOperationException("Refusing to marshal " + source.getClass().getName() + " for security reasons; see https://jenkins.io/redirect/class-filter/"); } @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { - throw new ConversionException("Refusing to unmarshal " + reader.getNodeName() + " for security reasons"); + throw new ConversionException("Refusing to unmarshal " + reader.getNodeName() + " for security reasons; see https://jenkins.io/redirect/class-filter/"); } @Override -- GitLab From 964e22487af8f652fd03b1f140024d1746c4113b Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 11 Jan 2018 12:12:27 -0500 Subject: [PATCH 0247/1380] =?UTF-8?q?Reminder=20about=20JEP-200=20impacts?= =?UTF-8?q?=20on=20slave=20=E2=86=92=20master=20callables.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/src/main/java/jenkins/SlaveToMasterFileCallable.java | 1 + core/src/main/java/jenkins/security/SlaveToMasterCallable.java | 1 + 2 files changed, 2 insertions(+) diff --git a/core/src/main/java/jenkins/SlaveToMasterFileCallable.java b/core/src/main/java/jenkins/SlaveToMasterFileCallable.java index 10071cf1f8..7e2d609089 100644 --- a/core/src/main/java/jenkins/SlaveToMasterFileCallable.java +++ b/core/src/main/java/jenkins/SlaveToMasterFileCallable.java @@ -8,6 +8,7 @@ import org.jenkinsci.remoting.RoleChecker; * {@link FileCallable}s that can be executed on the master, sent by the agent. * * @since 1.587 / 1.580.1 + * @param the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist */ public abstract class SlaveToMasterFileCallable implements FileCallable { @Override diff --git a/core/src/main/java/jenkins/security/SlaveToMasterCallable.java b/core/src/main/java/jenkins/security/SlaveToMasterCallable.java index 2f256f2e9d..c1eaf84d1d 100644 --- a/core/src/main/java/jenkins/security/SlaveToMasterCallable.java +++ b/core/src/main/java/jenkins/security/SlaveToMasterCallable.java @@ -9,6 +9,7 @@ import org.jenkinsci.remoting.RoleChecker; * * @author Kohsuke Kawaguchi * @since 1.587 / 1.580.1 + * @param the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist */ public abstract class SlaveToMasterCallable implements Callable { @Override -- GitLab From b844f0fbfdd2a025be057ef388ddaa054939fadb Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 11 Jan 2018 12:57:38 -0500 Subject: [PATCH 0248/1380] KubernetesTest.upgradeFrom_0_12 --- core/src/main/resources/jenkins/security/whitelisted-classes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index e719dfffe6..0f8689347d 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -10,6 +10,7 @@ com.google.common.collect.RegularImmutableList com.google.common.collect.RegularImmutableSet com.google.common.collect.RegularImmutableSortedSet com.google.common.collect.SingletonImmutableList +com.google.common.collect.SingletonImmutableMap com.google.common.collect.SingletonImmutableSet # TODO remove when maven-plugin 3.1 is widely adopted -- GitLab From 02d0531add35154d051de71398aefb58b9295a3f Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Thu, 11 Jan 2018 21:23:12 +0100 Subject: [PATCH 0249/1380] [JENKINS-48773] - Limit which section headers are added to the breadcrumb config outline (#3213) * [JENKINS-48773] Don't include section elements in repeatables * Only include visible section headers * Fix tests by checking for presence of method first --- core/src/main/resources/lib/form/section_.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/resources/lib/form/section_.js b/core/src/main/resources/lib/form/section_.js index 5d681463a8..b6f0732dc8 100644 --- a/core/src/main/resources/lib/form/section_.js +++ b/core/src/main/resources/lib/form/section_.js @@ -38,7 +38,7 @@ var section = (function (){ root = $(root||document.body); /** - * Recursively visit elements and find all section headers. + * Recursively visit elements and find all visible section headers that are not inside f:repeatable elements. * * @param {HTMLElement} dom * Parent element @@ -46,13 +46,19 @@ var section = (function (){ * Function that returns the array to which discovered section headers and child elements are added. */ function visitor(dom,parent) { + function isVisible(elem) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects && elem.getClientRects().length ); + } + for (var e=dom.firstChild; e!=null; e=e.nextSibling) { if (e.nodeType==1) { - if (e.className=="section-header") { + if (e.className=="section-header" && isVisible(e)) { var child = new SectionNode(e); parent.children.push(child); + // The next line seems to be unnecessary, as there are no children inside the section header itself. + // So this code will always returns a flat list of section headers. visitor(e,child); - } else { + } else if (!e.classList.contains("repeated-container")) { visitor(e,parent); } } -- GitLab From 22c9bd0bf4f159c1acbb8d0a257daa155c3216f9 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 11 Jan 2018 15:45:07 -0500 Subject: [PATCH 0250/1380] =?UTF-8?q?Javadoc=20warnings=20were=20misleadin?= =?UTF-8?q?g.=20For=20master=20=E2=86=92=20slave,=20the=20return=20type=20?= =?UTF-8?q?matters;=20for=20slave=20=E2=86=92=20master,=20the=20callable?= =?UTF-8?q?=20fields=20matter.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/src/main/java/jenkins/MasterToSlaveFileCallable.java | 1 + core/src/main/java/jenkins/SlaveToMasterFileCallable.java | 3 +-- core/src/main/java/jenkins/security/MasterToSlaveCallable.java | 1 + core/src/main/java/jenkins/security/SlaveToMasterCallable.java | 3 +-- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/jenkins/MasterToSlaveFileCallable.java b/core/src/main/java/jenkins/MasterToSlaveFileCallable.java index 18a39dedb7..9b5c88dcb6 100644 --- a/core/src/main/java/jenkins/MasterToSlaveFileCallable.java +++ b/core/src/main/java/jenkins/MasterToSlaveFileCallable.java @@ -8,6 +8,7 @@ import org.jenkinsci.remoting.RoleChecker; * {@link FileCallable}s that are meant to be only used on the master. * * @since 1.587 / 1.580.1 + * @param the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist */ public abstract class MasterToSlaveFileCallable implements FileCallable { @Override diff --git a/core/src/main/java/jenkins/SlaveToMasterFileCallable.java b/core/src/main/java/jenkins/SlaveToMasterFileCallable.java index 7e2d609089..236bd738f2 100644 --- a/core/src/main/java/jenkins/SlaveToMasterFileCallable.java +++ b/core/src/main/java/jenkins/SlaveToMasterFileCallable.java @@ -6,9 +6,8 @@ import org.jenkinsci.remoting.RoleChecker; /** * {@link FileCallable}s that can be executed on the master, sent by the agent. - * + * Note that any serializable fields must either be defined in your plugin or included in the stock JEP-200 whitelist. * @since 1.587 / 1.580.1 - * @param the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist */ public abstract class SlaveToMasterFileCallable implements FileCallable { @Override diff --git a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java index 60ac25bfbc..39be186fb0 100644 --- a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java +++ b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java @@ -13,6 +13,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; * * @author Kohsuke Kawaguchi * @since 1.587 / 1.580.1 + * @param the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist */ public abstract class MasterToSlaveCallable implements Callable { diff --git a/core/src/main/java/jenkins/security/SlaveToMasterCallable.java b/core/src/main/java/jenkins/security/SlaveToMasterCallable.java index c1eaf84d1d..5da1172246 100644 --- a/core/src/main/java/jenkins/security/SlaveToMasterCallable.java +++ b/core/src/main/java/jenkins/security/SlaveToMasterCallable.java @@ -6,10 +6,9 @@ import org.jenkinsci.remoting.RoleChecker; /** * Convenient {@link Callable} that are meant to run on the master (sent by agent/CLI/etc). - * + * Note that any serializable fields must either be defined in your plugin or included in the stock JEP-200 whitelist. * @author Kohsuke Kawaguchi * @since 1.587 / 1.580.1 - * @param the return type; note that this must either be defined in your plugin or included in the stock JEP-200 whitelist */ public abstract class SlaveToMasterCallable implements Callable { @Override -- GitLab From 8b3e897cec6bae21f56ca6c3895c072b21cd6d18 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 11 Jan 2018 17:52:28 -0500 Subject: [PATCH 0251/1380] https://github.com/jenkinsci/ruby-runtime-plugin/pull/5 --- core/src/main/java/hudson/util/XStream2.java | 10 +++++++++- .../main/java/jenkins/security/CustomClassFilter.java | 2 +- .../jenkins/security/whitelisted-classes.txt | 11 +++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 633bc66d9a..41b462df29 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -75,6 +75,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; @@ -534,13 +535,20 @@ public class XStream2 extends XStream { throw new ConversionException("Refusing to unmarshal " + reader.getNodeName() + " for security reasons; see https://jenkins.io/redirect/class-filter/"); } + /** TODO see comment in {@code whitelisted-classes.txt} */ + private static final Pattern JRUBY_PROXY = Pattern.compile("org[.]jruby[.]proxy[.].+[$]Proxy\\d+"); + @Override public boolean canConvert(Class type) { if (type == null) { return false; } + String name = type.getName(); + if (JRUBY_PROXY.matcher(name).matches()) { + return false; + } // claim we can convert all the scary stuff so we can throw exceptions when attempting to do so - return ClassFilter.DEFAULT.isBlacklisted(type.getName()) || ClassFilter.DEFAULT.isBlacklisted(type); + return ClassFilter.DEFAULT.isBlacklisted(name) || ClassFilter.DEFAULT.isBlacklisted(type); } } } diff --git a/core/src/main/java/jenkins/security/CustomClassFilter.java b/core/src/main/java/jenkins/security/CustomClassFilter.java index 960dee5835..49781a242e 100644 --- a/core/src/main/java/jenkins/security/CustomClassFilter.java +++ b/core/src/main/java/jenkins/security/CustomClassFilter.java @@ -51,8 +51,8 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; * Allows extensions to adjust the behavior of {@link ClassFilter#DEFAULT}. * Custom filters can be called frequently, and return values are uncached, so implementations should be fast. * @see ClassFilterImpl + * @since FIXME */ -@Restricted(NoExternalUse.class) // until a use case is identified for a _dynamic_ extension from some plugin public interface CustomClassFilter extends ExtensionPoint { /** diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 0f8689347d..73f4fc862d 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -114,6 +114,17 @@ org.jboss.marshalling.TraceInformation$ObjectInfo org.jenkinsci.lib.xtrigger.XTriggerCause org.jenkinsci.lib.xtrigger.XTriggerCauseAction +# TODO remove (also XStream2.BlacklistedTypesConverter.JRUBY_PROXY) when https://github.com/jenkinsci/ruby-runtime-plugin/pull/5 is widely adopted +org.jruby.RubyArray +org.jruby.RubyBignum +org.jruby.RubyBoolean +org.jruby.RubyFixnum +org.jruby.RubyHash +org.jruby.RubyObject +org.jruby.RubyString +org.jruby.RubySymbol +org.jruby.java.proxies.ConcreteJavaProxy + org.jvnet.hudson.MemoryUsage org.jvnet.localizer.Localizable org.jvnet.localizer.ResourceBundleHolder -- GitLab From 293897f85f60a5359afb40707dd4e039c5f0a578 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 12 Jan 2018 09:15:32 -0500 Subject: [PATCH 0252/1380] https://github.com/jenkinsci/dependency-check-plugin/pull/20 --- .../main/resources/jenkins/security/whitelisted-classes.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 73f4fc862d..7870635e7a 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -128,5 +128,11 @@ org.jruby.java.proxies.ConcreteJavaProxy org.jvnet.hudson.MemoryUsage org.jvnet.localizer.Localizable org.jvnet.localizer.ResourceBundleHolder + +# TODO remove when https://github.com/jenkinsci/dependency-check-plugin/pull/20 is widely adopted +org.owasp.dependencycheck.dependency.Reference +org.owasp.dependencycheck.dependency.Vulnerability +org.owasp.dependencycheck.dependency.VulnerableSoftware + sun.security.rsa.RSAPublicKeyImpl sun.security.x509.X509Key -- GitLab From 1c219ba095a13fd5b33a7f8d45b9716cba123c50 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 12 Jan 2018 11:02:15 -0500 Subject: [PATCH 0253/1380] Adding general kill switches for the whitelist and blacklist which merely log violations. --- .../java/jenkins/security/ClassFilterImpl.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/src/main/java/jenkins/security/ClassFilterImpl.java b/core/src/main/java/jenkins/security/ClassFilterImpl.java index 5f8ca82111..6c2f79b77d 100644 --- a/core/src/main/java/jenkins/security/ClassFilterImpl.java +++ b/core/src/main/java/jenkins/security/ClassFilterImpl.java @@ -50,6 +50,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import jenkins.model.Jenkins; +import jenkins.util.SystemProperties; import org.apache.commons.io.IOUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -67,6 +68,9 @@ public class ClassFilterImpl extends ClassFilter { private static final Logger LOGGER = Logger.getLogger(ClassFilterImpl.class.getName()); + private static /* not final */ boolean SUPPRESS_WHITELIST = SystemProperties.getBoolean("jenkins.security.ClassFilterImpl.SUPPRESS_WHITELIST"); + private static /* not final */ boolean SUPPRESS_ALL = SystemProperties.getBoolean("jenkins.security.ClassFilterImpl.SUPPRESS_ALL"); + /** * Register this implementation as the default in the system. */ @@ -76,6 +80,11 @@ public class ClassFilterImpl extends ClassFilter { return; } ClassFilter.setDefault(new ClassFilterImpl()); + if (SUPPRESS_ALL) { + LOGGER.warning("All class filtering suppressed. Your Jenkins installation is at risk from known attacks. See https://jenkins.io/redirect/class-filter/"); + } else if (SUPPRESS_WHITELIST) { + LOGGER.warning("JEP-200 class filtering by whitelist suppressed. Your Jenkins installation may be at risk. See https://jenkins.io/redirect/class-filter/"); + } } /** @@ -155,6 +164,10 @@ public class ClassFilterImpl extends ClassFilter { LOGGER.log(Level.FINE, "tolerating {0} by whitelist", name); return false; } + if (SUPPRESS_WHITELIST || SUPPRESS_ALL) { + LOGGER.log(Level.WARNING, "{0} in {1} might be dangerous, so would normally be rejected; see https://jenkins.io/redirect/class-filter/", new Object[] {name, location != null ? location : "JRE"}); + return false; + } LOGGER.log(Level.WARNING, "{0} in {1} might be dangerous, so rejecting; see https://jenkins.io/redirect/class-filter/", new Object[] {name, location != null ? location : "JRE"}); return true; }); @@ -249,6 +262,10 @@ public class ClassFilterImpl extends ClassFilter { } // could apply a cache if the pattern search turns out to be slow if (ClassFilter.STANDARD.isBlacklisted(name)) { + if (SUPPRESS_ALL) { + LOGGER.log(Level.WARNING, "would normally reject {0} according to standard blacklist; see https://jenkins.io/redirect/class-filter/", name); + return false; + } LOGGER.log(Level.WARNING, "rejecting {0} according to standard blacklist; see https://jenkins.io/redirect/class-filter/", name); return true; } else { -- GitLab From f4be029342e152e2c8ce2c353e32e7ab76e92190 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 12 Jan 2018 11:03:03 -0500 Subject: [PATCH 0254/1380] Some additional whitelist entries for RubyBoolean. --- .../src/main/resources/jenkins/security/whitelisted-classes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 7870635e7a..aec140e221 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -118,6 +118,8 @@ org.jenkinsci.lib.xtrigger.XTriggerCauseAction org.jruby.RubyArray org.jruby.RubyBignum org.jruby.RubyBoolean +org.jruby.RubyBoolean$False +org.jruby.RubyBoolean$True org.jruby.RubyFixnum org.jruby.RubyHash org.jruby.RubyObject -- GitLab From 5f44ee2d85027e2ea2eb3b9174e12cd270537f05 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 12 Jan 2018 11:49:54 -0500 Subject: [PATCH 0255/1380] More ruby-runtime. --- .../main/resources/jenkins/security/whitelisted-classes.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index aec140e221..c53dec5984 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -110,6 +110,9 @@ org.eclipse.jgit.transport.URIish org.jboss.marshalling.TraceInformation$FieldInfo org.jboss.marshalling.TraceInformation$ObjectInfo +# TODO see main ruby-runtime section below +org.jenkinsci.jruby.JRubyMapper$DynamicProxy + # TODO remove when https://github.com/jenkinsci/xtrigger-lib/pull/9 is widely adopted in fstrigger-plugin, urltrigger-plugin, etc. org.jenkinsci.lib.xtrigger.XTriggerCause org.jenkinsci.lib.xtrigger.XTriggerCauseAction @@ -126,6 +129,7 @@ org.jruby.RubyObject org.jruby.RubyString org.jruby.RubySymbol org.jruby.java.proxies.ConcreteJavaProxy +org.jruby.runtime.builtin.IRubyObject org.jvnet.hudson.MemoryUsage org.jvnet.localizer.Localizable -- GitLab From e3bf773cea7c517241ee29b2e50950e9d674fcf1 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 12 Jan 2018 16:22:00 -0500 Subject: [PATCH 0256/1380] https://github.com/jenkinsci/kubernetes-pipeline-plugin/pull/66 --- .../main/resources/jenkins/security/whitelisted-classes.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index c53dec5984..4d48f7681e 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -16,6 +16,11 @@ com.google.common.collect.SingletonImmutableSet # TODO remove when maven-plugin 3.1 is widely adopted hudson.maven.MavenInformation +# TODO remove when https://github.com/jenkinsci/kubernetes-pipeline-plugin/pull/66 is widely adopted +io.fabric8.docker.api.model.ImageInspect +io.fabric8.docker.api.model.Config +io.fabric8.docker.api.model.GraphDriverData + java.io.File java.lang.Boolean java.lang.Byte -- GitLab From 47be7c3db3210674d3feb7fb3a497d4342cd1b92 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 12 Jan 2018 17:37:02 -0500 Subject: [PATCH 0257/1380] Fixed order of entries. --- .../src/main/resources/jenkins/security/whitelisted-classes.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 4d48f7681e..275ff3eea1 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -17,9 +17,9 @@ com.google.common.collect.SingletonImmutableSet hudson.maven.MavenInformation # TODO remove when https://github.com/jenkinsci/kubernetes-pipeline-plugin/pull/66 is widely adopted -io.fabric8.docker.api.model.ImageInspect io.fabric8.docker.api.model.Config io.fabric8.docker.api.model.GraphDriverData +io.fabric8.docker.api.model.ImageInspect java.io.File java.lang.Boolean -- GitLab From ee862b8cc3c8da236983e29dfc25535e4203f08a Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Sat, 13 Jan 2018 23:38:13 +0100 Subject: [PATCH 0258/1380] [JENKINS-48992] - Whitelist StringBuilder and StringBuffer for serialization (#3230) * [JENKINS-48992] - Whitelist StringBuilder and StringBuffer for serialization. * [JENKINS-48922] - Reorder the whitelist entries --- .../src/main/resources/jenkins/security/whitelisted-classes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 275ff3eea1..064dd47660 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -36,6 +36,8 @@ java.lang.Object java.lang.Short java.lang.StackTraceElement java.lang.String +java.lang.StringBuffer +java.lang.StringBuilder java.lang.reflect.Proxy java.net.URI java.net.URL -- GitLab From 1feb23e55ca807a664cf89986241155f24038aa5 Mon Sep 17 00:00:00 2001 From: Alessandro Menti Date: Sun, 14 Jan 2018 12:19:02 +0100 Subject: [PATCH 0259/1380] Update the Italian localization --- .../resources/hudson/Messages_it.properties | 3 ++ .../LogRecorderManager/levels_it.properties | 1 + .../model/AbstractBuild/tasks_it.properties | 1 - .../hudson/model/Messages_it.properties | 8 +++-- .../config_it.properties | 1 + .../hudson/model/View/builds_it.properties | 1 + .../model/View/newJobButtonBar_it.properties | 23 +++++++++++++ .../BecauseLabelIsBusy/summary_it.properties | 1 - .../summary_it.properties | 2 -- .../BecauseNodeIsBusy/summary_it.properties | 1 - .../summary_it.properties | 1 - .../hudson/security/Messages_it.properties | 2 ++ .../csrf/CrumbFilter/retry_it.properties | 32 +++++++++++++++++++ .../hudson/slaves/Messages_it.properties | 3 -- .../hudson/tasks/Messages_it.properties | 1 + .../SCMTrigger/SCMAction/index_it.properties | 1 + .../Jenkins/fingerprintCheck_it.properties | 1 + .../jenkins/model/Messages_it.properties | 5 +++ .../message_it.properties | 28 ++++++++++++++++ .../security/csrf/Messages_it.properties | 23 +++++++++++++ .../resources/lib/form/helpLink_it.properties | 1 - .../resources/lib/layout/layout_it.properties | 1 + .../resources/lib/layout/pane_it.properties | 1 - 23 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 core/src/main/resources/hudson/model/View/newJobButtonBar_it.properties create mode 100644 core/src/main/resources/hudson/security/csrf/CrumbFilter/retry_it.properties create mode 100644 core/src/main/resources/jenkins/security/csrf/CSRFAdministrativeMonitor/message_it.properties create mode 100644 core/src/main/resources/jenkins/security/csrf/Messages_it.properties diff --git a/core/src/main/resources/hudson/Messages_it.properties b/core/src/main/resources/hudson/Messages_it.properties index 29410318df..48deedea69 100644 --- a/core/src/main/resources/hudson/Messages_it.properties +++ b/core/src/main/resources/hudson/Messages_it.properties @@ -22,6 +22,9 @@ Util.hour ={0} h Util.day ={0} g Util.month ={0} m Util.year ={0} a +Util.pastTime={0} +Util.millisecond={0} ms +Util.minute={0} min FilePath.TildaDoesntWork="~" supportato solo ed esclusivamente in una shell Unix. diff --git a/core/src/main/resources/hudson/logging/LogRecorderManager/levels_it.properties b/core/src/main/resources/hudson/logging/LogRecorderManager/levels_it.properties index 229cd0dff3..8f15d87798 100644 --- a/core/src/main/resources/hudson/logging/LogRecorderManager/levels_it.properties +++ b/core/src/main/resources/hudson/logging/LogRecorderManager/levels_it.properties @@ -6,3 +6,4 @@ Logger\ Configuration=Configurazione registro Name=Nome Submit=Invia defaultLoggerMsg=Il registro senza nome quello predefinito. Questo livello sar ereditato da tutti i registri senza un livello configurato. +url=https://jenkins.io/redirect/log-levels diff --git a/core/src/main/resources/hudson/model/AbstractBuild/tasks_it.properties b/core/src/main/resources/hudson/model/AbstractBuild/tasks_it.properties index 0cf3894d2a..80f6768b96 100644 --- a/core/src/main/resources/hudson/model/AbstractBuild/tasks_it.properties +++ b/core/src/main/resources/hudson/model/AbstractBuild/tasks_it.properties @@ -24,4 +24,3 @@ Back\ to\ Project=Torna al progetto Changes=Modifiche Edit\ Build\ Information=Modifica informazioni di compilazione Status=Stato -View\ Build\ Information=Visualizza informazioni di compilazione diff --git a/core/src/main/resources/hudson/model/Messages_it.properties b/core/src/main/resources/hudson/model/Messages_it.properties index 2981daedac..939e4558b8 100644 --- a/core/src/main/resources/hudson/model/Messages_it.properties +++ b/core/src/main/resources/hudson/model/Messages_it.properties @@ -130,7 +130,10 @@ FreeStyleProject.Description=\ utilizzato anche per attivit diverse dalla compilazione di software. Hudson.BadPortNumber=Numero di porta {0} non valido +Hudson.Computer.Caption=Master +Hudson.Computer.DisplayName=master Hudson.ControlCodeNotAllowed=Non consentito alcun codice di controllo: {0} +Hudson.DisplayName=Jenkins Hudson.JobAlreadyExists=Esiste gi un processo denominato "{0}" Hudson.NoJavaInPath=java non nel proprio PATH. Forse necessario configurare i JDK? Hudson.NoName=Nessun nome specificato @@ -240,7 +243,6 @@ Run.Summary.Unknown=Sconosciuto Slave.InvalidConfig.Executors=Configurazione agente {0} non valida. Numero di esecutori non valido. Slave.InvalidConfig.NoName=Configurazione agente non valida. Il nome vuoto -Slave.Launching={0} sta avviando l''agente Slave.Network.Mounted.File.System.Warning=Utilizzare un filesystem di rete \ montato come radice del filesystem? Si noti che non necessario che questa \ directory sia visibile per il master. @@ -251,7 +253,6 @@ Slave.Remote.Relative.Path.Warning=Utilizzare un percorso relativo per la \ in grado di assicurare che il lanciatore selezionato fornisca una directory \ di lavoro consistente. L''utilizzo di un percorso assoluto altamente \ raccomandato. -Slave.UnableToLaunch=Impossibile lanciare l''agente per {0}{1} Slave.UnixSlave=Questo un agente Unix Slave.WindowsSlave=Questo un agente Windows @@ -291,13 +292,16 @@ UpdateCenter.PluginCategory.buildwrapper=Wrapper di compilazione UpdateCenter.PluginCategory.cli=Interfaccia a riga di comando UpdateCenter.PluginCategory.cloud=Provider cloud UpdateCenter.PluginCategory.cluster=Gestione cluster e compilazione distribuita +UpdateCenter.PluginCategory.database=Database UpdateCenter.PluginCategory.deployment=Sviluppo +UpdateCenter.PluginCategory.devops=DevOps UpdateCenter.PluginCategory.dotnet=Sviluppo .NET UpdateCenter.PluginCategory.external=Integrazioni siti/strumenti esterni UpdateCenter.PluginCategory.groovy-related=Strumenti legati a Groovy UpdateCenter.PluginCategory.ios=Sviluppo iOS UpdateCenter.PluginCategory.library=Plugin libreria (per l''utilizzo da parte di altri plugin) UpdateCenter.PluginCategory.listview-column=Colonne visualizzazione elenco +UpdateCenter.PluginCategory.maven=Maven UpdateCenter.PluginCategory.misc=Vari UpdateCenter.PluginCategory.notifier=Notificatori compilazione UpdateCenter.PluginCategory.page-decorator=Decoratori pagina diff --git a/core/src/main/resources/hudson/model/StringParameterDefinition/config_it.properties b/core/src/main/resources/hudson/model/StringParameterDefinition/config_it.properties index 68a8f2153e..7266b68ea6 100644 --- a/core/src/main/resources/hudson/model/StringParameterDefinition/config_it.properties +++ b/core/src/main/resources/hudson/model/StringParameterDefinition/config_it.properties @@ -3,3 +3,4 @@ Default\ Value=Valore predefinito Description=Descrizione Name=Nome +Trim\ the\ string=Elimina spazi vuoti iniziali e finali dalla stringa diff --git a/core/src/main/resources/hudson/model/View/builds_it.properties b/core/src/main/resources/hudson/model/View/builds_it.properties index cbb3bb77ce..9f3fa7ab8c 100644 --- a/core/src/main/resources/hudson/model/View/builds_it.properties +++ b/core/src/main/resources/hudson/model/View/builds_it.properties @@ -22,3 +22,4 @@ Export\ as\ plain\ XML=Esporta come XML buildHistory=Cronologia compilazioni di {0} +Recurse\ in\ subfolders=Esegui ricorsivamente nelle sottodirectory diff --git a/core/src/main/resources/hudson/model/View/newJobButtonBar_it.properties b/core/src/main/resources/hudson/model/View/newJobButtonBar_it.properties new file mode 100644 index 0000000000..3ba8e395c8 --- /dev/null +++ b/core/src/main/resources/hudson/model/View/newJobButtonBar_it.properties @@ -0,0 +1,23 @@ +# The MIT License +# +# Copyright (c) 2018- Alessandro Menti +# +# 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. + +OK=OK diff --git a/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseLabelIsBusy/summary_it.properties b/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseLabelIsBusy/summary_it.properties index fc95617eb4..f592f8ff79 100644 --- a/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseLabelIsBusy/summary_it.properties +++ b/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseLabelIsBusy/summary_it.properties @@ -21,4 +21,3 @@ # THE SOFTWARE. # note for translators: this message is referenced from st:structuredMessageFormat -description=In attesa del prossimo esecutore disponibile su {0} diff --git a/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseLabelIsOffline/summary_it.properties b/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseLabelIsOffline/summary_it.properties index 2e5b4f8a96..d20c52c378 100644 --- a/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseLabelIsOffline/summary_it.properties +++ b/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseLabelIsOffline/summary_it.properties @@ -21,5 +21,3 @@ # THE SOFTWARE. # note for translators: this message is referenced from st:structuredMessageFormat -description=Tutti i nodi dell''etichetta "{0}" non sono in linea -description_no_nodes=Non esistono nodi con etichetta "{0}" diff --git a/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseNodeIsBusy/summary_it.properties b/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseNodeIsBusy/summary_it.properties index 16b2d46037..d20c52c378 100644 --- a/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseNodeIsBusy/summary_it.properties +++ b/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseNodeIsBusy/summary_it.properties @@ -21,4 +21,3 @@ # THE SOFTWARE. # note for translators: this message is referenced from st:structuredMessageFormat -description=In attesa del prossimo esecutore disponibile su {0} diff --git a/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseNodeIsOffline/summary_it.properties b/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseNodeIsOffline/summary_it.properties index 5cd1c3d48a..d20c52c378 100644 --- a/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseNodeIsOffline/summary_it.properties +++ b/core/src/main/resources/hudson/model/queue/CauseOfBlockage/BecauseNodeIsOffline/summary_it.properties @@ -21,4 +21,3 @@ # THE SOFTWARE. # note for translators: this message is referenced from st:structuredMessageFormat -description={0} non in linea diff --git a/core/src/main/resources/hudson/security/Messages_it.properties b/core/src/main/resources/hudson/security/Messages_it.properties index 6353b56db4..e6cae94b0b 100644 --- a/core/src/main/resources/hudson/security/Messages_it.properties +++ b/core/src/main/resources/hudson/security/Messages_it.properties @@ -57,3 +57,5 @@ LDAPSecurityRealm.UnableToConnect=Impossibile connettersi a {0}: {1} AuthorizationStrategy.DisplayName=Chiunque pu fare qualsiasi cosa GlobalSecurityConfiguration.Description=Metti in sicurezza Jenkins; definisci chi autorizzato ad accedere/a utilizzare il sistema. PAMSecurityRealm.CurrentUser=Utente corrente +HudsonPrivateSecurityRealm.Details.DisplayName=Password +LDAPSecurityRealm.DisplayName=LDAP diff --git a/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry_it.properties b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry_it.properties new file mode 100644 index 0000000000..e7d06bfca2 --- /dev/null +++ b/core/src/main/resources/hudson/security/csrf/CrumbFilter/retry_it.properties @@ -0,0 +1,32 @@ +# The MIT License +# +# Copyright (c) 2018- Alessandro Menti +# +# 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. + +blurb=L''URL a cui si sta tentando di accedere richiede che le richieste siano \ + inviate con il metodo POST (come per l''invio di un modulo). \ + Il pulsante sottostante consente di riprovare ad accedere a quest''URL \ + utilizzando il metodo POST. \ + URL a cui si sta accedendo: +This\ URL\ requires\ POST=Quest''URL richiede il metodo POST +warning=Se si stati indirizzati qui da un''origine non attendibile, procedere \ + con cautela. +Retry\ using\ POST=Riprova utilizzando il metodo POST +Method\ Not\ Allowed=Metodo non consentito diff --git a/core/src/main/resources/hudson/slaves/Messages_it.properties b/core/src/main/resources/hudson/slaves/Messages_it.properties index 954cd59030..6aeb7237f7 100644 --- a/core/src/main/resources/hudson/slaves/Messages_it.properties +++ b/core/src/main/resources/hudson/slaves/Messages_it.properties @@ -28,17 +28,14 @@ SimpleScheduledRetentionStrategy.FinishedUpTime=Il tempo di attivit RetentionStrategy.Always.displayName=Mantieni quest''agente in linea il pi possibile ComputerLauncher.UnknownJavaVersion=Impossibile determinare la versione Java di {0} ComputerLauncher.abortedLaunch=Avvio del processo agente interrotto. -CommandLauncher.cannot_be_configured_by_non_administrato=non pu essere configurato da utenti non amministratori EnvironmentVariablesNodeProperty.displayName=Variabili d''ambiente NodeDescriptor.CheckName.Mandatory=Il nome obbligatorio. ComputerLauncher.NoJavaFound= stata rilevata la versione di Java {0}, ma richiesta la versione 1.6 o successiva. JNLPLauncher.displayName=Avvia agente tramite Java Web Start Cloud.ProvisionPermission.Description=Esegui il provisioning di nuovi nodi -CommandLauncher.NoLaunchCommand=Nessun comando di avvio specificato DumbSlave.displayName=Agente permanente RetentionStrategy.Demand.displayName=Poni quest''agente in linea quando c'' richiesta e non in linea se non attivo ComputerLauncher.JavaVersionResult={0} -version ha restituito {1}. SimpleScheduledRetentionStrategy.displayName=Poni quest''agente in linea secondo una pianificazione RetentionStrategy.Demand.OfflineIdle=Non in linea perch il computer non era attivo; sar riavviato quando richiesto. ComputerLauncher.unexpectedError=Errore non previsto durante l''avvio dell''agente. Questo probabilmente un bug di Jenkins -CommandLauncher.displayName=Avvia l''agente eseguendo un comando sul master diff --git a/core/src/main/resources/hudson/tasks/Messages_it.properties b/core/src/main/resources/hudson/tasks/Messages_it.properties index e126c35284..df27728217 100644 --- a/core/src/main/resources/hudson/tasks/Messages_it.properties +++ b/core/src/main/resources/hudson/tasks/Messages_it.properties @@ -72,3 +72,4 @@ Fingerprinter.Aborted=Interrotto BatchFile.invalid_exit_code_range=Valore ERRORLEVEL non valido: {0}. Controllare la sezione Guida BuildTrigger.InQueue={0} gi in coda BuildTrigger.you_have_no_permission_to_build_=Non si dispone dei permessi per compilare {0} +JavadocArchiver.DisplayName.Javadoc=Javadoc diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/SCMAction/index_it.properties b/core/src/main/resources/hudson/triggers/SCMTrigger/SCMAction/index_it.properties index 168050d6bd..7fec52cce0 100644 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/SCMAction/index_it.properties +++ b/core/src/main/resources/hudson/triggers/SCMTrigger/SCMAction/index_it.properties @@ -21,3 +21,4 @@ # THE SOFTWARE. Polling\ has\ not\ run\ yet.=Il polling non stato ancora eseguito. +title={0} diff --git a/core/src/main/resources/jenkins/model/Jenkins/fingerprintCheck_it.properties b/core/src/main/resources/jenkins/model/Jenkins/fingerprintCheck_it.properties index edcc1ed43a..9cb83124bd 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/fingerprintCheck_it.properties +++ b/core/src/main/resources/jenkins/model/Jenkins/fingerprintCheck_it.properties @@ -25,3 +25,4 @@ Check\ File\ Fingerprint=Controlla impronta file File\ to\ check=File da controllare description=Si ha un file JAR ma non se ne conosce la versione?
    possibile scoprirlo controllandone l''impronta memorizzata nel database di Jenkins more\ details=ulteriori dettagli +fingerprint.link=https://jenkins.io/redirect/fingerprint diff --git a/core/src/main/resources/jenkins/model/Messages_it.properties b/core/src/main/resources/jenkins/model/Messages_it.properties index 667120f62c..4604275fe3 100644 --- a/core/src/main/resources/jenkins/model/Messages_it.properties +++ b/core/src/main/resources/jenkins/model/Messages_it.properties @@ -36,3 +36,8 @@ Hudson.ControlCodeNotAllowed=Non CLI.keep-build.shortDescription=Contrassegna la compilazione per far s che sia mantenuta per sempre. CLI.safe-shutdown.shortDescription=Pone Jenkins in modalit quiete, attende che le compilazioni esistenti siano completate e quindi spegne Jenkins. IdStrategy.CaseInsensitive.DisplayName=Nessuna distinzione maiuscole/minuscole +PatternProjectNamingStrategy.DisplayName=Pattern +NewViewLink.NewView=Nuova visualizzazione +Hudson.Computer.DisplayName=master +Hudson.DisplayName=Jenkins +Hudson.Computer.Caption=Master diff --git a/core/src/main/resources/jenkins/security/csrf/CSRFAdministrativeMonitor/message_it.properties b/core/src/main/resources/jenkins/security/csrf/CSRFAdministrativeMonitor/message_it.properties new file mode 100644 index 0000000000..cd9d4d48af --- /dev/null +++ b/core/src/main/resources/jenkins/security/csrf/CSRFAdministrativeMonitor/message_it.properties @@ -0,0 +1,28 @@ +# The MIT License +# +# Copyright (c) 2018- Alessandro Menti +# +# 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. + +warningMessage=L''emittente CSRF non stata configurata. Ci potrebbe costituire \ + un problema di sicurezza. \ + Per ulteriori informazioni, fare riferimento a questa pagina. \ +
    \ + possibile modificare la configurazione corrente nella sezione \ + Protezione CSRF della categoria Sicurezza. diff --git a/core/src/main/resources/jenkins/security/csrf/Messages_it.properties b/core/src/main/resources/jenkins/security/csrf/Messages_it.properties new file mode 100644 index 0000000000..89ff71e39f --- /dev/null +++ b/core/src/main/resources/jenkins/security/csrf/Messages_it.properties @@ -0,0 +1,23 @@ +# The MIT License +# +# Copyright (c) 2018- Alessandro Menti +# +# 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. + +CSRFAdministrativeMonitor.displayName=Monitor protezione CSRF diff --git a/core/src/main/resources/lib/form/helpLink_it.properties b/core/src/main/resources/lib/form/helpLink_it.properties index 964c160c3c..58c9dcf3d3 100644 --- a/core/src/main/resources/lib/form/helpLink_it.properties +++ b/core/src/main/resources/lib/form/helpLink_it.properties @@ -21,4 +21,3 @@ # THE SOFTWARE. Help\ for\ feature\:=Guida per la funzionalit: -[Help]=[Guida] diff --git a/core/src/main/resources/lib/layout/layout_it.properties b/core/src/main/resources/lib/layout/layout_it.properties index adec3d1531..eab94c22d4 100644 --- a/core/src/main/resources/lib/layout/layout_it.properties +++ b/core/src/main/resources/lib/layout/layout_it.properties @@ -23,3 +23,4 @@ Page\ generated=Pagina generata il logout=disconnetti search=Cerca +searchBox.url=https://jenkins.io/redirect/search-box diff --git a/core/src/main/resources/lib/layout/pane_it.properties b/core/src/main/resources/lib/layout/pane_it.properties index 3c21d152aa..7b07c42d33 100644 --- a/core/src/main/resources/lib/layout/pane_it.properties +++ b/core/src/main/resources/lib/layout/pane_it.properties @@ -21,4 +21,3 @@ # THE SOFTWARE. expand=mostra -collapse=nascondi -- GitLab From 4e9376034546e9d18571cc02ea8fa5d1393418d5 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Sun, 14 Jan 2018 22:32:53 +0100 Subject: [PATCH 0260/1380] [FIX JENKINS-48407] Permission issue after upgrade to 2.93 Simply revert to using pre-NIO createTempFile for backward compatibility. There is no simple way to restore a similar way using NIO's createTempFile. --- core/src/main/java/hudson/util/AtomicFileWriter.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/util/AtomicFileWriter.java b/core/src/main/java/hudson/util/AtomicFileWriter.java index 05f752ce28..a2fae13bac 100644 --- a/core/src/main/java/hudson/util/AtomicFileWriter.java +++ b/core/src/main/java/hudson/util/AtomicFileWriter.java @@ -138,7 +138,8 @@ public class AtomicFileWriter extends Writer { } try { - tmpPath = Files.createTempFile(dir, "atomic", "tmp"); + // JENKINS-48407: NIO's createTempFile creates file with 0600 permissions, so we use pre-NIO for this... + tmpPath = File.createTempFile("atomic", "tmp", dir.toFile()).toPath(); } catch (IOException e) { throw new IOException("Failed to create a temporary file in "+ dir,e); } -- GitLab From 066252ee56c638c739d9ca24b62ebca17ce1e76e Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 14 Jan 2018 18:13:39 -0800 Subject: [PATCH 0261/1380] [maven-release-plugin] prepare release jenkins-2.102 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index be0be03f03..e4903f0aa9 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.102-SNAPSHOT + 2.102 cli diff --git a/core/pom.xml b/core/pom.xml index 1186c73e13..9cb6c5ab69 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.102-SNAPSHOT + 2.102 jenkins-core diff --git a/pom.xml b/pom.xml index 94602108c4..ae45343a4c 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.102-SNAPSHOT + 2.102 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.102 diff --git a/test/pom.xml b/test/pom.xml index 23e7bc0e62..8ea289cc96 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.102-SNAPSHOT + 2.102 test diff --git a/war/pom.xml b/war/pom.xml index e930a6dd80..50bd55037e 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.102-SNAPSHOT + 2.102 jenkins-war -- GitLab From d50004fa4e1f798b9c879881b0a44d76d2cfd0dd Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Sun, 14 Jan 2018 18:13:39 -0800 Subject: [PATCH 0262/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index e4903f0aa9..66a9ded91d 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.102 + 2.103-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 9cb6c5ab69..85e168a2df 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.102 + 2.103-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index ae45343a4c..91e3fa2e49 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.102 + 2.103-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.102 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 8ea289cc96..2e125185d6 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.102 + 2.103-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 50bd55037e..b25893b6b5 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.102 + 2.103-SNAPSHOT jenkins-war -- GitLab From ad8fecfbd39652f0127aec070d91eb7f381bb0d8 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 15 Jan 2018 16:47:49 +0100 Subject: [PATCH 0263/1380] [JENKINS-48946] - Add all private classes of java.util.Collections (OpenJDK) to the whitelist. --- .../jenkins/security/whitelisted-classes.txt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 064dd47660..0f8e589a3c 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -45,16 +45,42 @@ java.security.KeyRep java.util.ArrayDeque java.util.ArrayList java.util.Arrays$ArrayList +java.util.Collections$AsLIFOQueue +java.util.Collections$CheckedCollection +java.util.Collections$CheckedList +java.util.Collections$CheckedMap +java.util.Collections$CheckedNavigableMap +java.util.Collections$CheckedNavigableSet +java.util.Collections$CheckedQueue +java.util.Collections$CheckedRandomAccessList +java.util.Collections$CheckedSet +java.util.Collections$CheckedSortedMap +java.util.Collections$CheckedSortedSet +java.util.Collections$CopiesList java.util.Collections$EmptyList +java.util.Collections$EmptyMap java.util.Collections$EmptySet java.util.Collections$SetFromMap java.util.Collections$SingletonList java.util.Collections$SingletonMap java.util.Collections$SingletonSet +java.util.Collections$SynchronizedCollection +java.util.Collections$SynchronizedList +java.util.Collections$SynchronizedRandomAccessList +java.util.Collections$SynchronizedMap +java.util.Collections$SynchronizedNavigableMap +java.util.Collections$SynchronizedNavigableSet +java.util.Collections$SynchronizedSortedMap +java.util.Collections$SynchronizedSortedSet java.util.Collections$UnmodifiableCollection java.util.Collections$UnmodifiableList +java.util.Collections$UnmodifiableMap java.util.Collections$UnmodifiableRandomAccessList java.util.Collections$UnmodifiableSet +java.util.Collections$UnmodifiableNavigableMap +java.util.Collections$UnmodifiableNavigableSet +java.util.Collections$UnmodifiableSortedSet +java.util.Collections$UnmodifiableSortedMap java.util.Date java.util.EnumMap java.util.GregorianCalendar -- GitLab From 260a2576f2e8e141706b4b65a67fb052dccd8b8d Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 15 Jan 2018 20:02:58 +0100 Subject: [PATCH 0264/1380] [JENKINS-48946] - Move whitelist ordering test to core to fail fast --- core/pom.xml | 6 ++ .../security/ClassFilterImplSanityTest.java | 61 +++++++++++++++++++ .../jenkins/security/ClassFilterImplTest.java | 16 ----- 3 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 core/src/test/java/jenkins/security/ClassFilterImplSanityTest.java diff --git a/core/pom.xml b/core/pom.xml index 85e168a2df..2f2870fb98 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -179,6 +179,12 @@ THE SOFTWARE. tests test + + org.hamcrest + hamcrest-library + 1.3 + test + com.infradna.tool diff --git a/core/src/test/java/jenkins/security/ClassFilterImplSanityTest.java b/core/src/test/java/jenkins/security/ClassFilterImplSanityTest.java new file mode 100644 index 0000000000..e665cd7bfe --- /dev/null +++ b/core/src/test/java/jenkins/security/ClassFilterImplSanityTest.java @@ -0,0 +1,61 @@ +/* + * The MIT License + * + * Copyright 2017-2018 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 hudson.util.CopyOnWriteMap; +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertThat; +import static org.hamcrest.Matchers.*; + +/** + * Tests for {@link ClassFilterImpl}. + * More tests are available in the "test" module. + */ +public class ClassFilterImplSanityTest { + + @Test + public void whitelistSanity() throws Exception { + try (InputStream is = ClassFilterImpl.class.getResourceAsStream("whitelisted-classes.txt")) { + List lines = IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*")).collect(Collectors.toList()); + TreeSet set = new TreeSet<>(lines); + assertThat("whitelist is NOT ordered", new TreeSet<>(lines), contains(lines.toArray(new String[0]))); + for (String line : lines) { + try { + Class.forName(line); + } catch (ClassNotFoundException x) { + System.err.println("skipping checks of unknown class " + line); + } + } + } + } + +} diff --git a/test/src/test/java/jenkins/security/ClassFilterImplTest.java b/test/src/test/java/jenkins/security/ClassFilterImplTest.java index c3ca2c3bac..b94601d0d9 100644 --- a/test/src/test/java/jenkins/security/ClassFilterImplTest.java +++ b/test/src/test/java/jenkins/security/ClassFilterImplTest.java @@ -66,22 +66,6 @@ public class ClassFilterImplTest { @Rule public LoggerRule logging = new LoggerRule().record(ClassFilterImpl.class, Level.FINE); - @WithoutJenkins - @Test - public void whitelistSanity() throws Exception { - try (InputStream is = ClassFilterImpl.class.getResourceAsStream("whitelisted-classes.txt")) { - List lines = IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*")).collect(Collectors.toList()); - assertThat("whitelist is ordered", new TreeSet<>(lines), contains(lines.toArray(new String[0]))); - for (String line : lines) { - try { - Class.forName(line); - } catch (ClassNotFoundException x) { - System.err.println("skipping checks of unknown class " + line); - } - } - } - } - @Test public void masterToSlaveBypassesWhitelist() throws Exception { assumeThat(ClassFilterImpl.WHITELISTED_CLASSES, not(contains(LinkedListMultimap.class.getName()))); -- GitLab From e0a1e3b5c825893d1337716345a39563a95c24db Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 15 Jan 2018 20:05:15 +0100 Subject: [PATCH 0265/1380] [JENKINS-48946] - Fix ordering of the entries in whitelist --- .../resources/jenkins/security/whitelisted-classes.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 0f8e589a3c..ab78b23a37 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -66,21 +66,22 @@ java.util.Collections$SingletonMap java.util.Collections$SingletonSet java.util.Collections$SynchronizedCollection java.util.Collections$SynchronizedList -java.util.Collections$SynchronizedRandomAccessList java.util.Collections$SynchronizedMap java.util.Collections$SynchronizedNavigableMap java.util.Collections$SynchronizedNavigableSet +java.util.Collections$SynchronizedRandomAccessList java.util.Collections$SynchronizedSortedMap java.util.Collections$SynchronizedSortedSet java.util.Collections$UnmodifiableCollection java.util.Collections$UnmodifiableList java.util.Collections$UnmodifiableMap -java.util.Collections$UnmodifiableRandomAccessList -java.util.Collections$UnmodifiableSet java.util.Collections$UnmodifiableNavigableMap java.util.Collections$UnmodifiableNavigableSet -java.util.Collections$UnmodifiableSortedSet +java.util.Collections$UnmodifiableRandomAccessList +java.util.Collections$UnmodifiableSet java.util.Collections$UnmodifiableSortedMap +java.util.Collections$UnmodifiableSortedSet + java.util.Date java.util.EnumMap java.util.GregorianCalendar -- GitLab From 6923dbc4f84b618a732b6cf598516aac61d70fe2 Mon Sep 17 00:00:00 2001 From: Manuel Blechschmidt Date: Tue, 16 Jan 2018 09:25:17 +0100 Subject: [PATCH 0266/1380] * JENKINS-48957 added jboss-deployment-structure to remove using of jackson from wildfly --- .../main/webapp/WEB-INF/jboss-deployment-structure.xml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml diff --git a/war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml new file mode 100644 index 0000000000..57682c7350 --- /dev/null +++ b/war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml @@ -0,0 +1,8 @@ + + + + + + + + -- GitLab From d9d3efb10d0b99d4590d97319e280c4a24f3ec61 Mon Sep 17 00:00:00 2001 From: Manuel Blechschmidt Date: Tue, 16 Jan 2018 10:30:07 +0100 Subject: [PATCH 0267/1380] * JENKINS-48957 exclude whole jaxrs otherwise jackson is automatically added --- war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml index 57682c7350..f1164914d4 100644 --- a/war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml +++ b/war/src/main/webapp/WEB-INF/jboss-deployment-structure.xml @@ -2,7 +2,13 @@ + + + + + + -- GitLab From 88eb5ee3e694d95f1c7da44bfce181c4590a47ad Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Tue, 16 Jan 2018 12:58:12 +0100 Subject: [PATCH 0268/1380] [JENKINS-48958] - whitelist java.util.Optional --- core/src/main/resources/jenkins/security/whitelisted-classes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index ab78b23a37..5bf54df868 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -92,6 +92,7 @@ java.util.LinkedHashMap java.util.LinkedHashSet java.util.LinkedList java.util.Locale +java.util.Optional java.util.Properties java.util.RegularEnumSet java.util.Stack -- GitLab From 00d9397ab46d2204f26ebb19cc3a1a093d22e0d9 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Tue, 16 Jan 2018 16:58:14 +0100 Subject: [PATCH 0269/1380] [FIX JENKINS-48407] Test to check we have the expected permissions --- .../hudson/util/AtomicFileWriterTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/core/src/test/java/hudson/util/AtomicFileWriterTest.java b/core/src/test/java/hudson/util/AtomicFileWriterTest.java index 51bd205cb4..8d231c487b 100644 --- a/core/src/test/java/hudson/util/AtomicFileWriterTest.java +++ b/core/src/test/java/hudson/util/AtomicFileWriterTest.java @@ -6,20 +6,36 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.Issue; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.EnumSet; +import java.util.Set; import static org.hamcrest.core.StringContains.*; +import static java.nio.file.attribute.PosixFilePermission.GROUP_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.GROUP_READ; +import static java.nio.file.attribute.PosixFilePermission.GROUP_WRITE; +import static java.nio.file.attribute.PosixFilePermission.OTHERS_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ; +import static java.nio.file.attribute.PosixFilePermission.OTHERS_WRITE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; +import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeThat; public class AtomicFileWriterTest { private static final String PREVIOUS = "previous value \n blah"; @@ -124,4 +140,55 @@ public class AtomicFileWriterTest { containsString("exists and is neither a directory nor a symlink to a directory")); } } + + @Issue("JENKINS-48407") + @Test + public void checkPermissions() throws IOException, InterruptedException { + + final File newFile = tmp.newFile(); + + // Check Posix calls are supported (to avoid running this test on Windows for instance) + boolean posixSupported = true; + try { + Files.getPosixFilePermissions(newFile.toPath()); + } catch (UnsupportedOperationException e) { + posixSupported = false; + } + assumeThat(posixSupported, is(true)); + + // given + final Set givenPermissions = EnumSet.of(OWNER_READ, + OWNER_WRITE, + GROUP_READ, + GROUP_WRITE, + OTHERS_READ + ); + + final Set notGivenPermissions = EnumSet.of(OWNER_EXECUTE, + GROUP_EXECUTE, + OTHERS_WRITE, + OTHERS_EXECUTE); + + Files.setPosixFilePermissions(newFile.toPath(), givenPermissions); + Path filePath = newFile.toPath(); + + // when + AtomicFileWriter w = new AtomicFileWriter(filePath, StandardCharsets.UTF_8); + w.write("whatever"); + w.commit(); + + // then + assertFalse(w.getTemporaryPath().toFile().exists()); + assertTrue(filePath.toFile().exists()); + + final Set posixFilePermissions = Files.getPosixFilePermissions(filePath); + + for (PosixFilePermission perm : givenPermissions) { + assertTrue("missing: " + perm, posixFilePermissions.contains(perm)); + } + + for (PosixFilePermission perm : notGivenPermissions) { + assertFalse("should not be allowed: " + perm, posixFilePermissions.contains(perm)); + } + } } -- GitLab From c339ab4aff568598852edbbc3d6096bc039db874 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 16 Jan 2018 15:34:42 -0500 Subject: [PATCH 0270/1380] Minor cleanups of ClassFilterImplSanityTest. --- .../security/ClassFilterImplSanityTest.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/core/src/test/java/jenkins/security/ClassFilterImplSanityTest.java b/core/src/test/java/jenkins/security/ClassFilterImplSanityTest.java index e665cd7bfe..77cf27883c 100644 --- a/core/src/test/java/jenkins/security/ClassFilterImplSanityTest.java +++ b/core/src/test/java/jenkins/security/ClassFilterImplSanityTest.java @@ -23,30 +23,28 @@ */ package jenkins.security; -import hudson.util.CopyOnWriteMap; -import org.apache.commons.io.IOUtils; -import org.junit.Test; - import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.TreeSet; import java.util.stream.Collectors; - -import static org.junit.Assert.assertThat; +import org.apache.commons.io.IOUtils; import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import org.junit.Test; +import org.jvnet.hudson.test.For; /** * Tests for {@link ClassFilterImpl}. - * More tests are available in the "test" module. + * More tests are available in the {@code test} module. */ +@For(ClassFilterImpl.class) public class ClassFilterImplSanityTest { @Test public void whitelistSanity() throws Exception { try (InputStream is = ClassFilterImpl.class.getResourceAsStream("whitelisted-classes.txt")) { List lines = IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*")).collect(Collectors.toList()); - TreeSet set = new TreeSet<>(lines); assertThat("whitelist is NOT ordered", new TreeSet<>(lines), contains(lines.toArray(new String[0]))); for (String line : lines) { try { -- GitLab From c1cf47c57f77b66402aa6f4a719d258b02c79e18 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 16 Jan 2018 15:44:53 -0500 Subject: [PATCH 0271/1380] Some more refinements to ClassFilterImpl shutoff during development. --- .../main/java/jenkins/security/ClassFilterImpl.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/jenkins/security/ClassFilterImpl.java b/core/src/main/java/jenkins/security/ClassFilterImpl.java index 6c2f79b77d..2de71b01e3 100644 --- a/core/src/main/java/jenkins/security/ClassFilterImpl.java +++ b/core/src/main/java/jenkins/security/ClassFilterImpl.java @@ -130,7 +130,7 @@ public class ClassFilterImpl extends ClassFilter { return true; } String name = c.getName(); - if (Main.isUnitTest && name.contains("$$EnhancerByMockitoWithCGLIB$$")) { + if (Main.isUnitTest && (name.contains("$$EnhancerByMockitoWithCGLIB$$") || name.contains("$$FastClassByMockitoWithCGLIB$$") || name.startsWith("org.mockito."))) { mockOff(); return false; } @@ -222,11 +222,11 @@ public class ClassFilterImpl extends ClassFilter { LOGGER.log(Level.WARNING, "problem checking " + loc, x); } } - if (Main.isUnitTest || Main.isDevelopmentMode) { - if (loc.endsWith("/target/classes/")) { - LOGGER.log(Level.FINE, "{0} seems to be current plugin classes, OK", loc); - return true; - } + if (loc.endsWith("/target/classes/")) { + LOGGER.log(Level.FINE, "{0} seems to be current plugin classes, OK", loc); + return true; + } + if (Main.isUnitTest) { if (loc.endsWith("/target/test-classes/") || loc.endsWith("-tests.jar")) { LOGGER.log(Level.FINE, "{0} seems to be test classes, OK", loc); return true; -- GitLab From 01c46ba29da1a22f3a3389af979c61d7bc957a0e Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 16 Jan 2018 18:53:09 -0500 Subject: [PATCH 0272/1380] Removing XStream2Security247Test as it no longer reproduces SECURITY-247. --- .../hudson/util/XStream2Security247Test.java | 136 ------------------ .../util/XStream2Security247Test/config.xml | 27 ---- 2 files changed, 163 deletions(-) delete mode 100644 test/src/test/java/hudson/util/XStream2Security247Test.java delete mode 100644 test/src/test/resources/hudson/util/XStream2Security247Test/config.xml diff --git a/test/src/test/java/hudson/util/XStream2Security247Test.java b/test/src/test/java/hudson/util/XStream2Security247Test.java deleted file mode 100644 index d67434495f..0000000000 --- a/test/src/test/java/hudson/util/XStream2Security247Test.java +++ /dev/null @@ -1,136 +0,0 @@ -package hudson.util; - -import hudson.model.Items; -import org.apache.commons.io.*; -import org.apache.commons.io.IOUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.jvnet.hudson.test.Issue; -import org.jvnet.hudson.test.JenkinsRule; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import javax.servlet.ReadListener; -import javax.servlet.ServletInputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -import static org.junit.Assert.assertFalse; -import static org.mockito.Mockito.when; - -public class XStream2Security247Test { - - @Rule - public JenkinsRule j = new JenkinsRule(); - - @Rule - public TemporaryFolder f = new TemporaryFolder(); - - @Mock - private StaplerRequest req; - - @Mock - private StaplerResponse rsp; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - } - - @Test - @Issue("SECURITY-247") - public void testXmlLoad() throws Exception { - File exploitFile = f.newFile(); - try { - // be extra sure there's no file already - if (exploitFile.exists() && !exploitFile.delete()) { - throw new IllegalStateException("file exists and cannot be deleted"); - } - File tempJobDir = new File(j.jenkins.getRootDir(), "security247"); - - String exploitXml = org.apache.commons.io.IOUtils.toString( - XStream2Security247Test.class.getResourceAsStream( - "/hudson/util/XStream2Security247Test/config.xml"), "UTF-8"); - - exploitXml = exploitXml.replace("@TOKEN@", exploitFile.getAbsolutePath()); - - FileUtils.write(new File(tempJobDir, "config.xml"), exploitXml); - - try { - Items.load(j.jenkins, tempJobDir); - } catch (Exception e) { - // ignore - } - assertFalse("no file should be created here", exploitFile.exists()); - } finally { - exploitFile.delete(); - } - } - - @Test - @Issue("SECURITY-247") - public void testPostJobXml() throws Exception { - File exploitFile = f.newFile(); - try { - // be extra sure there's no file already - if (exploitFile.exists() && !exploitFile.delete()) { - throw new IllegalStateException("file exists and cannot be deleted"); - } - File tempJobDir = new File(j.jenkins.getRootDir(), "security247"); - - String exploitXml = org.apache.commons.io.IOUtils.toString( - XStream2Security247Test.class.getResourceAsStream( - "/hudson/util/XStream2Security247Test/config.xml"), "UTF-8"); - - exploitXml = exploitXml.replace("@TOKEN@", exploitFile.getAbsolutePath()); - - when(req.getMethod()).thenReturn("POST"); - when(req.getInputStream()).thenReturn(new Stream(IOUtils.toInputStream(exploitXml))); - when(req.getContentType()).thenReturn("application/xml"); - when(req.getParameter("name")).thenReturn("foo"); - - try { - j.jenkins.doCreateItem(req, rsp); - } catch (Exception e) { - // don't care - } - - assertFalse("no file should be created here", exploitFile.exists()); - } finally { - exploitFile.delete(); - } - } - - private static class Stream extends ServletInputStream { - private final InputStream inner; - - public Stream(final InputStream inner) { - this.inner = inner; - } - - @Override - public int read() throws IOException { - return inner.read(); - } - - @Override - public boolean isFinished() { - return false; - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setReadListener(ReadListener readListener) { - throw new UnsupportedOperationException(); - } - } -} diff --git a/test/src/test/resources/hudson/util/XStream2Security247Test/config.xml b/test/src/test/resources/hudson/util/XStream2Security247Test/config.xml deleted file mode 100644 index 5b9b22ab59..0000000000 --- a/test/src/test/resources/hudson/util/XStream2Security247Test/config.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - hashCode - - - - - touch - @TOKEN@ - - false - - 0 - 0 - - 0 - start - - - - - 1 - - -- GitLab From 8def746838bedad3215fccd40cc8847900040724 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Wed, 17 Jan 2018 17:28:17 +0100 Subject: [PATCH 0273/1380] [JENKINS-49000] - Whitelist Extra Guava Classes --- .../jenkins/security/whitelisted-classes.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 5bf54df868..a747759abe 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -1,11 +1,25 @@ +com.google.common.collect.AbstractListMultimap +com.google.common.collect.AbstractMultimap com.google.common.collect.AbstractSetMultimap +# Artifactory - https://issues.jenkins-ci.org/browse/JENKINS-48991 +com.google.common.collect.ArrayListMultimap com.google.common.collect.EmptyImmutableList com.google.common.collect.EmptyImmutableSet com.google.common.collect.EmptyImmutableSortedSet com.google.common.collect.HashMultimap +# First hit: https://github.com/jenkinsci/pitmutation-plugin/ +com.google.common.collect.HashMultiset com.google.common.collect.ImmutableList +# Registered in XStream2 converters +com.google.common.collect.ImmutableMap +# Registered in XStream2 converters +com.google.common.collect.ImmutableSet com.google.common.collect.ImmutableSet$SerializedForm com.google.common.collect.ImmutableSortedSet +# https://issues.jenkins-ci.org/browse/JENKINS-48989 +com.google.common.collect.Maps$TransformedEntriesMap +# https://github.com/jenkinsci/sonar-gerrit-plugin +com.google.common.collect.Multimap com.google.common.collect.RegularImmutableList com.google.common.collect.RegularImmutableSet com.google.common.collect.RegularImmutableSortedSet -- GitLab From 847a2534ce7a63e2b0943d7f5ddbd4a612a69776 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 17 Jan 2018 14:52:07 -0800 Subject: [PATCH 0274/1380] [maven-release-plugin] prepare release jenkins-2.89.3 --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index a6f850b032..6610a1ea30 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.89.3-SNAPSHOT + 2.89.3 cli diff --git a/core/pom.xml b/core/pom.xml index 77438d3623..8e26a992c7 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.3-SNAPSHOT + 2.89.3 jenkins-core diff --git a/pom.xml b/pom.xml index 803856390a..38364a8fcd 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.3-SNAPSHOT + 2.89.3 pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - HEAD + jenkins-2.89.3 diff --git a/test/pom.xml b/test/pom.xml index 40e66224ae..c1aafcfdac 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.3-SNAPSHOT + 2.89.3 test diff --git a/war/pom.xml b/war/pom.xml index 4405af0e96..58956cc3cb 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.3-SNAPSHOT + 2.89.3 jenkins-war -- GitLab From 2904044e5105d415383305b802695ca059ed2153 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 17 Jan 2018 14:52:07 -0800 Subject: [PATCH 0275/1380] [maven-release-plugin] prepare for next development iteration --- cli/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 4 ++-- test/pom.xml | 2 +- war/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/pom.xml b/cli/pom.xml index 6610a1ea30..1c717b6e89 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.89.3 + 2.89.4-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 8e26a992c7..b9345cd9b8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.3 + 2.89.4-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index 38364a8fcd..b45748cc3e 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.3 + 2.89.4-SNAPSHOT pom Jenkins main module @@ -58,7 +58,7 @@ THE SOFTWARE. scm:git:git://github.com/jenkinsci/jenkins.git scm:git:ssh://git@github.com/jenkinsci/jenkins.git https://github.com/jenkinsci/jenkins - jenkins-2.89.3 + HEAD diff --git a/test/pom.xml b/test/pom.xml index c1aafcfdac..bcf700e1f8 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.3 + 2.89.4-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 58956cc3cb..7dc5da5d65 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.89.3 + 2.89.4-SNAPSHOT jenkins-war -- GitLab From d1e278b6528aae9b966d724182cbbc2a18489976 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 18 Jan 2018 16:16:51 -0500 Subject: [PATCH 0276/1380] [JENKINS-49025] Add java.lang.String$CaseInsensitiveComparator. --- core/src/main/resources/jenkins/security/whitelisted-classes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/resources/jenkins/security/whitelisted-classes.txt b/core/src/main/resources/jenkins/security/whitelisted-classes.txt index 5bf54df868..ff1ada9279 100644 --- a/core/src/main/resources/jenkins/security/whitelisted-classes.txt +++ b/core/src/main/resources/jenkins/security/whitelisted-classes.txt @@ -36,6 +36,7 @@ java.lang.Object java.lang.Short java.lang.StackTraceElement java.lang.String +java.lang.String$CaseInsensitiveComparator java.lang.StringBuffer java.lang.StringBuilder java.lang.reflect.Proxy -- GitLab From 64d5e840748b50fa32f725244563c3b68f30dc46 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 19 Jan 2018 15:22:02 -0500 Subject: [PATCH 0277/1380] Confirming that RobustReflectionConverter gracefully handles loading of configuration fields now blocked by JEP-200. --- .../jenkins/security/ClassFilterImplTest.java | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/test/src/test/java/jenkins/security/ClassFilterImplTest.java b/test/src/test/java/jenkins/security/ClassFilterImplTest.java index b94601d0d9..89131e335f 100644 --- a/test/src/test/java/jenkins/security/ClassFilterImplTest.java +++ b/test/src/test/java/jenkins/security/ClassFilterImplTest.java @@ -25,24 +25,25 @@ package jenkins.security; import com.google.common.collect.LinkedListMultimap; +import com.thoughtworks.xstream.XStream; +import hudson.ExtensionList; import hudson.Launcher; +import hudson.XmlFile; +import hudson.diagnosis.OldDataMonitor; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.FreeStyleProject; import hudson.model.Result; +import hudson.model.Saveable; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Iterator; -import java.util.List; -import java.util.TreeSet; +import java.util.Collections; +import java.util.Map; import java.util.logging.Level; -import java.util.stream.Collectors; import jenkins.model.GlobalConfiguration; -import org.apache.commons.io.IOUtils; +import org.apache.commons.io.FileUtils; import org.junit.Test; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; @@ -53,7 +54,6 @@ import org.jvnet.hudson.test.BuildWatcher; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.LoggerRule; import org.jvnet.hudson.test.TestExtension; -import org.jvnet.hudson.test.WithoutJenkins; public class ClassFilterImplTest { @@ -136,13 +136,26 @@ public class ClassFilterImplTest { config.save(); config.obj = LinkedListMultimap.create(); config.save(); - assertThat(config.xml(), not(containsString("LinkedListMultimap"))); + assertThat(config.getConfigFile().asString(), not(containsString("LinkedListMultimap"))); + config.unrelated = "modified"; + FileUtils.write(config.getConfigFile().getFile(), new XStream().toXML(config)); + assertThat(config.getConfigFile().asString(), allOf(containsString("LinkedListMultimap"), containsString("modified"))); + config.obj = null; + config.unrelated = null; + config.load(); + assertNull(config.obj); + assertEquals("modified", config.unrelated); + Map data = ExtensionList.lookupSingleton(OldDataMonitor.class).getData(); + assertEquals(Collections.singleton(config), data.keySet()); + assertThat(data.values().iterator().next().extra, allOf(containsString("LinkedListMultimap"), containsString("https://jenkins.io/redirect/class-filter/"))); } @TestExtension("xstreamRequiresWhitelist") public static class Config extends GlobalConfiguration { LinkedListMultimap obj; - String xml() throws IOException { - return getConfigFile().asString(); + String unrelated; + @Override + protected XmlFile getConfigFile() { + return super.getConfigFile(); } } -- GitLab From 976fe5aaa05858b88c5141b8d133efea696233b8 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Fri, 19 Jan 2018 17:52:02 -0500 Subject: [PATCH 0278/1380] [JENKINS-49054] Work around XStream bug by looking up list type before trying to unmarshal elements. --- .../java/hudson/util/DescribableList.java | 5 +- .../java/hudson/util/DescribableListTest.java | 99 +++++++++++++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 core/src/test/java/hudson/util/DescribableListTest.java diff --git a/core/src/main/java/hudson/util/DescribableList.java b/core/src/main/java/hudson/util/DescribableList.java index 7d90a2ed74..18642093c7 100644 --- a/core/src/main/java/hudson/util/DescribableList.java +++ b/core/src/main/java/hudson/util/DescribableList.java @@ -270,10 +270,9 @@ public class DescribableList, D extends Descriptor> } public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { - CopyOnWriteList core = copyOnWriteListConverter.unmarshal(reader, context); - try { - DescribableList r = (DescribableList)context.getRequiredType().newInstance(); + DescribableList r = (DescribableList) context.getRequiredType().asSubclass(DescribableList.class).newInstance(); + CopyOnWriteList core = copyOnWriteListConverter.unmarshal(reader, context); r.data.replaceBy(core); return r; } catch (InstantiationException e) { diff --git a/core/src/test/java/hudson/util/DescribableListTest.java b/core/src/test/java/hudson/util/DescribableListTest.java new file mode 100644 index 0000000000..de4449e7db --- /dev/null +++ b/core/src/test/java/hudson/util/DescribableListTest.java @@ -0,0 +1,99 @@ +/* + * The MIT License + * + * Copyright 2018 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 hudson.util; + +import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter; +import hudson.model.Describable; +import hudson.model.Descriptor; +import org.junit.Test; +import static org.junit.Assert.*; +import org.jvnet.hudson.test.Issue; + +public class DescribableListTest { + + @Issue("JENKINS-49054") + @Test + public void exceptionDuringUnmarshalling() throws Exception { + Data data = new Data(); + data.list.add(new Datum(1)); + data.list.add(new Datum(2)); + data.list.add(new Datum(3)); + XStream2 xs = new XStream2(); + xs.addCriticalField(Data.class, "list"); + String xml = xs.toXML(data); + data = (Data) xs.fromXML(xml); + assertEquals("[1, 3]", data.toString()); + } + + private static final class Data { + + final DescribableList> list = new DescribableList<>(); + + @Override + public String toString() { + return list.toString(); + } + + } + + private static final class Datum implements Describable { + + final int val; + + Datum(int val) { + this.val = val; + } + + @Override + public Descriptor getDescriptor() { + return new Descriptor(Datum.class) {}; + } + + @Override + public String toString() { + return Integer.toString(val); + } + + public static final class ConverterImpl extends AbstractSingleValueConverter { + + @Override + public boolean canConvert(Class type) { + return type == Datum.class; + } + + @Override + public Object fromString(String str) { + int val = Integer.parseInt(str); + if (val == 2) { + throw new IllegalStateException("oops"); + } + return new Datum(val); + } + + } + + } + +} -- GitLab From 0311b2dffa76ba01748b1be6a18fc7c1955e65fe Mon Sep 17 00:00:00 2001 From: Vincent Latombe Date: Sun, 21 Jan 2018 08:38:52 +0100 Subject: [PATCH 0279/1380] [FIXES JENKINS-47406] Milestone JOB_LOADED is now attained after cleaning up obsolete items (#3078) * [FIXES JENKINS-47406] Milestone JOB_LOADED is now attained after cleaning up obsolete items This allows @Initializer(after=InitMilestone.JOB_LOADED) implementations to create items safely without risking them to be removed. * [JENKINS-47406] Add test with an initializer creating a job * Revert "[FIXES JENKINS-47406] Milestone JOB_LOADED is now attained after cleaning up obsolete items" This reverts commit 79ce796b00fde11fda586c9c9e2b8e93a53e2b31. * [JENKINS-47406] Reinstate the test with Jesse's solution * Reverted this commit by mistake Revert "Revert "[FIXES JENKINS-47406] Milestone JOB_LOADED is now attained after cleaning up obsolete items"" This reverts commit 36c499c58c83427c7b8a6d3cc5902ec26732b2c7. * Add link to hpi sources * Fix Mix-up after the previous merge --- core/src/main/java/jenkins/model/Jenkins.java | 7 ++++--- .../src/test/java/jenkins/model/JenkinsTest.java | 11 +++++++++++ .../src/test/resources/plugins/jenkins-47406.hpi | Bin 0 -> 5022 bytes 3 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 test/src/test/resources/plugins/jenkins-47406.hpi diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 5de956996b..01951e03f9 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -3054,8 +3054,9 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve } }); + List loadJobs = new ArrayList<>(); for (final File subdir : subdirs) { - g.requires(loadJenkins).attains(JOB_LOADED).notFatal().add("Loading item " + subdir.getName(), new Executable() { + loadJobs.add(g.requires(loadJenkins).attains(JOB_LOADED).notFatal().add("Loading item " + subdir.getName(), new Executable() { public void run(Reactor session) throws Exception { if(!Items.getConfigFile(subdir).exists()) { //Does not have job config file, so it is not a jenkins job hence skip it @@ -3065,10 +3066,10 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve items.put(item.getName(), item); loadedNames.add(item.getName()); } - }); + })); } - g.requires(JOB_LOADED).attains(COMPLETED).add("Cleaning up obsolete items deleted from the disk", new Executable() { + g.requires(loadJobs.toArray(new Handle[loadJobs.size()])).attains(JOB_LOADED).add("Cleaning up obsolete items deleted from the disk", new Executable() { public void run(Reactor reactor) throws Exception { // anything we didn't load from disk, throw them away. // doing this after loading from disk allows newly loaded items diff --git a/test/src/test/java/jenkins/model/JenkinsTest.java b/test/src/test/java/jenkins/model/JenkinsTest.java index a68ee0a4d6..833230dbe8 100644 --- a/test/src/test/java/jenkins/model/JenkinsTest.java +++ b/test/src/test/java/jenkins/model/JenkinsTest.java @@ -23,6 +23,7 @@ */ package jenkins.model; +import static hudson.init.InitMilestone.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.greaterThan; @@ -44,6 +45,7 @@ import com.gargoylesoftware.htmlunit.WebResponse; import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.init.Initializer; import hudson.maven.MavenModuleSet; import hudson.maven.MavenModuleSetBuild; import hudson.model.Computer; @@ -72,6 +74,7 @@ import org.jvnet.hudson.test.ExtractResourceSCM; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.JenkinsRule.WebClient; import org.jvnet.hudson.test.TestExtension; +import org.jvnet.hudson.test.recipes.WithPlugin; import org.kohsuke.stapler.HttpResponse; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; @@ -83,6 +86,7 @@ import java.net.URL; import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.logging.Logger; import org.jvnet.hudson.test.MockAuthorizationStrategy; import javax.annotation.CheckForNull; @@ -683,4 +687,11 @@ public class JenkinsTest { VersionNumber nullVersion = Jenkins.getStoredVersion(); assertNull(nullVersion); } + + @Issue("JENKINS-47406") + @Test + @WithPlugin("jenkins-47406.hpi") // Sources: https://github.com/Vlatombe/jenkins-47406 + public void jobCreatedByInitializerIsRetained() { + assertNotNull("JENKINS-47406 should exist", j.jenkins.getItem("JENKINS-47406")); + } } diff --git a/test/src/test/resources/plugins/jenkins-47406.hpi b/test/src/test/resources/plugins/jenkins-47406.hpi new file mode 100644 index 0000000000000000000000000000000000000000..747db946fdd0f53d965e98f40abf6555adbe6871 GIT binary patch literal 5022 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 literal 0 HcmV?d00001 -- GitLab From 6f2769e69ae23114f381cd095712bfa33ced91d2 Mon Sep 17 00:00:00 2001 From: Manuel Recena Date: Sun, 21 Jan 2018 14:28:51 +0100 Subject: [PATCH 0280/1380] [JENKINS-43786] Overhaul of Manage Jenkins page (#2857) * [JENKINS-43786] Initial source code modifications * [JENKINS-43786] Adding a customized Bootstrap version and some messages were adapted * [JENKINS-43786] Some Monitors included in the core have been adapted * [JENKINS-43786] Reverting an unexisting typo * [JENKINS-43786] Applying final styles * [JENKINS-43786] Addressing more use cases * [JENKINS-43786] Revisiting how the buttons are shown * [JENKINS-43786] Adjustments in the administrative monitor pop-up * [JENKINS-43786] Added more use cases related with administrative messages * [JENKINS-43786] Adapted the Administrative Monitors provided by Jenkins core * [JENKINS-43786] Addressed the @daniel-beck's review * [JENKINS-43786] Wrong indent * [JENKINS-43786] Better semantic HTML * [JENKINS-43786] Better semantic HTML * [JENKINS-43786] Applying the same criteria for listing items * [JENKINS-43786] Adapted tests * [JENKINS-43786] Removing redirect links in the administrative monitor descriptions * [JENKINS-43786] Adapted Administrative Monitor * [JENKINS-43786] Reverted Redirect URLs * [JENKINS-43786] Applying the same code style * [JENKINS-43786] Applying the same code style * [JENKINS-43786] Apply a common HTML markup to the Administrative Monitors in core * [JENKINS-43786] One more Administrative Monitor updated * [JENKINS-43786] Polishing some details * [JENKINS-43786] Deprecating CSS Styles * [JENKINS-43786] We are ready for deprecating anything * [JENKINS-43786] We are ready for deprecating anything * [JENKINS-43786] Details --- .../message.jelly | 14 +- .../message.properties | 2 +- .../PluginUpdateMonitor/message.jelly | 14 +- .../PluginUpdateMonitor/message.properties | 2 +- .../message.jelly | 33 +- .../message.properties | 2 +- .../HudsonHomeDiskUsageMonitor/message.jelly | 18 +- .../message.properties | 2 +- .../NullIdDescriptorMonitor/message.jelly | 18 +- .../message.properties | 2 +- .../diagnosis/OldDataMonitor/message.jelly | 6 +- .../ReverseProxySetupMonitor/message.jelly | 4 +- .../TooManyJobsButNoView/message.jelly | 10 +- .../CoreUpdateMonitor/message.jelly | 18 +- .../MonitorMarkedNodeOffline/message.jelly | 6 +- .../MigrationCompleteNotice/message.jelly | 4 +- .../MigrationFailedNotice/message.jelly | 2 +- .../os/solaris/ZFSInstaller/message.jelly | 10 +- .../AdministrativeMonitorImpl/message.jelly | 8 +- .../util/AdministrativeError/message.jelly | 2 +- .../jenkins/CLI/WarnWhenEnabled/message.jelly | 16 +- .../CLI/WarnWhenEnabled/message.properties | 2 +- .../diagnosis/HsErrPidList/message.jelly | 2 +- .../message.jelly | 7 +- .../SecurityIsOffMonitor/message.jelly | 10 +- .../resources.css | 24 +- .../jenkins/management/Messages.properties | 2 +- .../jenkins/management/PluginsLink/info.jelly | 6 +- .../management/PluginsLink/info.properties | 1 + .../DownloadSettings/Warning/message.jelly | 12 +- .../message.jelly | 14 +- .../message.properties | 2 +- .../jenkins/model/Jenkins/downgrade.jelly | 16 +- .../jenkins/model/Jenkins/manage.jelly | 92 +-- .../RekeySecretAdminMonitor/message.groovy | 27 +- .../message.properties | 8 +- .../UpdateSiteWarningsMonitor/message.groovy | 50 +- .../message.properties | 8 +- .../s2m/AdminCallableMonitor/message.jelly | 12 +- .../s2m/MasterKillSwitchWarning/message.jelly | 12 +- .../message.properties | 2 +- .../message.jelly | 8 +- .../message.properties | 2 +- .../java/hudson/model/ManagementLinkTest.java | 2 +- war/src/main/webapp/bootstrap/config.json | 389 ++++++++++++ .../webapp/bootstrap/css/bootstrap-theme.css | 596 ++++++++++++++++++ .../bootstrap/css/bootstrap-theme.min.css | 14 + .../main/webapp/bootstrap/css/bootstrap.css | 416 ++++++++++++ .../webapp/bootstrap/css/bootstrap.min.css | 14 + war/src/main/webapp/css/style.css | 113 +++- 50 files changed, 1798 insertions(+), 258 deletions(-) create mode 100644 core/src/main/resources/jenkins/management/PluginsLink/info.properties create mode 100755 war/src/main/webapp/bootstrap/config.json create mode 100755 war/src/main/webapp/bootstrap/css/bootstrap-theme.css create mode 100755 war/src/main/webapp/bootstrap/css/bootstrap-theme.min.css create mode 100755 war/src/main/webapp/bootstrap/css/bootstrap.css create mode 100755 war/src/main/webapp/bootstrap/css/bootstrap.min.css diff --git a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly index 1c80b2d66a..5ccac430ec 100644 --- a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.jelly @@ -24,12 +24,12 @@ THE SOFTWARE. -

    - ${%PluginCycles} -
      - -
    • -
      -
    +
    +
    +
    ${%PluginCycles}
    + +
    +
    +
    diff --git a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.properties b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.properties index 7a9748d28f..0bfeb5b97c 100644 --- a/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.properties +++ b/core/src/main/resources/hudson/PluginManager/PluginCycleDependenciesMonitor/message.properties @@ -21,4 +21,4 @@ # THE SOFTWARE. -PluginCycles=The following plugins are deactivated because of cyclic dependencies, most likely you can resolve the issue by updating these to a newer version. \ No newline at end of file +PluginCycles=The following plugins are deactivated because of cyclic dependencies, most likely you can resolve the issue by updating these to a newer version \ No newline at end of file diff --git a/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.jelly b/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.jelly index 66093c4fc0..73182ec483 100644 --- a/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.jelly @@ -24,12 +24,12 @@ THE SOFTWARE. -
    - ${%RequiredPluginUpdates} -
      - -
    • -
      -
    +
    +
    +
    ${%RequiredPluginUpdates}
    + +
    +
    +
    diff --git a/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.properties b/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.properties index 930492c735..b40f9a2956 100644 --- a/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.properties +++ b/core/src/main/resources/hudson/PluginManager/PluginUpdateMonitor/message.properties @@ -21,4 +21,4 @@ # THE SOFTWARE. -RequiredPluginUpdates=The following plugins require an update. \ No newline at end of file +RequiredPluginUpdates=The following plugins require an update \ No newline at end of file diff --git a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly index bbffff4593..a98627489a 100644 --- a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly @@ -1,22 +1,17 @@ -
    -
    -
    - -
    -
    - ${%Dependency errors}: -
      - -
    • ${plugin.longName} v${plugin.version} -
        - -
      • ${d}
      • -
        -
      -
    • -
      -
    -
    +
    diff --git a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.properties b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.properties index cbad3bfe78..555e651d66 100644 --- a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.properties +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.properties @@ -19,4 +19,4 @@ # 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. -Dependency\ errors=There are dependency errors loading some plugins +Dependency\ errors=There are dependency errors loading some plugins. diff --git a/core/src/main/resources/hudson/diagnosis/HudsonHomeDiskUsageMonitor/message.jelly b/core/src/main/resources/hudson/diagnosis/HudsonHomeDiskUsageMonitor/message.jelly index beefff24ce..1ff3673ab6 100644 --- a/core/src/main/resources/hudson/diagnosis/HudsonHomeDiskUsageMonitor/message.jelly +++ b/core/src/main/resources/hudson/diagnosis/HudsonHomeDiskUsageMonitor/message.jelly @@ -24,13 +24,11 @@ THE SOFTWARE. -
    -
    -
    - - -
    - ${%blurb(app.rootDir)} -
    -
    -
    \ No newline at end of file +
    +
    + + + + ${%blurb(app.rootDir)} +
    + diff --git a/core/src/main/resources/hudson/diagnosis/HudsonHomeDiskUsageMonitor/message.properties b/core/src/main/resources/hudson/diagnosis/HudsonHomeDiskUsageMonitor/message.properties index 179c3ad646..b8196734b1 100644 --- a/core/src/main/resources/hudson/diagnosis/HudsonHomeDiskUsageMonitor/message.properties +++ b/core/src/main/resources/hudson/diagnosis/HudsonHomeDiskUsageMonitor/message.properties @@ -1 +1 @@ -blurb=Your Jenkins data directory "{0}" (AKA JENKINS_HOME) is almost full. You should act on it before it gets completely full. \ No newline at end of file +blurb=Your Jenkins data directory {0} (AKA JENKINS_HOME) is almost full. You should act on it before it gets completely full. \ No newline at end of file diff --git a/core/src/main/resources/hudson/diagnosis/NullIdDescriptorMonitor/message.jelly b/core/src/main/resources/hudson/diagnosis/NullIdDescriptorMonitor/message.jelly index d194fc95f7..610d36a6be 100644 --- a/core/src/main/resources/hudson/diagnosis/NullIdDescriptorMonitor/message.jelly +++ b/core/src/main/resources/hudson/diagnosis/NullIdDescriptorMonitor/message.jelly @@ -24,14 +24,12 @@ THE SOFTWARE. -
    - ${%blurb} -
      - -
    • - ${%problem(d, d.displayName, app.pluginManager.whichPlugin(d.getClass()))} -
    • -
      -
    -
    +
    +
    +
    ${%blurb}
    + +
    ${%problem(d, d.displayName, app.pluginManager.whichPlugin(d.getClass()))}
    +
    +
    +
    diff --git a/core/src/main/resources/hudson/diagnosis/NullIdDescriptorMonitor/message.properties b/core/src/main/resources/hudson/diagnosis/NullIdDescriptorMonitor/message.properties index 399938b97a..60fb060dff 100644 --- a/core/src/main/resources/hudson/diagnosis/NullIdDescriptorMonitor/message.properties +++ b/core/src/main/resources/hudson/diagnosis/NullIdDescriptorMonitor/message.properties @@ -1,3 +1,3 @@ blurb=The following extensions have no ID value and therefore likely cause a problem. Please upgrade these plugins if they are not the latest, \ - and if they are the latest, please file a bug so that we can fix them. + and if they are the latest, please file a bug so that we can fix them problem=Descriptor {0} from plugin {2} with display name {1} \ No newline at end of file diff --git a/core/src/main/resources/hudson/diagnosis/OldDataMonitor/message.jelly b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/message.jelly index 89d38c592e..c213f7ac22 100644 --- a/core/src/main/resources/hudson/diagnosis/OldDataMonitor/message.jelly +++ b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/message.jelly @@ -24,13 +24,11 @@ THE SOFTWARE. -
    +
    - ${%You have data stored in an older format and/or unreadable data.} - - + ${%You have data stored in an older format and/or unreadable data.}
    diff --git a/core/src/main/resources/hudson/diagnosis/ReverseProxySetupMonitor/message.jelly b/core/src/main/resources/hudson/diagnosis/ReverseProxySetupMonitor/message.jelly index a92382aa46..e2aee20eff 100644 --- a/core/src/main/resources/hudson/diagnosis/ReverseProxySetupMonitor/message.jelly +++ b/core/src/main/resources/hudson/diagnosis/ReverseProxySetupMonitor/message.jelly @@ -38,11 +38,11 @@ THE SOFTWARE. } ); -