From a1fae1cc1fd2670269e8ee94e89e3c6c0e05b580 Mon Sep 17 00:00:00 2001 From: David Rutqvist Date: Sun, 9 Oct 2016 15:29:03 +0200 Subject: [PATCH 001/863] 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 002/863] 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 003/863] 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 004/863] [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 9585f94cfc13cbc0289d6d7f4bfc9baf60f1333f Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Fri, 8 Dec 2017 14:33:12 -0500 Subject: [PATCH 005/863] [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 065b874a4784c5d0bcfd9731c0ad4139c5e61c78 Mon Sep 17 00:00:00 2001 From: t-hall Date: Thu, 16 Nov 2017 08:18:43 -0800 Subject: [PATCH 006/863] [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 007/863] [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 008/863] [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 b6443aacbbc3fffec1f8f45ce1fb9fb322f5030f Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 13 Dec 2017 16:41:56 -0800 Subject: [PATCH 009/863] [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 010/863] [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 8fa79061bb0beaf93be4e05620463fd363ac5b30 Mon Sep 17 00:00:00 2001 From: Alexander Akbashev Date: Tue, 21 Nov 2017 14:18:01 +0100 Subject: [PATCH 011/863] [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 012/863] 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 013/863] [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 014/863] [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 015/863] 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 016/863] 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 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


    ^v7e>L((hxz&GsuhDi7w} zA~ACk2@cc3@qJbsTzn;&z`*Z@9F0=IxRQiB%fw23;B2*Y} zTy~KD27uhjYbiDE-@vr4lMqP)UMMwJ;HK6ppwk$A}v$`u}r9ovAuH3IPWC(M=c@|+?NU2uM&b_vz9yTOYQ&cX&5TOn(0+HD230`y+bci-|%88IPakuMk7;ML($t+N}& z`vMU$R+eosavyvJ$LNu@Q6u}^LmE&k8q7tD%TLbcR-KX#$VMN^?m+pZfS#pP7(Qoc zt4XwG7Jc2|hTKx1e#byE^6?=+Mgs<@@N!hVr;$Wr#{ocQQOLlKSk$4dA>%ezB~__t z4@_=k=`s)*Df9FNahkp1@!V*pP4q+%?ks|16)l@cS~lj|&#mYBvjgJEdQyRKwai#o zth!3|yi)zaJEm|2A~N$31(BKyZ`3P9We7J4MW$5Bh_Szt^@B)WPl#JjFWQ6qs~5Kf zfHoA$q$(U{l1Yo{!zMIa)_|1csJF~@t0heKl0TA9@0d#2+w?@R<_29et?u;|Ypc2P z5j4onCpH?(S4(x;69{ivK;PKd&1bulABS-^sQo8}uI}efSkA3H9@c9?5hcU);zqFw zG?z`jG^&(UR4<*7^-@_*jXWEKK|xGbNDr2!NYEvK*C8;)znhN1^I$XcDGKgVFy&Q` zuSQ<*g0yOx+acbYWDRL$3|*B{MwV)sOLQ!dC1{6@cH_Gf1RV5J)JAVZo=RUy*j5>w zw1l5?r`9>wiUcIr35kl#i0} z&xjuAzftJ>TVV5_>7+6|rK~EH9IXNagDL|{3sZAF6RRqe1GFLo69dbM)XXe39VL}4 z-8g+Ctpv^Z9?0MNT-wq})p-L00r`Od0dfDkXCz~2XYOif{-2BH=P2^Z4>BO~hRq4Q zgrZZx21XDCLk43CNR-b}f`OHH;#5CKS*gbc?dkUi5A`AJi4)REYAdX3Tzk*D-)uMW z_wMim?EzVtru$byR^eBFub;_x352<#42sC{j0)JCdrIsNjFW<}tfQUIu^0`edE-KL zl*XJo2R_|^Cj-QK+Y;@hoCB*O6Wxqjije09L%gvi7+THoAxK}tqLmOOjsm(QHUu8R zD;t%cI)5tOrrW*Lu$B{9m2VeM7|FLX2qy9ZI9 zF}NGOQthM`p3L8nyg}L6|2Zy|GRO8OD|T(`E08Z*EhTWFOrjBuqms)|%-!+<{VfFv zSxS-W#)rZS7f#DA(a)-dcC{S(NtG~A^j>>M`J8*a2EIG7=dI6Trl_a0C3$`910V#MsB$#SK z6+^yU_%P06VbFv%3gSFi4#Z9EK-AY7TA{w>vbfQ~TnQ6mOz9++GI|EY#N;!P6s!xf ze!+qRs@eA%Yx(xgd^w?_TSa6>`;Nl+dDZ(PRJ5f8FC-~q;%FZRj^{h<<1fSNe1c1Ao=GvP(!BX|~#$X2vK*cqwT zzfJ{{s=eIQ;q)of;iMS*ISf+CF2AiMVf!Vyd04ZCT+SvU6DOwaj^tmW@M3#Xm6__-qKpmFo7YYZ35fVBQ7E())NO>F4 z&6dsC9_y?f>!rdFz(q9oMK#LCL<-RncxGr~_Pv>&Y3luYzDMg5-m)C%Z;i?!tKF-N zvKH;p8;#+zV_M3UAiYu!l_JMvP zHVh}qA>G#~H?EMO8MMqKcY%@394$zf!)4cWN(u^NyjUXmZ1O^?c0~#$4fc_^Sk>*Y7j<%h*{m=gT&Vb0b9hv1nD&~f z$H&+EV|~OXAjagg(ps!}5oCoA6@_Ui@;7%*%L&Q7y=#dC_A5t=%Ef{A1fomo<+gkW zWA`2c4h(!oPaFJO=Pxr3^oFZ_;L3+Vba}Mu+c=}7%6O)tF2@^`HApTF4&eqi`|E+C zQ(7R7DD)$U8qZpiU0PD@o?lpKy{||(yL;J6VYC&OiaZFthc1%=XuWPfnCLXH)hS0A zbu@%s)ho7HRUoy0c_5`pZJF+o|CJ*XNkW3BKO7G`l-_Sb9|e5rx?04LsHCy4d;#KoxPd% z$bA}}t=k3S83}@MYF8YXhOygsgLY4Rw8M&Fx=v~6SMVk)uAjxauW`#Os4s%&989Dp z*^MazUVYp7MpArDwF~=}i!PZP0PaNt801zz>%(&TPE>ps!VGiZ?-qO4f~h(9p#jVN znZs2;9<&DA?zm%7A~y=DcxtAQc8%mPdE31F*;%NJCZ%Gy8g=SRtzvqb<7p{vK5In? zO0*PytCX&I8bjV?3X<6GC1hRVRRNyDi)NkIi$0!Tg<=cR03s$GAWEE8b=mA0#@(~mxlT@0DiL}8+l zRge9~Llj11k4w4DaoX{~a`rNDsZz`EU^J{yk>oKnXD9o;2gP&5WfLP?g5?2u`ui6D z*js=6TWgdp>M6Ti4|x5MP-Gf~vxO=2>n-z|_{O0^iH1x+oKC4m4x+*!a!nM=jT70< zgw)D;2mRP8Y+n5>UWc>Lno0`UFjuWvrrZjHK7H(Uv!LtWzgx``4M-v6MBgOMosKSb&m%gyvuQm+sH z7swtKe#_ooXjlun-OtppP1Bymp>48jMixCO(ktpvE%r<1_RUuXhUyWehz}zc#1}8V zF(_!GoZISIXU!F~(+y%vOgH14vU!Dnh!jzgqgwGpq)g&u11k}wlO;L-B*g(0tp}A% zGkK#8vbt%Un?#7I$2{DZoP%6HaflCJ;Wk$>(;>mLi;jzdbA^(Xh4Nr5%R#ifnNJO< z?qS4Q$|AMjrbX2ub9Fa$atiT$n%Kg6UCKY@$(EA}K1OXmD^86<*kD#1g&HvXbq0p; zqkTN z6UMz818T(8KBdkp+~h1cHD2LbAywfN#cG~6#G42LBnAar=^mGNyU zSRD1Z+g^47dV5*9Ux>31RF?93z6X}z@Om}GO5n)&K)ZvTP7U^`gDLc~cCj5@0oSR_ zfvQf`Yb_}M--6u$dT!8>KddBz{yQ^2{{<@^|F`>D`yW&`GVc|8EGZ)sB0~@%QGIT&ECWaa!Uhu?>2;oVaB4f!m5tX!!k#uBh5hoI*{eDo#GSw*omk)_>8@ zSB+Eq2P!LnP^l~3OBle6x8HK6fwm``cx&fZA#S2nNBLW*foXy5oi;LYrp0PmMdW_~~dmjZIyNcK0T* z7d1%OAjIu25NPnxX;>l*v#BDPihV!ZC$C3%__+r%d<@tMmY?Uh?-O;QpkEZMLVFP; zU5Cz|wE(*or|}7L7K5s)uu;|#R&aEy42a49&c;qx9b>I>fJsN2@A+#f2V4d6qR9hG zy|SmOPyVm499pldP0rtTCFQeVeoiR?9DCc?dh^KUNT%NfnoNSN>_kt#06 z-Q@TA`3$!Qd`*tnZ-fhNqGDpKA+-mQYTTY7fZC>VsB&mbA#r01K3{6PlA)kbzE)V! z@&nzOb&<^_$uTWrX&cPZ4qE$Q@OL?UGBGL31D7Ykj?n z@lLER<4{uFq!drrq2q>=*e;111|JWJ>4LWnH^|zyY4Mx1GoXL7b}%}2@7mX|s@ov8 zh&^5S(ix}pn!|S6HkDTWQoTgA#is2&ft$`Wcl?N&w!6x@)Q+{;R^6dWu>R#7tDSV< zB|?F-@$igtT1X)E(M@|zS53VhQ-lRD{w&UOFrdjU`xX>E4pGdBR9uT?R)-#8Tc?tx z6PbN}p_(j3;)tIlNs_=-qNk`eY)mBsSK_5=%{UmdFNaJ`v9LU=AiiKxhKkJqrq@g0 z_|pvGtBN41&-@t#}z@~k#+dF!lpy(YR+@&2l&X6t#@oCn=gZ6Bh@n3vC& zM!qf4FY7gt5cGu~2z6@WHK`;~_DbLv>J^I?nie@sm;(G5lwsy^`H9RTWZ-w;c7H6b zgZ6M~-dm=Dc;ZL!0vC>V*aZ9Z=gS7TBok;SEOpO#9@0ePSMox8$a>)+F+*yw&hyz~ z2!VbF9p$@h+c2dr7~$WoMD$7^aKNaB5_uP*h( zkiRv%P=E2%Ncb}X`J-+E|7}9?x)3Y#%m$X5BYvTlQA1Qln%g z;`j*FW=QG^Qm61esqtlP0>3;yT;>(u+|F^8sVs$GXU0e!rM|+F#WNcQt}T@a? zW6uD74mrgyU*iFU%Zu(YqkLb2 za+W0*kuR>pYc*&9BS{;Hf?|=QxP!{PyQS1%p|MwXum?QOv9Q?Hc!L-Q#$g){#;kApYdYLB#X0l|A$Hqw``Xd}}AhF3cEi zNFQi3fUg}l0QE`?M+W;F+D7z-^+du@h>zECoG;&qwlC2Y<7Nu%K}$ zd~1Uvi^N3Vl~Jvj2eDU7JSR@>zA!! zq4-^hZD% z7hFZ1kOtx}52Z|;U^#2x#~QlM+AQK+rJO6VcOFbW%_*OS1fwzl1c?QX(%~$K$rqt8 zGpw%WR5+ZcsGFLt@EZ9c<01zCO;siNIn_(Zm>ygnT|Tyod}DTAq$$=yLRdJcGd5>; z)52|&^?)Lm=8`U6`JCM<``?yqws7X zG_#bORf$%3rG7Ce*-kPf+t@Ec;$X<(Echeu#5Ami4KcAm&xLqsSnv34f-*n0Yfk5? zb3dJy;J7oBlx`5hm)m~g0Bn8Uh|-2G(=}&hh6*Z5Wz;X2s#f`wH%{q)ozdleqpUc6?z!Xs#6QZF^{_nvQV& z*P1hoE#Ze#kcJur;THA6LUVZ*Tq5ZwKDwD0E+SFAiI$KaM1Q9>eChpW_oMR$&+CyV zj4&*Kf#ua9_ARMtye_^%GIK?peK@}%*tZ82KPRiEAqieK{x(X9xuN)TU5F6(8wGQ3 zDXn&_hUzjL<$1(HOqg~jR>MW&aYJGQ4dq&{)JQ6?5qB&S`r zZe;#X9UIGej-BcBM4geiAt`wFUXXZ)v&5keNY2WYF=EAxlGgCZ0&xZL5}%O88L~x= z<`YF+w{&s27?Lr>g7|b0E!AWY*@SkVg;o+)Oaop5ks(Rt;M)6$7u0Y=hYBX?rI_zt zc?9epSH);{LVfwH)q03GWh%sPa=**n!qXUI;v5*=_&0j3u_~M$EiQjuLaa}2WiqSm zPL(-PkeJyWjW7mVFUA!p`8ow6kcE|eZ$4B{c?^FGp|E226r74~-`UiW6uDRg1FW ziTYc;kDYfwte+#mOafrWLlC67RM1ZbHmH@tEprcqw15vD5iKiW(#1$Zg`0057!iHI zsCf8ZbZkxHF%sRK+Qeustv4FqW3w}oY3bxX@-C{1rwIBqMf`idUk16Bg7v6bjPZsq zF}IWG0S5r!xs7N|1BV9ZYw)MZIPkwIJSe0LQMw7U^WG6AMxRS{um>XJ5Lh4kJy39K z(-)9;+12v+o5Q35es&i`J9%k+6&QmQM2{h>RC!sOI9bJE+}G^|U%@TvEpCw?mSra> z(n%xdsDW!_>Rog`0B4d@CG5K7xc!Ng+eKTuNE};P;9kAHIDZSzND=xhuGPjmjy>rJ z@&?|V!Y^Dk1L6w!xZTB0k7@PiB(@^1oyNp5s;Yf!sa!+ZEZ2=)h zp050?Z91nIXAh^!Zvp@1bG?dj%c@4nimbmow#t5cWm(k=cIMm{JZ2ldr6scCM#!fh zm^Lfd&|-TIUWiJ-34X(N+qcc6z1~fOT||Xakh=_UH`N@*a}6uQ=clYh z=Z2pG*0(=w+xsrp+d-{1mfk&+b7WZ_umsn7U>oF5uCS=Co})8PO;HjKDbqeTVcLp5 z{D~KZSQfZtAkBd=59LBuF9+E3I)B-DLHynW4Ebc=Rg?5#uAj!bYT=@fL^23mY{A^Y zGaxMie9)`MgiqU-*h{AZ%G?`&PfD+^fCAo6e=>pr@vK>}E9zjxTatXJA{V=Oe$^_Q zxa(FJNDW~5rCblSG@=Mm9s<&)4{_Y}GM_y{u zH=J)?C*Mk;tjtv<-LdaQPb9AFBL?)hNu)@GaEd+Uj^u-%WgrL!oY{Y%vo;=ns^^D5 z>wVf(hVrj5ZlMVPe@iPH4X_0>A@qYc*+}0$qc2qBc{tbwW|ecRo|qblPtxQ?NM~tV zEf5DQ+>-#zG9W|?Bq*4&XE{aFuD-5Mo(@!dz7=5SU}57>p(5;%^pp^@cV=YA?z)x^ z-N0jxUYY%NXI(@8cqr5E7os%uYPMBK?n= z&LGW{0*8cO!d>=RQJz2q?ItK=zAb zxF)x#k0VUP(dCrK?<{*W^OMz^8-5@j0p2jeEye`lc@cDX*{#9m2;>O}Dl(rc>~gGj zdxKEIs#a5#Y*UXFfY9m=(x=y%cx*Gb-YV&MY_@`%3Q4vd#qwu?RohS3l11ns-s@#( z3&~d{TIMc_#}JhrlQEXSRs&2+DgCbNYr6=CAp7qolq{fOURgZJS~pnCT;LGa(@&yn zw;rg_N_kI%g&g8oSaS87`-GzD5|@Q|MegkzdXK8@v$qX(I#K3>E06)2dd3pq&H$Vs zfz`ENfW?wy_}zonQJ+=crWRscResxf6(`!WR~=VZSN?ZvWp&9}yP>c?RYW?tz!xS* zc5Oj+mm%qjPRobq9<1qLEyrc|u5XDwZ&Kw~`L=zn3{jl0Cx)7sBVD5GHw6LW=;-wp zdkrDv9uxJSU!2;QqohCTeID6`KTVdgMrNqEg}dV<07VthC7!;_#i6#G;Ui zbZ3+XPNPVQDmv*UcT4zVvKPa@LmGE$*eM%bRm?G!1f#-V>6VYv&ALxWnVNe z-*o#$6UmY3Q5hhH62*)k6p|k5cM{{o=v8_Nef(u;HQ^nu7on%LqB&DMzJ_V`m}*aN zNOValQ}hWR#_?Z?zK?EEFXxS|obviRB6AR04}Tlv-2>DP>#aHx>)9(A*mj3(V#WlO zl*^3MORqp5e*Ud=>4kS19QX(Dt^WnUnf^84&0R2HnHa9x6u?R_sw%}&z%Z2Eo!1lF zKWmA-lq(>85s@+A!G?exkHrbia!?A`stE zEVmdPF__6$QD1iN|BN6q;3r zw%sU}`U|Ys4_r%@VS;={>yYLWFUnt-6|F)2X=)is0>6hI1NE^!fDFnMG+bPAx86w1`gXTW>Z)?v&dV^#?n{cd zxSIWgac$_`+EI+X@^3oWKzF8vc5Fdzr}r2_7z?L&woRE(s{55LAKwZ;=sPx`TjAEw zj~VRud1znE$-Ao;aM4bJsU6LKlM-{F z=_U6V^$Y4Gd`YPSsh^97L5D);ZoRp0)3AYk^n_pu#m6Z{FabDIH<$nDZAk%FOKeUv&e z-L?CkT|4=G8=fd_4A_v;3YkB8Sv1KRUj8=MUlGY*BoY(|C>sh0NaKHq_&+yHP5#wn z;1-oxdmK^J&yfoc+a}qqVp?!1EeoJ8ifw`XcLykFOQ{&4grCjfMDSUUW4R{G)^6rS z$3X0K5)wLZ7!LbD$h6_0fz*B~%j`{?2KF%`q)r*^zm9!&y?%9Pyu9___yK7Qh@&On znxU!c%r~`Fdf?os+|g~Y?JT;9BHXB3+Aoh}#vouug3{wNOxay~LgwOlG~T$Wl9=>P z8;~mYG33}u)^25KXC2bf!cs253=Y2@l;7=*&`#9j|K=E+Dvf^^YwR5?vb87Q<1#<> z>Zheg_RQgovifEe%4|Joh>Cx^Vr~S5YVoQASJ}7oDcO_1g^daa)(}DLC3^ zA3ms9o|*kOF{29OQ<6mapty9Kwavs8IBZhV9MTn^`f3bA*?ElQLNbLv{Fs7Q^H&@n zD-zB?717Tg7}sUBZpW;G>K3`>Z{lGZ&uI}DbBuCvR4?N*bo(3Fh9TUY2h-zhSaL2Y zCpSKqaLoHuzU}-EL_K&+-dg}a9aR(vPQBi>I14_a=*M0b-N*7Um zPpSxs&^m%T+o2{qmW4oC0`c$EDvB*QWC^o%(A&u!j$5X>(1Xs}5h_M`?uP;4;Al-` zrsX`OLXz(LT@^O;xI^Rk?vjfi^(S_B>9x6a9&t)CNUB1Y*TrKLQA;M*F%Ac@KNB7T zvl#8U(?_C%uvhRnI1CRegWzanzL1i zA7{)VEcg~?-t&*>d~#;Gdo||1RoKNXr|>p_ilS~vp#|j^#LEw!(qX~Vc=6vCFz`R1ONhOxdJ_GE?yJI&F zkz0HS{+z|Qh1JN~#6}uRyQD(8Cr&C?DmJ?-QE7R?qVQ3F13k9oDzTy{TwQj_G|;Lu ze05AtIofVu;lzM%r*H9a(Y-U+luP`G@UQ+4H}(6j;gA12{qcY0|0VzbdnXq)TlGJ# z?@MMcy;wqPP_JRTTu3I+xzzIv;v)#d!WJ-5>frR;Y%^kFkTJc!4*tr$bg-E>N8i_= zHrufKu6Uj!+t+*u%66Q+&F$=bev!5dq%&FxOdTaCW5(!jt?3I-GOLYRB2cWqZ2?Wc-x257uqE{cp;1A{*jm=#B=)!{0 zrvhyPl!0Eq-b*XSff%u6(^uD|uw~nh-s5VvT{0jx%Gvp#vbq#Yv$c8cizs}!owjG0 zmSL$A)1CT&Izt>swDrCEThsr}?$Q>4tq7z~br;TS<>{1Z45F#HA9;tW+%jkTJ!?g+9o zZwy-(X?juFfycu?DlFS-Qn;l>i><+Q2c@5`Ar=u^FU_1$ugG|((W|ClNbyz2JHzl* z`N9kHzD*645}OI{Iz0i=@Zl`ZfK-hg7nN?L&%t;&%H0|)!|qA zAR@KGDY!e0;n`KAY2{M8bQ&`*8G7U`ZxN!#EK+b=E!EdC{V0Mx?p|*0Gkb978rj4V z?W-)VP9Ec1{jD37X*C8HE><}*bN!&eE@JX!VRH0UsIf+5k%i`=;PBJ}escxg^(>gh z3z|`Uux;~_x9;i;+ugB4LVugw82>qe%g*2Wftn5KL((4`t}bC}{#vgKaweKIFW`!p zu{G@Qe70e`euY?avTiQz8T%u`{+8=(Diw0(0A!r~Ofun^Hj$-bqrG?F8?6GLKqZSF z?HA*3)U$JClga}~hTrq6dbx#1e2tJC-H^<`03XYT&-gQI%cv1eZmhCDvw82{%Z4LP z)P2cs`Do_8f$<_3XGB}VFV3Voc9#!QLNihYF!!YG-0;XZ|k(1T}4U6jjtuxkR%>hGs%r;^Zp- z^wK`m%EIQA5e*B{W>U#P*`c$;beXJ~;Ev?Iqp(|8k2^sDI_VXsvnqa=ZF45uB!*KX zK?0{*FTc0Ro}8Drqd##$df%+0h@&WA7;J%p)IwFx01FLzb;w&6z#iNu%jiuEW*4!) z31)Y|4Q&OWhJFxEQjp!F^1$6KLKo%*#;ZbJAz{HJa$T-vE{3T(53OWTDJo6Im1eg} z0vN6mMST8F>Blqe(8xb{l4|NO^1XJmv#e;>7)P@^!N8bkTActaf-hE5R&8l4!f7w2 zm<`sPWj^+uEh*G+AcgSP|P}!`pL$@vgzjqGJ^*|0v*I1XvxbW^P~Z z#P(T);3l$HWgF(_<9PS$>hG&*>RLMN6Dcd>pe-*9@9;-+xNe(a>irZ;Sv$n3v}IQ< zi;f46V!_a<|4gZDB|x zw?!9cCVD1`q4%H?ZohLTpRTsrDF(zbXlN>B`FiMq9?LBaS5>>Sn(c#Ph}F5fLaWP$ zNrj1f&qOdCsolnTdEq?mxf`~0SOIoybRI9lk-(l@CBG?LM`)SFZgZ4;WRzEndoM8G z2C8MG%{C9EwFR$vJ3&@iYw!j-SyrVby3wFL$gsmM!(G%#M(L>tjPi_C*i|QL$YfWn zLk@H>$-RGdF!QoL?+IB6F?|ErPbLW#nRKy*#a9pmoMElTO$*U*?Pm47s{u)&CXCm} z+HBixwOxo&hrLQFVG~t*dS5lc1?&j<2QrD}OCm@#_Lxo7Yjgoysx2R-G1!~wH$5@k5yw_kOtmjK&H~0+03;K{(=CYYru^Hh|xEwFj zeLl_#lCVp@ifS4uGihCU7I7w)SCGQ%9(qlf3wFOeO!$c=f{!g23}*yL`T#S>eAAO7 z76I6v^peS*lqRf8HPj}IQR%GZwt}J=<=kg|7xC3~(pLqy(I;27gLZF| z=V-SzKn|s^N}w9!bngp9k+?_^DCaT2XuT|)M|78-S3{hu-iQw!WqM+@S9D_-FzVat5+TRag0A~fuh1LQd)0_*n5meUQPcW~Up@mx z$gh#pC)5VLALLyAN3;f5dGnFEirJ$iNJtjH2EeAh^uBrWzCqoIOYjWV4y?7ZqX_w% zQsT+E^Ze4LF2{tMeTM8JM-*%`Nc>XfX)*g&4EM3f0ar1T(N zoUYILv99~OXwI)BzlP!&vs|_)Y6a0jcb6gl*%L0|i0|8YB}J2Yx2;gzzpxm(R6|T>a|!U8sxc zlF_Le`B;rDvk6;wk3ssCgu2T?e=L;VEnb*eB;M@L88(DsZ1N;5S73m45^Ui@@quM) z_z!OZ_~WXU8T{Ro(m~zLOHp8GNe1D-QKknxUxtMfW$q*|r7<02II@h@U*gde!rc@=)Tk;g2`Qz1@S+Ne3Clu}d;)d! z-H=rTgpa9WlvB!Cq;R)rC*{2SN{YQe09w(a^FeSLvg+l1%(3O&03U(ZY0R_Dc#~)r z=!pHFbsx4$RV@qTCVKe$GK^kOw1x?)Q;N>v6^@rryY(etStBI{=S;N{IA=Sqjd^LY_M&o9{f#4wGN_;OY3Noh?zsl1v6 z0wc}yvkXJi@g4!Us@6A`C(aXY{k%nU-}jSW^uXC8Ubd%0A~2TOX$v8!iDM-U5$dQi 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 OfXNR-ju5&-<x%8#`G#xX?M9 z8oN4Ky8NZLaIpN}P_+Mm-2Xq+rCYh0QD(lT!*;9n|IM48y?;3w6UTWOG9}bL|44IY zrOK>!k^@;7p-}sO5kQ#bp#6W5<9}1C%OeSEinB3$27f~Yf%bpGE@146t${#*G(drY z^1tL|)D+~DWK=Xm{4a*D0G5Le72vmKS}b7~R2yDjj3I`}6{VwI^|&D+kUJcs0>1Xc+_6@@U- z%L1BxA}V#=aSL!bs;+HVVb~y?luM+qm-`MLbUYcwJfF!wdkb6@vsM$3Ly4cKNqMeroocPNwSReXc2yZ@Vdu;q*LqFB>z;pu29bv7aWDY8+C=4&^sz^63XiFd% zY&kuhUyf&Ta|{^oLqPp&e0>e@+}9YouV67^(i34n!tP59xWd+39I#yj6_UL|e)R_k ze`s743x31&e)VBa{S??z#eRi1er27yTZv2`K|nOFKtYgx`y`wxyu8@uwakUN|B!eg zYKd`)L2%Ism!UG8fq{IptPNhYUeagXuxSKlVLCa0iMHzWeYBj(hx!6aNQa8(q@pH5}rH3{O`*OK(f-_azeH zmn=;aWNls4_e%=V`;*|TV{2?+=kk3h{`lL4;n;dw`!gnX!C!Mmmq&~WfelbNn|qot)4RBN?(4+ZT!10jXs20CXL(5nZ*JK4({ zR6AKN*>0|{CqDQQa1`>}IvrDS7kw7H71suTyh%WD2jh`a*z)>?LQmR$MBI91!PNH* zq0+8U-{iHq!%ciU=?5TP>ykg1i?V@^8rVU+U04!n9eYa*q$x2kx_&AI^=!UuQ$828 z^qFyP>p8Pq9P;p92s6K5Z={*IvnLBNK5kbgYTj(y+E|O)$KJeMxw=53dQ!&Nn7H!8 zdtdM3`Qg*_F0Xt}2==?olH)Dv0QdPQ0Qb8TOmnJOlhkR{mcV)@h2;j+Lyf+B@&Fs=a z!+c9}J{!_&mtPBToAzZSr{jjxOCFGjrdeRnqK9+r(`sWU#{~?eGFN~;`_Gof`tGWc z*bIA9NS-W=OBN9gPYnSx32_;l4vA)LCAg&u95ZtB6GJ#6Xjb;PJ|xi$=P@>T?ePZ% zh!y=svVxIW3I_osgo@EjfNx1*_Hk_WHb(d>^fV`%Ika(OG#Chtd(NiB{;Sb#8gW}k zr-@dXth3LaoND7sg8q~c0ODNrWGjnkPj5CGh;zB7EoKE-EWioec21Bakc!eH4faN! zsc(_5nn&+B<#RG0U;Vf<{_pF zbRJzl2x0W3FGsG-hD&oC5Fs2baiRy}*F&E`B>8QXKP3A(D58oOwWGE39GNx^(w+dx z=^DrSy;xw>--a3)`)Kg*+Ic07RT8>F(h$jjT4pHg0+{ z6;LmK^9J(Zzwjf(_WcO#z*E;i1gn(_Y;t)uKy2W~zsj^&5wRD?@G`GT3IS-!ao5g7 z-!Kb!xw^18Gg+@K%20GDZHQE*gREsV{O5EobJJ2rtbzHhT~Z~)W5)&)te8ZFYNda4 zbL)RGx^!|7EK(XSI*i=i3O#f}Xoh45lp@}IgITRKE!-X)Oq5LCSp(MWW$Q0j8MWiOi^Ze?mGQ zeBf<&zd%7HA23Y*ibSC4@H)Gs5X ztAq)vh$dvWXvk-=sLS845WC^2IOkR-b;&QXU%5eW!H;HiIyqjWpnjOt!N9%d3`n74{H@LxoM5_#CM>r z;P(`sT!M<@q6JZ6FQ8=B>7f-3w_qKXDc>biVzo3-h-OBMU9GQ5?!0%WS>@HAmHQ&z zSxNk#v+$m?`o)%FgBt1r&LKmv_Fe4^q8e#O((zNKANjlEZYoq(r$UcMwxId@Vl%6n zS=R%>Rc2cPTDu3bDIltVGEW0T?5flwB~1e`rBLX^v#vy;-;0G30}PZZmD%98D-^g| zTa+3kTvCmg)F6e?boV-8x^6vr_aLsel4(~xhtnoOZhfFbfj?<%6Hd~6OtjBws=Sn` z4|bemr2_$azB!h*ynylk;)(Gzqlr`H;WORcASQGd?fhK((6qYn#AuKy z;)65w{`S{{uuc*WIXpWDtlbh_6l8hM z#k0rxzmTy;7G@Cd?$H}cqB#dm*Is+_hHqI6{#x#Uj$On^xrQ6*CQ;7I1}99!1?D8< z63?QgmgUE7dMEm)+!dytzD{LV+4|#A#})z<@BOsoj2EG{YBC2lF4x&o;y!l44NVji3+E)N!(3CMlKTV5A3do;Vj zm^VMa>`z=W2Nvi1RZZfaQ-ceb?;5Ntjr|Q^d+d&UQc6@XHm|CE!+JR~RMcP{mQM<< zS6DL$vGb7_E1T>Y}`tNi{I3*>~p z@Yf+4%&V}YjL}SrE6+MXr-KpO+|6ePYoKuyE(m`}j{~qKi^BDvFa{UDeXGMA>wX+X z9uyY$?;Ia^{|Cci_Bd8)L~RBadZ<%urijI(zB@W ztL#{~b?^k!+8td7qV@j6Pz$%i@8vr^X$Q1BeIzS;r0Q<9I;S~KcFz)OjXF{h2_w2y zaxp8gtG;E$Rc{s1V-3Zj)iK8OUWz24^`hE zxD9BpUyn~3s=!fKf|cJbN~?$i*4$V$;H^CgR%9T?WggY-nNACiL!RMp*{F0NjpLoB zxjE8Sxu29Pi1~ctuozG2QN!iTJpEVYC5*4HSJb1@)4Qot6G~3yjmz9Gk*1Wgy8}Sw z1RwLl*d$G|(OiK$mpaCPrR4@?tcLFl0>x-&G$emV&ivxWbFvO~$XtMTztyeO6dgQa z=JC%@a(LY0xu(np@bWmkxs=%KTlDm%5e;X%`AUmN@FDx)F`|KPH|8eMPM|`mM$_t5 zUOdDC6Of5SG)Z}n&JW5ft?)&Lc3f_U+EV4LI6bnT#A$8dgbK3LR_pK6!M^fHPA`2* z6FzQ7VQoy>H)dUn>U6uCUQi$e^r>THL%vR|Ihxrlb$r7halspRDxV8%hwWEldO_n_21EJUx4 z2^Q!C4zqftgQs@N4d5zd^jaG!{8v*P?D;Hz2}d<|KVr2eK8oLwj`PWA!ajvcc@~Y# z#Dx)rPzB-F{9%I!nU zKJXK`BRDjjxX%}k%sjGJ$Lx%zY2Iex zK9n6BVt`@S#vs(QW*$y~Nhwvlx)pkd*PdyCA-7gjM_upH!g+QZFRQv*Bz(vqHW;+=PZZ{E; z)iY)!mirLEmmFM<#bwI|VZxF9K$_!zFC02IC2!Pg7^s{jpA1qfcccTcsr~$n=QwP0 zX7P~S%+=)lJ3o^J)CMxfhB^<*+C;uEn&8anG1**3x2qmJQhVy`a03rwJga{50V)!0z1;G;0O2+dY+x*ipP2l zMln)?gah99`MjKB3bDBggvK&F@xX$#1(i^y(CS!t6Zm|b6U*2SPV%ZUQj!;?lvnFH zIgo_W%(8Y<1;_c4lM=;AkwUVGz!P|N2E3)e)3PsW z@gy2b3@mt17^qElQ^hT*ICu1n|06j!kn)WS^xQUK>7dqc5UBoPf$_6Zt&F0 zsY)Vo!@v2Yu(93;&eUH7xdnX*;A()Mgxo|Nxuxb@b_$_#9=LZl%S6J3A z!<-B6#!PE{%e@9sI`L1L(F@Z3a!$>XhaY-Xc$NZY)s}q-1bxdmo8_wQ6aA+U+?{pJFmTqJ~X z{gjsVrcpF}!LwT4=5}-lc~F-gs|m~xNkvM%KFJ-!;~G|o0vrjUv7!>xUUNv-sI>8Z&Z1Q41LQ!1hs zGA+1vu+(oDRjma_%;WunxKV`@xgU$+4OrZ4MgRr3OiZM8pFNoBwstxheSEBjDmiG0 z%lH4|xf`<>lFxhqU69ydqb8lNDQ}p9k^ybSFLUW?EWztN-F;q$vybFy zoHXn588;)1??*N?-0;!vt5E9#jA-k%l}0w?NPGkq#fjdVbxjvX%^??w={u1J{e$ql z^l^gBfq&sk!dVg~n5`w%U14IOr45fV@bVlMom%QNTl#0zP>q<~7i+ZQ#L7T(9E;5M z(dfcE8l;f;Y#`(`8d!`;LhD)kw-v!h@I#6J-qT$vz0Sjo7_5HZ(CJ%4!-du%nN@$= zgjNLRqF?Q)WZ~;Zr}X6LL_zPjO@=V6^zrTSk0?E|?K6|^={?II94Mbn?~J;V#n1oV zJ_-zuV;jy7Reaz1%+Tfx%K2^@9AsHcKqtZ}0jH;q!l;A9_MyGg0}PeYoLJl3G{8Xq zH6TF!6a4wGN!rqtcOJX#7ILLv%dlNKFvVpbF)@NM>QvEz1Q`mjvc&|RpZ#dN9nFWi__a0h* z<>ENQeEMpK+!XKj?shamjCz_L*60z0sCE(K^=?0}fM$+?8;*bfZMeY)J;`CD?QJ^FI%{pM4vGWr=N;?p9C}aafn?*B=1PdOwjoF0` z?2S33Vxxvk*{2bW#3A-U<@L@bHr6*Q(JKx=xN}*XGS7>vYqysjp9EV`mvE{^=Q8@p zWy%|Zi?SF7lyCJS;*l6GQ5-P``%CP&((wrZ7V3N!W*0Q8^S?MJxs|7Dpc~0KGpJF` zTJ89sGhH1yuu?KtoGi(1vQ8jd*x><*E?bl`#PnI~vjyCwCh_Ng2BPBIkjootO#TJv zHb_mJC4PEoOOza?Qcw2SPrw#GCbKl5OHm)6OJc=XZfruqY5fgfUeVfHsO>VjdN6gy z*obTO2|4>P* z&C!NW_R3Xhz8QNZwF^$Fmhpbc%H+jX!>7N0mUmFi~Q0FT4K=~xj1XNxqCDH`G zT&V>GN?rMuX?$j(&XO#hBIM*QRotdYn3=r7P{Uc7n^|Q9M_Ox~u~H-c0MaaGrLn{X zre3%js2}y8cRc7q4BPskR*$E_QLN}0wp9|*`VcE&h6&NB<5+PMRWgGfIl_*>RbX)e z3xh3T<(w@{m3$N&vIgpK$vhL~b2xzMHp=uDFc5X>cwYvKEK+9zo3S8Ll7$jN;=Wl; z0q%|u1zB~9gQDW}F)&QQsX}?6EhJ9wmkYU=HTgRpG`+eiLJB3 z-dx~OeUkD}?-aa|QZxcbE_&|B{zM4@8)93{3Ef=kuRuQFvVfUtBBc>J`qm&{E6pY$ zMsLXTX)-=SfB?l*U_n)G8OD)3h4p^g8*C3kX;%yhD?<*$BLAF+z+VE)44xF!Zy$N! z|NMH}<;RDLwzTDroJdSQi!!ct1M*WKgGbmh7_Zs_#B8Ae5^@ibeYD~IZAEK5O%K$~ zs(98sM?zsTl~<`8rmb(>2wYK;lhe=?w;nl{uhb-)44BXZsQS!wj4*&}3v zjo^FNc$_xU;{}b@7*$C+6tBb*8;s#vbc%6jYp--Ssm8LeOlzjl6D(v|xB%~l!Kenj zb`AbZ$J*%Fc1#bahyd}3oHC6Y+;nd&#O@JNNF`(TI{%Bw-+rdQl%O1jyE4X<KCd%7O3@pIWIVIhdYaK_phH8 zT+G;f`!B8PT$q~A_AB*CT(Z@%phi}JV?;G{phoCLU;~e0es1i&5<)z z;gWo}#W+q`rwz3t>hjvbdB1&~zC|35m5CZ#X8oY{X@waBI)H<(F{wef2oap60MtSF}Yn?rOyht3SOCc6KCjTUbf?}HX=WGeZa3<@#Ee%8aRbE`24(fGD}c1L-P3J?vjsiCeQo4f3{ep;sR>c|orPWIr`iJM5E|6`Td`}&T^YLJZn3iJj)%_(n|0k`5teWcV{1Q56Nj9K`lWJd?^o zL@^Uo>n$tV<@OnRkoU1nBomZXUQQ1a^jZXx9bWYIB(|(1D@`2RSO)}<05!*LQra*x zYsEJhmka@3@KDwx*D9VBQT8!3Y-LnGRw8|jYTsGFVDMbHz4E@xGaQepXG{{~-ddN& z$)`Y|VIQZ$uD+gvO9ic1q9#YGxK5i@63T}a9#NS37THX2p=31hRur$VH|9p-W|Yln zK7!;Mnn}i$EA@`gKwDVAU1xX}wivJiqfIbGqEp=r}x9gsHkR(Ie_L> z^3)C0z)EfJ&%?n!qlaP(_(NfllvHFBRS1MlrLq%p8Sl2)9K+;^5KhcjX!qHGMN$(S zE?mnUG!)lX90q7Su}OnI^l|5zS$BGiX@2?P?ZWKi^vUtZ!6HqnKR8du#0fj(vqDR; z`P3Y;5}{FlMFbYS#bzfSW@BUwez~K}M-FLShwf{PrxHnv7e)r^Ucx1{J&RkoapIzX zPq|rDJt@h`Su|A=@p~Ds_2y1y5$4%vD+sFNb;K4tIN7JR)}7_f_5OGzLxY>18qTq~qXIbr#tIFm;uglAF+lu8=SIE zl%pB&dTgU6{$**mO9-o|QPi*LZO5EusK+>RGC2vyXM#>ba zHGAO-#xTk*qC2Aa=$vrYf*NSReu`3-q)aA8`q0S)yp&{O4MDNIosf3H-UzsKP6ErF z$*;9G!Aw#b&WA{vijUxsHl8VKlr)+m?x9g85^dCvgO!VG?(V@~iWcp=<-cuKM@`K- zjNbOwN8a$4lvGNsi8>#$lKyI0fg`Pma^9(cP#09uHCrUO)IM$IMk}<^3aqLUBw*-B zyJNj{5J_Km{hs1v;CY!@bxtEP46AlbmSiSLdcYz!3G=>I%873hpEKUIz2#Kly{1&^ zxZx@8U>A|Rq2WHFq3ga;_1`QZ5+`g8m2m$coTg{q4thN% z>;YlXF#+Lwq;M6p-WZ{#8lk|jd=|2HZj$)lSoY|J_sR~5Op>y>c7RW26Do5OVac;M68D4C zHhI-fz!o@{*k`jg*m+vEWQS@i?nZtCaxwlL3Z(hE+qE2}m&}xjoJr@JR?7m%>A7Nj zCae8`*6$;W=G(325DYTm3s=T0zdy2woi2iWLaI37Ut7*^N|R}}h@1EKF{e5+3o;J5 zdEw-B`D}l>E=RFONNT8zbam+lNg-I0=l=dmqseL9rR{y-DO`K~7}6k(fjflyOW-2? z?_1rX!-0PoLpC_UyW+TlC?t~F{IgXSM*OzHTN~Fdfeunxmfy0w}rxt>Wk=+Q@Y37pf1(MXr+dUg@id!4X3YeM?UaYRvf3O!km z1lMKewH_@PfzfzrtQIn48}vH^u0u0{G4%_vn&1#B#jLAkDf4>Triihni8#-7(LSo^ zy6;OUxykRsQfLV&Vk*h*EKgup2B*Bm&Skp_1MUfQPOAEUN*M&P4KT=fr#j|}_q+0a zF^YW1-l5`bFbrRPL#uI&Uc=N_lWDy~zvT2ey;je_f`rOYHpVXIGRBB6^)WRIE?IEy z5L!SZ5LP5H9EznVL8}fk1z8e^k2+`W;Taxew_t2h*rH*HBtgpNAnEe#o39 zbGmHOe_=q8$=>FK%4s|O&fC0G^KRQbHnrWGs#3)=bLLo7TlAaA>I%)C&~9Z&Robt_ zT;KL7+q#9wv?)A$JJ`_^mljXp+%l0p1~Z* zy^F#BdiF`y(@lwetUkf#aiz+lVz6Z8h0$5H=QSpxp(}K0;H=KIMoR8VABU^<<*Zy7 zcI=MKn0{YmfH%Gv@nx|N&BXVm#yfQ=<^N&-moqW_GP1#|bYJH{gd2h{V5Z88xsQ z);DvmMNU{1dN*Bn&6w%6+$p|0S!*Y5G*bW5F1Xd!?TY5F)+2&?^M-9K@~XDP6=pR2 z;dbMJx0mdoU9gcR80(ai))3K1%IPgmK}-+WH0fl&4wV-qm^1-27|C85Y1iayWT@XB zL`6s`0b9t~Y!Hnt`EG0)+vw=N74iMmMYKRa_wXbnL|LWa=yySLx z{$>77yn_@H75Z+v2bM5xgwLHrMmDWo9KDYv&fMzK$eyrqhI-$l4Gkeq>~LA2XYhc3 z41@gHk!|ow_BVJxf)Nj!#8u{xQbXfFgzyU|m#m1s5$KFeFpfx{{T1S)%;S8#x&^Ia zz>HWCMa!9eBWKVTH&j_kwJhj&QuHbRgLU=ikH5pE7j&H(c_?R%aG0#~La&eIyQy0jV@Ia>i-ER}!)Pl4*RzoKx#*T}PSK|P@& z>FUp#UV&1@{Bawg0KOYd&`T9#45od+*CyMjDZS5OJoEYrMc*qcQFM&7L;y`j$}vvT ztc7tNmKByvP(G!$M8iGV<)b?d27GqZr9Soj`-^7^9Wb6)Oq3I19FNAKegmLmoJ6Jm zx+bS#I!zD7s>+@~SuXxpyME7^`{xZm7+8pVYfu!faZ=6bjdzvbl!+Z&dB;^YWDCCx zalj^ogbz0+_ouOs=GC?{EM9tQthlf!PjDJ-Y<)d(%+_tgij1A@@6$Ww20ttmf}Ly| z`W?*@#XAqoDDMW@yJf=OdM7p8EbRdT3|A_>W6fIM5gFoa%oU*&mWGWJ=7dcr*FjNEscr{`$4fr&j_gKoC4dnnk zmC-*>iH6Uw@r8oHc7T=R>+?LbOh2@HtK4qdQN?{z#r0kBB0MG^M&!k9eKs zj4b>t0eKzC<@hQr-m$H5E~Ny;I;jirxNEU@_!v0oys+U%9Xoe zyc>LQyVWKBR%rm!2Nmh;ur&*$r#yMaO~ug$&4liz0w&%5jp+yc9pUr=Fc|=eEo&r< zMbY2lm94TB91|0(D_FowsRQcdO=|^a>kM6ob)CVEi!z$QXHUEaDy+%jZmOdhEms#p zhlLuB1d@T!9kHb{tkh*6Edg({0VR>i;_lN#*T}`!lFdsIEV8bZBP}TCQZ-U^R7{uM z|1NmMdKf4M4!!f(WngcK3XLbY8Bd4QXEE{;X2r)Ezsl=HLvahoGwN;u)3-)*7%M~F zAWuhY+GR{{$kTK?G$U}v%X2WRB!*iu4OoA;l*vooj*V=K7f#p=k93_EH_`8S!7JCe z58i&9HsB?c?p&ILZI7XjJc#}%v^Jg)zrm~mNz7*nVshKBi5nn1tFmVZcA1e@?ZHh& zFeFcHBtil>%bL#lPx`h!CrHWy{RzfBiErJE10E_Zfr*jI z1I$=uZcW96S+IPsR?z35TSh~r&`uL)oQ8QLe|!=aB<)wRtMt*p%dDUim; znFID~vnmuxr=v=2+#^DUvlyPw^lV~^6k&5vzOft0LLIy8)*=)@=7_%W+iWKM%nFtE z@ZtTm_Eg1(>mUvXCr!U{sLI%X#g6@ovR_BvjvBtHdGwL6H&lMRd<}Sm_@#wY-9F)5 z((DDHU53ic(~ zDTgnZSR!!rBli&T7dRL!6cXfBJmRNTj;24^Sfhd#eauNYcg{O0t1nl714iJIOYnGr z9I-IH!~BAz*~D0oyrC4sH8^XqHx{K)xyg>4zD~f&*T37sW@9(5maTK@R|9^hf9_dQpy( zaH`dw+}Te0!L{+D%WM!4FVUg##a0{zUlzo`T69jf0(On{797z z84_rH&MHjGrW(S4d^k7yy;_YFRmv9Sf?!mT+-L|-E?3a(+yrB!FyB-hNxFy{+f;5Z zTk5#9REWDQgZ1h3whgAU-h=^vd$54-`}JD=OorZ@Vi5Wh5rEYBwu3FpvIrnAr}t zLLJXP^p@z%ZR$&0k;q4YgooIw_TZD-8Jq8y3I;?|* zAxn1Ndq8~S&jhh>V3A|A-kZ0hw?U(&N)PE+vCX|I<|c}8a#}3_@;l<%!~w>%+@XcG z!pz&`9id|!lq84p+tzY@YJPf|{y;iCpr8Z7cEB5Era#WYp1aUE+ik-~AHM)nytMvS2etWymY8)ItEtez-B;+)GPs<$0$7Ncr~0twAuQx^BG6oj0x->C$Yah z@DP7)=C07)QSYwLRp-T1bL7pMvKImoNa2kC^7D?novEROiL;5Vfv}~uo%3zMuWCP! zyeKh52x(Frt#~SXAo8ZfB(!*WgA!X)z(E3WX-~v;vsxCgO?!9S6g+O)K^NzklWq$y zE_P_^Xc{xMe%l$7?&RyklXnyF0bY}7UuThLo;Nhd zcmFy1BQamq{QfCjgZ`O)$)=jby4MK+m|uxmt3-xHNpM|Hii$!QL$eDKmMHh$aR%b%k=N zI}$z`Gk|NP(oo)tLH3#kTq{_w70eDvYcEgp6tNo8|MmftMF{@Iz^a8RXUA-`(6WPg zO&mZ$n8>gF8KnTjQbZl|8kiNaPH?Xy4RjyK(Wv! z@KSR^6I?R**U|!~f14~b-o;}IKra%i;K}X0;n}CqhXAbzJRlFt5%&CcgLj_LuI=id z2@8acY&H<2b<$kUHB%}*j~JlYStp{PpPS`+nE7T0pK{!0{v1L8`op({yYUCz{roYj z9pCzvUXGDBY!rLrz`Ue4OIq^?AUD<}2(mU5fRy2p734@3{obO;6(mWZS!L=Sn#DVp zhoO!bS`TSgUV(+|Dr?O)>MpC<< zDjp$HM{`tJVCN4dIuHK&*B_=px{Ul0j%gHEE@b0d$3y>Z4Pjcc2A-rU>Y5{@XYN(c zg7;yOMG~8E{7kdxh+kHXD>zCd+a}b(${n_bj-7dnKCCiovpY zgmXQ(O|1Vb$5j#1@_RE$ydy(Ns|{7VJ8@_e)Nm7Q+ zT`j~b1sl?R{{@^py;2Uw>7hSJV^yJ#u1s8LQSNe$_EZpz&%S%D{s0}uyebnLeu0{> zr=X)fxJ=BA%e#nr6&G5wb+F2^;D8r-Psg{j?Vl&m<8@(4xEA&9I-M5WvfeeSMRukV zrTIOlr<(sR=_+`;EOc8>9MSHXUYuM_u{pfc{4%!GAh-~LuzeYv2@rA+Lw(1?Dl;AX z0`)jR!3cezfh9vf8s!qsSh{sc*fHmANwGqr?T9Km!|I{ej-Klm;G6FcCHWSOmwp?T zo%n#LVml6vlN>b~I=M~lTMy-SeMJF9yfu}wskBXqP|bp8MT-JN<5CN9JVlvy(maEV zwF)LXiU;|~s@y6vDn|N6d;^3bS00j%Nv?||1r0hN8in0h+wjSNw_@ptR|t*q3F4W? z(0if^TQYfZ3tCNJ^;*!GuwMuEQA~dHj&nwwLN%UfJX57wWULV##RR*>w1#@gK*lnW zKzwQZ5yAb8fS*qfDCrUD(GUb{~ z_qx}-Wjmp@o}-Kz+mS{r|VZ||>m;#WHn$R}OnslH1`23eo zEP@uEu4~_WB)A<@;AV42Fmh;IGX(F*JYld3Z1IoAtuT-CeOxb>a5$@A^OAYRJNr(j z4Py-*h-#V&%cKuFFUEK9ia&-N;ZHS@9TK0)X`{LmJ<2EA2}a-zEAA7xaXc-YaP?Wo zU~#H>djS#pF9QJyj1pKa$I+vMuf3d-V~pMX-Azm^0!Wy(644Q%|7sS1Ym3dU~Pf6<7;?fud;3?&J1JDL^Tgl+*?vkB~#b=+i-UQ^E-1X{V@sy)~2& zQWe4qbPSDk=8n5NFi;6B#i|&e$~V}Gp{BBwYWj#pqiSA$s!esfsD#08UN!;gPoGc% z%vwA1MA~8m&{vFMt3KMiWzcv{TR{AJM#e3@7P+D8)Kxqj0KD*Ux43J9^ zqlx#xUvq~GwW;i6t*Ly_1avD{dGx&TJk7@Lk6-Mq|J!yN!^f-uk5yCNB0tph5a)IP zw|5clBh`AmqFYe1e9wt^>Y?U@a#QMruN}m=VuVpLcf;7YWVlLdh@Lu&H9lga6c$_% zX&ZMsIDiefWcg}E(lr-pvTq)0vex8ZkU+B87Jlp2Jk6;qLR2BKI;wt%W`eB^X*@HJ z4PyoXf>V|#_^f#N7cZ+w(Kcpych^ZVeh|lOrFb$Vk0%&EZr2MjKhJ0>9$18Ge(vx~W zg94x7NlBF#5bcdWVTeDbC45yFdhFsKi8G(8>b7O9`~9$j5o1@bxF=+P6Ur$2IG@qcp@=CwEaZ)Z?p_LLYU?mjK=gBCWs?yb9$LrwZClt7sO z3mHJ>zZmoIrRsHvv-_@_85JvfJUWBzO9I5o9yXp4w6B2Hl}`$mha!$vJV{O@U<=?1 z;1{0gnr%Kq$>R@U*|u79_?OY?BhZ?wzyJxdK53+9nB4K{c->~K)3=x~X?#P_HK7n z*14O2v#>u2w7rOJ!K4F;pjUIJk8-SN99>@l3YRZ-tD?WYI?~d$jwUACj&={7z*6&o zKUJDGGg+Y9GqQ7IJ9S0MB(G*>J9z9O?{AA(CeU}uSu1#$?bn%!vyRElZmExWFh`xp zB@9KF3sns?cBx`!O=T!DJD~7HQM;IgBj#w;0Go*+p$UOTow7egv}!1WDjN7q?&jjB<_fcaeyza_D~1+h6el?v+hplcwb-zxDnU+RU03RoAsONGe$AZS^a@NVp#^w+B12_r%^$}aPpR_RcLH?lgH(u{yv~Ds{o1* zAOOB_7h3XNcOp*t(eCJjJUQr9a944dIBPA(YDv!GG1mGPy;O`J)wS9b>M^&~uXZtS zgXCz(kz5fgX7S~X5kBo}V@(zO^6DLIiyjIpa-X2-7-e&p7uS)(h)vu%qT#HLMXG2w zhI@^E__y{LyVmUH-(;JTY7EBy&lL%0QUAv=7G+CL-F2_s?Y67dJ=eP`$L-2D!?tZR zzs@&q|Kojc%oAsFBp))T)a5%G@*&E|KRLU4eov4l68o~usASUG0%2zHnv{>ErihPv z1`P7Q;7SG$Q^^zK+vs4$p%~sH`Zh=6j zOfN#4f=>~kj@@JEO1KCdRcy;q>ndLwy$4xbU^ixv+4(H!@wDiVKJB;{=hk4~zdl>$ z(_g)u7~Ej@4^Ff1Us-%{VY9#8as|EKf0M-TN}XE$TEGR4wqbfKje13Fk>`)0~nPd%VPqLqx7If)(^#dKZF_a0@TyN#2usREb8NMOH!+;BG zg6>;_kWoSlv6nzZy(qg&JZ}9UqvxwS`E1S)=|>>{_JSO{`#*QC9}xml6EM9ZxNV78 zc7rWIFmnK6TqX9!Lv%@jN8;2RR*NN#m?7Garlo_4Dp>cDk-zXv1*DLX{YwVN;gj8j zUH>(I5ZEotJN+;DpZyj7Yk%D{#XsZB@OXL(qqBL1x3}(G>t3h-yoCF#Mti{__TMSK z-+RsdFHIPuwXCVdUwlx=J7oIuQWmSvIyW)#eARPdgo z{Bwfv@*;0$v4hVU2NQm4S<#@{on7kK$~KLIsQ(&%u?5J0mz+2v?w?7?_}Ar8n6U6p z1%O@J7XwnsW>;m$9?#FLhpfhliktxu=G`LHzW|3cy5mN8b}*Qg`?Iu2qbEp3IOmH1 zl3Q7em&ALL;>{D^Y;^bL`;irG&7;#bH>oM8<}0I!z1ZZ|DAT=*)$H8_8f|G3TxQC6 zyi}wLS5uzxCCu9Xbx(G8Mlb=$1V?llp{kamX^LP^t)#Y+s0MMSJwQ zk4^SvZ#>`8&_e~X-(6#wA@Nj@QDaI&9q5o zbXt-gxW0Y-bA+xBg)*hrE#+*FmciectiY+{+W;IO>Xoz4j{Rnh2r&R!$TH)9{;`n` z^~LWXaDDj8NPN<0!f3r#!~OgrN&xH^mCDK3Ahxx2$C1carB2!GY-R_&$-Y8y#p@AQdXTbsR^*$4l4* zY@UN1YXRw@;q#E8F*0l3nzvPvY+l!nfwGDAdx-P|PD)0SpLtq39loLcG_llZxI0N# zQE1(m>(?bC5O!&xVn~wUld&3(eRM*&eivPZ}7vG!*kdRfS=!*%WfU-kuXe7;}t_SY&q6`W*e@Y`P5hIpHEsfVCNUB9-kaq-^EqFck0Cf%3dm zI;9Qec_GOclY!ShPd4Y6PRUBQXt?G$MHR{#pChyJ5+!Chr~&h2ehNq^p#+wkJ}E2r z2|#%J{M6Z#w;0~Mq<7Y+rd)l>a7LiVp#QBZ-+2%lccYQb;dGuVRC6%2FD&875Nfl@ z{gw-?IGnv>$mp@VU%#R<@q8VO?INe^o6|N;jFML5}&$?JRjN+z0P~Vs>r26*BLcV!teYz^O6oX<>E+UUDJgC(sHQ7lkEAu+wMRc1OxJw_1 zEExQ$N#$OX{mcM;c>9%FAH=tLXU+|C!q6Z>es+g9K{w{#$;OhYX^iII5$&HN*Qjol779EaT!u9vyfu37ICsh@$?d6 zCb?xd(LM*uQBRd;?a|rhY2ow{GKS-(JYK>Qphqc~#y&0O%;F92vO40n3%T_4b~U)A z;0lsaCSFybkqQSlCkXcfs586k(|9f{*GPc$Bwly??GUPOBn8B4M+7NCB0Ld~TrvyE-6J+700I)HMY!!swT;;QS^RDux zej7Y_6R*Gd6rua!&Vc%B3D$;0_>HP1f~}O$J}Mu9z1P@uRg{qF(e@-lS;)=o=kPPwBW%^0O#% zW4kurls%oEsO8tc1hIDkYGkzsK>D}Vy+Z;8@+UUwm2y%;#HMMIGK%<>#DllUPuQ3} z(A7@S9ehKxD=7%4NJa=nvWxhl~s@9g&~2JT*MYuVD5 zth&Z%%Mm=uFk!P|$oxUP21$FfKIN0!w!SNZ>fJtPfhRRAe+H3)-9%Fh zM_0m#DGO3qJkT2DMXYPP1@8$?jd5E))2w&}JBPcC&_l^IshgO?D!prD%7OrtWK^EY z8}y-+QL%`qUXrPG+Ma2XcULM>k6x0WE8gA7-hR!WuNMc5R-FYXRmZ=OzZ;2D*lNlb zq#&K%K!VAM4Az#+m`R991i!GFCTi|u;!DY1)oX%Kftuk^M|l5OYKl=OfhsuqSsv>A z2ceaEo^GK1t#Ri8-l=#twT(R~$MHUjT<7rAiVs6QSqNX*ynmAy zUZ8_WEXDxr6fI)8&bC`8TjilGkn2K4oWcdjzpP)U$~Yt_a*e7VI6~S6>FtGSf{pBA zIgv5oA>ZU*?pAei0lR{l7VuQ^*gCdPvWrXS9UrqR^d1reIF@DZfY*B^~J%FI&Y{rJ+24FP6ND z)q50gF!He@w2dH;Bwt|gG8{-VNnewA``~~40Xo(&fwKjhq^l7jeyd#x*Msy}KFh<* z-rgmPj|$jN4W)(j2b1nPYm>Z%*sQ&_zws;qOFrA8d?b-fGp8G!#~0lwYM*f0)ULl`2~JPp(!MfO^QmV^Hal+yrOC0{K(XYa=y#*& z_p#`_AJ$7z@_SM8QCZatkbPLlJtAeCEXh3&BloM5_>Pl(v?|yYBlm%m_@Ue@V*!n#6xsohJ%tUuxGzf7UL+oCD5rx)TO}kH7VafKt#f zo827<3JgIA2$ug{{zhkYhPFnwkRjF)>abxTM{)M0+d7wkWvF|Foy?t zAV&Rs7F07K#>~!!HlU5mHf)lNMWT4xa`OgitlRfqIKv>v*&{K#GrHe>b&<{?^sIue z_uv2M^6Yu>>8YK23#u4DGa9g)xYB&n?H2tz(Bsqe&dZjsTCp1UMK5R30{-rG z=Q=Wf9C12ovRXbm`9;EKsA%32IksHd+Z|#F6CO6*QEck6aldI?1gaG@S*JwOs#d=m zX8=WiUuev+%2bN3nb8P!fFbs1#j@<)D_$A7MN$8{!I)fSyi$qigc)GEEiM5VtLVUo zVrO_LiTk~gI^8mI5l5=xd%Iy6K3R>`X)_|yo7i`1pB^02O^?WvxRA^cLs>EUTEy%` zH&iw{nhd?;YQgxDC}?i%yh0hvq0B&DvGBCYQTsYksAlvYQenCp!zAW5#vKcTbqwNS ziM=O;q)U%Us_ZeTh>k%TE0`4}hEA6C?BN#FQyixUni^7C!4>y-dvX7KlDCINTk%v& zoipApDh#6Kt1p{WOg*G&tsqYC z1eEpsuCl95M_d3dLd#UQ&LJC~;$&JMjo>Cg!YLB!(C7SfI!B13ryi;#AVDx7F!&Rx zJicsFD#t7%rp#Ru_YN{mz$U$R2*Pm$%vjWg?GT>U;I&;%qq*TB{p#mE}Nf?G3c}1nF-q0^v z^o){7)$*uXX061ItBQp|Hb*tWiMgX=V2Gzw#_IJb3QZG(C>++OPYBm!-t~X1o*Slw zB)3nc#fvLwwq&Q%&AtCeVp#bYV?3O1W`I}a94}GqQVz$FK+lglz9&?>BfJD_vOAvSg9g#4 zlG&(_msOh9Jt0o>K_s?Wl-plx4Hu`?*9o2B4Uy6yufx7bg3>0R0XPuDv|va!M*2Y9 zJ)p^5x$@`HH!rn4U*%<{69$1GFXSHGATa=+o}9o%ey0D^mC%BG)tMO#-KIvLbXF{X zNt9RU0?+z06PhEC7%!Z=C@iotBRNVlc83+pQe3KHPxXi;MBnW-vf5{r?9b@wuD+XQ z;l3y8hxhcgiA-p<>(XFW@{C!>9HMB?lN3=7L=JsSyDfTj@?RD6_0!0-cX(`G3c927 zb#RaI1s_1%sudHp&p1;vLLF~OCaP^SVal$d%iQr}cEFp0d-3G%bRk_zHUFYdNE))w zI0eu=lah#huKKOlU=gPQS$QOQUk{ev410WP6G)F&n*YXEcCF^AdgF|vt>wUS>L0nk zY8FcO^0?^DF=!1FQEAWKfOxCa2gG!wPjz)_bUgGSH{w-OmiF5-4Rc~M3{M8%P^U1{ z_vKC-p=;lEAMI${RJC>h8)UrPgd*W(rTUQ6X-5Ov2~}f7udM1HCsuPcR;9f@pwOy` zQm#0bOp#B6?&yp>t)8@WT-th4=BIoL*($G5?v^6PKL<=BF0+*o2Xp?|Ks1qUevaG` z*LWa#`xXR0gJh}!;W7ewo`;9U*Kvk~Dqzltw)})zs<4=%O<~hjV0CMcuQh<@!bd~8 z$u@9OLy;s}Jo|@qBpR+?tos`1&7H$Liz4NcmL{tZAud1HKdC=0pp`3WD~HZ^ddA<2 z2+~3NRFltbTv26cA%^bc_rH9}Ayu65GGv083WC4MJ`jj~YiE}m=p_7gpGL85;TpIG z2M7Lpd{d5jWWi#mZ}-Qg5jDh)e-4@~mrv@Tu~xHq9nGoFQ5ecQ^JK(1Wrl~!{1|~S zvo}!v0kXQ{F4XWEw%MF03hqc!{KE=89k7H|YN){<|ccFJa5!Cd4?O$o-J}O`*S>p5#nm-?}$Nb0Z zi~rO~FZ4fr0|0@Le(WE<J*>L7v>2my(NwiliD%yNi}W3&37CL zc#FI(N!35!yWjoX4)TkpJFUj0sy^?z&oe)9Kmj!wmfsNr(?D5%9AG}BS_<7H{c2#6lZMW5| zW>k747L3{Qv0}d)Ne>xK8`d%!FYLJa=hG-FIh##wrVe5y>AY|%iKk?@`XRlS70{P# zKJAqFaK!i)tw`uD~_V3-ni8l(&g19DFrhj#sI z5>m=5fw)*)G<28vLqH3TBY92Ev!bg|^XWyd!zP~!sJ!tMLRdBa(+r+bvOp|ANCIR5zbdD)Z`DzKHI!&lv5h-mVrN_@sJCF8 zVA%Qvjtx)y!o2$_ki@MLl>|XSiU1;{_%a!ujt>QPy>i}KF`mF+&cF8E%WkVq*ePTB*NL{R? z4AH#h<1}~Bv@y*v{MsTCov0>Ma|59BaMtN`H98k8nbX{r`Q-DK+u-Y}`KS{GEv+%M z#p0F(N0`1E#(!5rQWFHXJwr@^36rbJ(g_q0Od37sXy00iT2_qyu!fn1QZI@|XGXzy zA8q2F5t=PQ&#P)(mUSM&R}L61OKwT+%7v+vCr$$Jk9clcC}1y}X5hL#506>MMsCjn ziZxooSGj<42QUYmC)o@zBCKk}gi9EXF;I%aAgF=}BStvD9tX0{E$t5f&K7@v!3%x(HPenM_Yv~4g3RuBM%N!nyoipsF$N?L< z2qwNW^k=Mbn(!kXl2LbDLv|Bb)GpXSKp^Y@GpE(7J)t^`J-gj8d*ZP7_4uxXJfwrQ^`s!U~kLriUKTyTC=^$9qYl#-*t zub7Orm;TrY@-n2`R@L2-`k+M?S1fXOtcI7TXi|PKmgKwX64@P}vdFiQ+%i}U!yKg$ zBs=m_uer%AEJ~5sq(HKlpH8l8z)MLZXU4aY3B#m9Bc*_j$|o7`xSBsjO32|Y{bu!3 z4a`>p6^?3^!F8Wr+|8OLt@Rb-2=WE>%E7!0 zMC{(3-f1dAKB4Vv9D>#ojvtP@?qhCJwK#M+*8lYTseKjrWVSjm-1Z&S&%Nqj?9njV z+`D~4{mwsKu2Z1dGtTOQw2gqW7|>!GUb;3yF?ckQu5+jTB-z`y=*j0Y-%jeY+Rwvl z?oYM%EINNSk+=UljiMvmnS-n7$9R78pTijQH-x!9z&ZRkok8eQ)aI-B!s$i-aV!BQv(?B&zE!xO&O#O4cZ+#e|+!lSv&* z85xVGhoA^H*_)DjDRS82Rw5WiSTV4wh0%j5u1P?vBkUk-VRSK4|-bT3n8g5y0il@)mu=tG3=;!jQis{clm4;ZSdyGp77Qa{9 z3bo4~Vi2+I(U(FxWgx`lsSC0r z{Iql)qsnNoQq}$U$^l#kja0d0x5AYELC<7p^+8xh7!r6?WSG`lX&lm$ z(n%L$Geu>AW~v}SC17eWTeWJrN2TsG*s z7!y^CL&XiFrkvqAdzjEfH<`7Q)NB_GilfII92e1Z0=3Op9CA0IbHX5K$%T_y!$^>= zkty|3C=UNFyM5U3$!2moGPHDz%`VPbavG%rqF#1yc^R=9*3*TchXD-QG&l&DAN0-V z+4VLy-vAYl>h*$JxavOu<==iqY|PK6KL{^0Yn{opF`yKF!7n^|Vj21i2^CO)l14bp zkkaHx=3znrk9KcnTW+N}6~;Rs6t#&R9 zg_vSxzZ8wBy*d&rM0mi)j0F&M{@EN!7zEo>a}W1=1_IBy?oAvQ*(~P~j51&bnwTag z-?{3UOSs1C_tnnzc`gp}CVFYiZmU&D=m3(7u(nDK5WjXCWmZK%YD&mOQFn@wNbfA( zzThYA{*@~eRxiN4?yFs>I+*vhe49BUT==PB0gJ8Pb|hYzYqtAZ*c=zYBLv_%ydu_h zf06&U82w>`&|TUosloT}F)XbxO8sqR*nWXk_dKE-_Fwl0kk#+#kwEu9-h=q{YW+XG zI6=Bdn4R0agsT}8w`5t9v33lCufluyS1V%gTi}u*u)ufHIH~tEVPv5D_TS`)Ct4Ew z7dkR~Gpa)6Ay*QS**=tV6>`o|1R4LqTl}=y&V;b|g7u&6kc3C~ap1z*b3H@R)y!0O zphCJQ+a+5Sq}J;m55p`U8o-#ysE9(Nho%Sy#YbQiC)c1{r_#m`(8G)sL&tK`^DdD6 zy`(h}C#4gfJq=X4%3sSYwNE)@W@voqJNdF_=gNH6uY52+z_g5Sb5FzWF4R6)-TJ zXDq___LUk)=O`aW;p7A5o-|G-R8o?0&BAi-xQ90s9SOAo*x87e_c(hdDhGtw=KbqVCEPRsds%6JW=r#brHIyek~VL(;fz6P0{?6 zPUayQ)WHlISMRmK<;q0S9Qf4*&#*gs$D#uSc|FUL(*3+#Z0DhJpyKxLrB4(SFZA2~ zCk2lkgtADrTtB|!<%bDUNuFXsVUA>80s}P0C;p)2p&z`s2macZiz5edsZgs=ObgxX zJ*VTnqOo30bGlV$Q7Z{8)d(JpDwes`G0MJ!Yv|f{$>_wchuI{$cIH0dedVbJf1uCL zPx@t;9#y{m8U2#TP;elokA3pktNpyf+~Sbf-qyU(+SReaR)Hz%SqAEyk!A^ng6 zpFX-4#_c4Uk>&l?(JbZ#M)kwaB9rbzspyDCWynTL`zC(jmIVJLO@!hMnLU}M{tjNB z5jHss!p_hj*sM>Y@jF&i-Qdh_H>7n$mqJ~fk;WJuv+>6zEwa-^3XG70CmhT?STCzx z2i{V3(fz0<*YMq(9c$FHBpWX*!Fqw>Ps}7Q$`+4tAkHOYCRb%|RQ*`x#;xiG-i~}h zqH@4~MNOi|}Hs`^GOCG~SAgib_ylcwFfcF+VgpsI(ZLaTXo^hma;&>%&Ze9RgR3}MD92YSq zkbR%Jyk-0@IhB7^QHY8XO86h^_EGbvYZrANU&VaEeUIZm4X=AMWqGp92PXfW{!*wTF_a9o=qv$ij%E$ zP7Rk5I-2clcQyD1NeX{2|1zYeOA%m2|`mK^X64i2ru2i0M+e8Kb8$n_0!z+5)=lG!l9_(6c8UZ$^b=@}peUV3qRdvZLTY zizb2WC##Z^4Fw5sWI=)9;x@vRfs~3HV)XMT(U#V_2xd%kRt$Y(A4C#~zYdOEG=5WS<4)ME&avV6s&uEpTrC^d}w>Wg4VQHIqX@ochR*y~KSj4fvL z;6anRAa{f0>3LaR z)cg27oA)$VoO6i zL9*oag|7(0C$Nn%?i9Heh46)!^IXLrZ=krPr>=)l+r2So?F<@o0(P9 zR$4P?YbFJ^X)ne^aq2dhDQssgu)YA(OZs9%^sPfHVWDVER9}v`+sM{# z)+z!$2-Pt5{F}PWF7I8mekJ6Cx!DeV5?o3RwSm{7D9UrcO&eNK&es zeo;?~pE_m|ya0W{(s<*nDZH!@nvV{ZSf2}u8u}3XA}a_-VQW;_SwHp!#I|e>>*N2_-Q4pDEzg_4Keg_J=@&Aj)yl7 zw>=JyOIhwhv>Z4!F9b{j$P4v?w$&BP%|ArhGl%K7Ede0m}kZU`fo9s&)T&dg|GIPHlnjKj;Dq2r+Tv&y|VX_D#Quv;2=MOWDwL z(>2WK2|5`B&Q9T6h^?^&X;3q#Y3N8MCW+#48~%g6VkwYSl+)^)jKdej>7SfzX@^l? z)5e}dmpRV;-e*r=|C=Xw#_TI-o$!G%hpnO1unpv{RzZ63q0OOV2_Khw?dIWZhJbsl z`|*`Q!x~J_(}vq-&(NNu6<4diX=CKdYR6l*kR3G(VzQpE%kB*t(=27{rHpZ>{i2hP zXrK`O4){^cDin>oFEVNY<_XKjpg!>hpGv3!d8SFwnO)rKR@V9CpcXB&w((yjB_JnR zz7!c`f|khB0N+nzYx+`uWZYy^*F>|2_qZl#jwAxSH_Owr-?f{}niBUZ<0cpAUuSBi z9CWbTtV(kU>w-Ycwm7bRU&r^jd4&k^Y$1rZDU@Br1lZs7STcKkV!8X};N_u8Nj9o2|IdY2A(X-1ONJdm>7w zn;FNB`qY$Sp4UGQdUVOo;j$PzM5~;L=z^-_hy+XN34BVeVh)w9jm+Vq2!;Sv48Ss~wXSXy?Nd6Z2XDGy1nEae5mSN^6r5*KKU|Rmel5Mm zX}LORh;Q2K7nCw@O?DO~Y~0Mfv3U_F_K+YGJoP%BAa8Eu>HwrEfnL=dxPCjY4^Dkk z2t_2ZO+I~=yjcTAQ!T$SCKz((Qo-S11yz(FJth_NB*Y;b=HEL@V~KT&#@gaa6MCK+ zWB96(2R9(?-fU$=`S1{@ytuzNqN{-4p@LajlGq_wei+S?8#y>jXm9{lB`R3(V(xn>eUG{=a)0y4-*T-i3pgZK(-4qX9c6wh@hfuYg** z)8SgFlQg%7j`Ugw^3-g4yTq7Ixdh2#tYUo_0NqNB=I9Yi@P6U#|Jsmj*Msb!b=h5^ zHv1>d(c8iPEf=5TX+SO{b@8}{DzMYyfbd~M3pEpb@DZ&r*yi_=-Y~ZsCN_BzF9AYH(z|Q3-h7Kg7+6Gx4MC97z{c6+lW-d?4B% zKzA*&>kCDDx$6EC-PVsgb$4}WFcEE_G zag2eIMD4#DdhXR@=T>0qUeuVle}NWzI9Bmn%qs&^*3vrAS&Agk|7DsP$+|8yDwBf} zg_h@dCj*q|mB)_Q%M$cI1AE{tTaiodpCp|TfXoj@l#L{45?O^fQ&S*#oNUOmNOxEB zN=2&&Xc&%^HLU2>97AHe8nyJEFB)0rF^dfF`+(#CUWg+|BCim49R76J6#foD<42^r zArAT4%Bl>yLlvhWhkBo5;n?Ff9-;zgBMI%{@r1l_Vjbz)yojq9zAenU5`Wt>T9Yt; z+dKPo<~9NAMfRyfnd~bL#bxyV;XV$5+Xy4y(ZZ`V@gK>)VS2-2+>fyDNYr#IvRS}lJ(IE?NY)Oc zYD0^YK6Nj7D4=5!nlDAtW$jiTBTuiPnE@AZA7GafvfCVR030u!0QF3DlR6cS zrozhlKqf-~>$e=#xdfBxtYp92#iTEdF)mSU&PM+|5JfQqgz8#?s zDUZZ7BOFB;OpD3uPQ8>HneFi<>S30f`hoeW-po}IS1AhH)cMzu%9S^cK%F{cN20?v zKI^gY1+V~|C)gpa%f8IMbEW;>hFeE7T)8q@Xl)gg3r)S?YcZrJ^CHU7Gi_h+uGAuW z7Js;#i7lFo>P?PR2a*lVDdp)aJ2lTV82IOfZ`>q8-|b}<_BJN7CP3!#sg_ zBn1|Wv)C15$cwgsiKO~#8&kkyWwi9=LHX?{qijA{IRkyj$RKs^VHMY+3A|*j2BVSbPcsY=!LnX_ zuh)c&LVwOweq2m#w@5YqjVU`5&Yz(eHc04}b616NP}N4u45A3A^D#hroiPgKck6=7 zbsNF^h3FaGcM&CB9x!p+#8;NLNQlc;TB3W^N+A8B{5A8m?tQhRk*yWd@-C5(PhJ(G z&*maC{q;N&*5e>n>vw8`j^O&r4WOAA!z>3n#apG zsP{_a(puD=?CiRvleIK4IzvwQ5!qn$t!k!D=&r;3LVhxzz9eXGaZTJ(guyp2M!V{B zeN;B3EhM#2HdS1;2zSA4HpaI9!Vg4m$eSuSpX0TchWC``I-?ht2QLG?^lckUmb!a| zwUwx!VM9Xt+QTQejW@?IeL!#xQ`Z!W%E1VtJU!_R%wy?1eXD?%_yve)oQLVi&O5W18>P6WQ@{lj` zoh`WrXGUkL{sY`Q@B&3a+Ae`019Z<1{z%t`t)Y0ndu8$+U-v|ps&NNGB%jhTZ|E2| zsxj^0F>mXb&lD`X)fm0taUbZIj}&a@$+fAm`Cobk^wm-EagB+I4EbB0-v<3OhUR`% z7AU8F=~#K6NhidfOR%JYS+~hQ%(JnyoKt##+2v4(f9P|U5*bk%+?hYfM>PMW?X?ul zVYBtZ00uP(gLo`Gg!$+u6-c}sb)upxy)ifEOfGQaPqZ z(YRIInzg0$8f0#3TymRKD#^4TE!Nv`0Fp(mNF!s@2ij6Efr4SQX|?BdgJtsiafvlv z|FeEea!~P_Set8=efm!&TV6+HnkS!#xv;{)k}lW!2+?% zFgwu;iqRCy^zj@vxhZlM0P?8dX{?`mhHPcbVTPpy*X~=K=q*@DjK+|CCy^k6T&n@n z24iiGUKs3UgT}AVJZnsl^>XwZAj*|}BEG7axB<&ecdpT=+3#V$*jHy$8f?N>wV)UqaVROAPVPY392)TIS?+n&{zz zTAd5nSGavf6z@&`{(W>3ZcRlH@?Xjc5wF`7#;?X|n5m3O+TA!*rf*}_ltLg@=p4xh z9j}uuIJ7LMeSnRTCN)z@GSE}1^OUQUDII}-tc%vk-^CGs7|Za9o)r` zn(8`GwUkpP#y}e?oRD8C0oEk9gKUxO^=TP7%*gqU^iNstY6Sx*Q1er`PE{p_q!p8M zK*%2*I7ar;#jt7Fe-r{bl=!#Y8nfvtDEfb6lPV!8ilx^%#sWnhG=qf*GJazxI(3|H z7`e#kicJh$B7}zJdlgp7(Za9oXr#znn${iO>z3_5545YUSE^aWmNv7u5Mtwkl~X^5 zrHbL;tB&XFY+x{9452d5dGMB!#(2m$<^=qR_~kC#&gZAd0}zpzprbz&KG26Sle85; zJXPM}5@~~cyu>A${INZoqmo`f+ghOU=ZkGb@&zP=L02N^!{XBvh7;{sgR2+SJTgdm9aTjyV0kl3Z%72BJA1~=I{puaG}{m zKwPYsMPCk+r3M^P?+G#v3f*@JYZ&Ly1*zl&6G9@0EDvV;Ju?}D;Yq0%Bg+qntZ&XR z#OAHJ-gB$2$*w*sZ>}Y8XibzBlle!2KwSq=h74N*SOd;#11iO#?c41rwg&zPtK*GOh<=CXq?;*R%aP!uu zgiCDrcc_I2V1b(}RbIppu?W2_N}l`#PiCCnpLh-FweY1SPc(on!IVL5M#f6wSe zAZZb6k?96<6F`?S*G2|G;LLljezbw8F9wpQ`Z#uyHWbwMkhz{#dh3xEldr`_{aBvp8ZLd42P>6W*BsIh69Q7}2>QqFvVE0~98o?O?rGF3z#`SpI% zMcz}0tjYE-xoedE#c&a$Zar|+>a;HeD@bK0{(j~$Ej8hdh^|TYH*{-C(Rb)Dh=o1i zF9x--^(&qn)+-D_^HtBwedM2&0n?Sb@fJl7CIgm*WkW3Ha-aiqA+CULDk% zD8yc>6TY-2vy$s|=r}!>Q1|NMVic@9fMVJMZ8nmN50a0^P_%H(Ki|KpmJ{4y_Rutm z9%VQB{^dqzo?WcsTvx%dTiM_Mke_ZEUtf=$%jRQPFQmxcl zRS+sea@1>t25NfVd3*hT5E4}I@QHb4-A1qa7>%9-PkR^8h>PO`kNvwF?=d_mkJV6arS>g%IAkDX@+e6Zkd=R$%}pkt|-F(`4$ja#PWEt_93 zCUkJ1Lacg}_jP=ikj8OuhLO`>T>1`ta>SkRKqocyN&x!yu1SazY{wRq$)O9U_?Sy(-)n|!;YThmM!ojuY_ z6Y1;8@%tJ%@&l3eK{LWdTAQG4_P~fvDD5|1KMCo%@j?Q;K80`V zi!JnxsI3;|=YDo63REeT7qk*yT+9tr1Y)eguH3E(K*NNzPcEyQbE%Ia{d{Qauj5#U zqo>v_Pd6|^#9tT<Bj3jMu3{CG6mgDgRcaa+K#$6XQ(6gYLxe z08mvch(&3D;(ByKF-bC)8yrq+aJDxkDll|^;apayw- zQ9r!C?K)f`E0K|Z3cevP>WT4x0sNQ1@u5X#3_nTySw<*11Wb?*&n0IJZ1+{`>GoU^ zbV{0K7sZ>P#+QHnXZ1wCFRDZS&otK<{!2Uh|FgL^E&X?M&GjYEl%wm=DJ+1_3w7H>c6+62jKw!4=ZmOTerJ!>xLR;W@c`f znK=#f40F;jGc!}e%*+fk&@@cV3^Q}mFrEI_+Gp*vb?%p|d!?UbS+Zo=^6TMeXbk73 z3!)3##_@GF3kaF${oWtxD9+9zI};P2Tf=B`-~*-$PTMo3{cc`S=d&qCT5mD7DRh{pwu5 zfCEzm zv-&5wHvWr2Q*0xeOBLn<(LRPMO<`>`wEMGYLUOn-R!B6a6hNS1wtCp|8n0veJFr$+ z;rpuA_(*fA3pbN)^AS}##-nJ|sk;F7Xfa6CoOJm7*GkLxRM6&e4w4JV$0)Cje~zr> zf-)^MQr7Y6(OK)UA~`qFz4<8f1YPqhiSnRfcp|8rTx`Uj>_#XNhcK&GEVcL05m(W~ z_9J}mvfEglF!jtm(m3ooT}M9~l3SeB2TOXENy1M|5*N!vnFhB=o|Es3AEFJJnsrKd zk-WhP>5;k@z9yIJG?gom?YB z44V~ELJu8k5bW~2VQD~h%sDcWbb(h=st2f#G|zsy|H_Awj#5j?e?^+efRkCp13Sq& zDq7v^j+TU#g8&?r!Xg9)6EJBisyKN^rjS1fh*vUmu6Dh#Uy$c9(CE%A41UxzYZGk; zvFuW*!n3^2w?$8D@{)#X9p@wg5DALGy`jMR!j~oc99do8S+d`oD4)`M%vxVWKEleD zU~GC>RA3$1YjUKu2D@Di3ZI1ha;iOzR02>)SUJ`a&YOE?A$^|`)7aTz?AOTeJ|hdA zh_&+%enlc!3z4<81t%P+E=>?glkD7~Xc*r*6lQVQL&XD_rdPb_mwoa#6*j& zqcY0X8?__((M~8w!jxvPDr{yDknFa7%JWzsoO1}9MWVE>m8=zwrSV%)s>&Zrsf0IG zkvmm_R}BrpR`x<*G4wb(!`GLx-KH07XDiSQO6y`=ajAzh*TuY~l~zY@{}*9F3o!F^$ni*uO0 zOi7~;Amxhe(XzueE4oq4Z1l_RDU~zh#py|v7^Y3ct$823Rw^Fp8BHr8XM|Q02`Vxg zU-c*&ylP_pr7?^gc#3um&7>K+Il`#+F>T_6{hKHIbNcT;;&xoB=m#CnZ@#vn1rJ1y zmN}6o0ENjhCyFgkp}jGt{uPlXR9KgAyyJNJLS6MMOzRbva}oZJv&kILVRzDv2-ts6MQ;57DuH(xZk+jbz61{^!&t)I}7jzzDUd{|>HQ zeq}EGv^5m;I}*h5RkjD!W4ye={7<2&B=Q5@{1qBhbTBZ7|KF|te=E>`AMzhf`uhLm z6a5<`al`H)D@~j1>_>@(+)%2PiqS>E9vOg}g)+B{Y zO*A$6pbe93z47ZtNpArLskz=w=HEsE@z0h2G71)KSAgr9>=faq9kt@=DeV&yY+b(d ziN{8=fEN=M5XunRjlPxQ&E!WiSe)v*mtdn;P|4&P>bTeJBtZD1e3 zq3_W(xqSB8%*iw+Sa&j^075l9yKJPgP<~LKF}46c1~XJuO}rb2UA^-?9LSpZ(%%%B z!_RGcVBpZ6g-PLzDDKK;6uyEds`ycYYQ|(N2&@8gAqakOM61)(Fu2wj=W9JS)c8?u z6JM=lk5uYYL%5bgeZ(?`H1y1XdzctM&GrMq6kH#(LYq#YKe~_^By2oPYfW>BNH*O- z%bG2wJ*6VMibV4LGm@n3wnC8ii9{`HZ$w%uZ z=IqNy``x*ku((&JxHDL#&rxMYDmmwB9t{#woz$x3v!4`*AY2Vl8}H;IMrG-s4;{+U z-vK)6)=3M+u;|q*#RfN$Z#C&zyQV5E!2C&tjd8*#O;Unzs#eb4H~Vfeyi)WKoT`44Jq+84QW`{zo<#s=D8u>r%E00 z$<@2RgTaTjK#~Cw$ejZVMSB@zTX2u&AbVZezp=&Pq+Hn14t)zg! zN7D6yE)vjIagXwKxNHqf{3k1|Wc|+vud#ib?`f%UrxpL(AKh)PC*YMwx6^OUSQP`+Z>xfWD{&Dlkco!& zFb=`-DnurR1+FEaBCaWct!hvISOh$=B@j4XgBLodvZ9^GWI`-vMu3XnB?-T$eOtWX zSe8+1wXFKOxzrP z(V~5|O;UWx195^T&`rB8m8ak?=Zy1_#A@i;rf|>7&v*Cwn5Hb4Y9d9>c-eO+*$6rC^ z*wvgJkc)ucS%tsp1zVJoU@5#er_J1ZFEcr-b^X(G?-w9=SWhz??G?(t+?`i$^jMs% z67&Lr&7n0ypk*Yo@#FCH<<#>~AbnBmiuXcMz|=Jk;|l=p)F}=(g0bzBml;#_!@(JaUSPdI|gR6Cm)0Hh_LDuh?o$c1UE zDY3zGxK}qQc-3jq+=1-c3}5rZI+Ha#*H)naCS(ClfY5Q~VV5*84?VKi{SyY8QR@Yj z0CuriM1(_1Z+S!qd=0+ik=*@1C0IYuh3We4LS$0ZBhK|e2J9Gu=l4H|#7dloqXPeU z1bi_O1Y@%_tg3nx_(cf~k(xn<;*6^+I*9oX=O{qNsDDbZSc!L=-^?nNpB-^Lr(wiy ztZWx$alTaOJV7w@NRWTbB^iA$$bAxYhl}WnvVVif!@oZ`328G+fr`x?ye7$1Uj8a@ z$A4kfA8Q3Z(`q!d}d zDgO3ALFdy#yGBUqqyNjF-aG-vygT?G>wu-?T$YXi`(h-+KoT3_g!cJxZElgucS~`Q z&ky{_|4u+v@w2=~oyl_Hj0~TiILJTa*thF5_^X&ivA=8%Wr`NAl2j&V84KJP zfYPlwY6ER9Wwx<*!^RHIdM;iT77}#g8$)<{Z%&BwB|Erv=Ik|!(|I>MB!~b0_f;id z-66iWrKU@cgA2$cl15zTdWUh@0LX^H-pFAnR}NR$o)VO6LVL#gS2%$UH0gv3)SLl* z#p0$MO@jLw-!J@{oiQ+ICP+iF#X>>EcHcGe-nLg!PzDq}*oF;qyT#ga?l zwJ}Dinf3?6*CeS1+k8R$hw@tgi}J?*MR}5||4n&aiIBkPmdxo-N*(s}pOMBcGVpN- z0rX|xzKFY3^q>=)&{DhM{T>#BX7I$Vij4yeH@NB8$b-cAPKXzB|5Bdai`1|dX*l&i z{iI(Kty?T+NjUcT_eEUl8aU?v=_k$noASanRblKAYyVQ-yuIm=;P=4;a+3h5?1GWx zhFtn@?YtL#roS^e0k#+zIS(2(H;nI{1UR)TE<))kup&{X$O6=Rs^=was3z`*cDg)r zc1Cv7;k@U)EXr#JRyeEfiE?7YsqK$Goz-6I<4Q8!D=w3+aMhU!G{z+266b1Yh1-FN zp29y7;wKHseig1_R(=FgczgRg{OZh z5BLw|k(>XE@&^Am-MG8+YAdDQ=Vp0ajcZ1ZLEqT;kOOh~pVRy>Z(#^i>N${Qu}-rl^?DMf za5Li#jk5o$C)sKcX=W4emP87UsZ>4V+v|NKxr5JSVASFq>wjut)WA0mWL!5@gvJE5 zFcmeAkf07SxK2I+5VK9lyhHy(c^@o3?@h|rOrP;f=wM$Sv(mvT4*EpUdZrp|hz;Pb z=Iz6FUt65&w*5rk1$_rp0$3VmZpBb;cbFM0Es$5*BsV|N#DM}0LKF8C(`UY7=XP); zuQVDg9)$LxGs1e1Xjs0%%I5k&%R4zUr8z&ce<-iGB_&!+|6i2XjAZ{`l*jRJ%5%St z$!T(Ko9;%uDRj+t)%nxm41+{z17`sHOGQ(M@{&WOl+ibFmEoVCGEo;_>(XP?_cH39 zQp^m}d;2RI(WolWiWm@Ad!!9#|8Oj{IIElQn)iIMT>PDetpDghyfw?Nqz2Ji0fgM; z6LUOk$a@)HgZ5^)-Gs_Ad1)m^fmWkD(c=+eQk+{Ow6}`+3|+OhbLfgYG*M`z+dq*o;KNizz`@2<6Ra-+<`A^S0G-J3|_xA(Y0; ztcsYXZ#^j!#vDyOf)?yz1J%so9srJkjk%~$Qj9`zAzp+ZXi1*Lf@<8e*>+tq8Xx~v zPg4A+p46nT?G~t_SS!LpgDrSYJ+1#ZAHRK)l_>Z0Pd%yEP*>V{vY>rB<;QOhV*AL` zN>2{7sSg#o5RNiR)UJLYrgSc6S3 zGCVyt_BtH1+U=A*Wi@i?OGzT8uGTzFY`AnF7EC102nu#hE>2H8uujowqiaiXtfN;VO*iq|rIk@C=At(ShazGnMK#KaY|#axAuP;bv8b6D zH`p&nONGQPkI>4h0-h6wMv5HVRn%)IQSy?nor5bv6MDiXiP!*#v6&#AQ-}D@oHb5o zK(4!_Hsc_hF~q#gbdPx8&k)&b7GuOSJFaDK;KeGhUQZ3msbU)@=3+8GLW4ojMx96- zgUSVb6(Qs(Jt3Q|7eJ$+hHO@wr5mF5Wb7q{6EYCVaRyDJHAzM7uB3HJv?x;>$I6B} z270HU21z2j8n|Se9X#O9;u@LRY{vWW3Zg6&PqtRj9B>~8gsjw2%bOZHtCF!Q6=Q%v zb#d@Ve}zoW1K zdH0bdk0iwI5hS;mIdpglk_N+@lc*;wPR!=5F!gbwq0>2_&Z>T>-iVYqxHs^UuA4Lo zQoiG+;3fOl;G5(U=NfVm_ew4C z0s1V~_An^!nHWOira)>-m!fbmGCHUfhB|s6aSc9sI7U6Ijd?;F+H9=oUCd1Inv#vf zB_>6rc+xZTSCxJUa3pPHFDvf`F9#@7mlChq@k1Gs$uDh2%cH9%V*tIJ^W>4%d3#{5 z9dX0PYCe%WO;m$Q?qC(j7vSSs7w0qfJ5lu&^6VfMM?yzrnvvAIj13sibsWLTYM$33oXtprniTdN|^51@zv5hw~N*pFk=(Lnf+vGF}r268Q5#rMQAz zV)KXwpzJK#z@eM9x~q2%q3n4A1Xt0*rjz$R?n#oV(}Nq^w&%)CCgF}^5d#@K>+T^n zvfBA)S`gOY_(X6kL$FB)c(wC`l!gmbb5moydkPSVUguOSv}DH=EPLG=Mk8u+$`3n- zjL4n(tK!%qPf}%c9G5Z{0Yb8A@%VI7%`}xJ9#=J3+%vPs-k0R1uuLZCqEy>VyWUA$ z=Fy%7!6+A(vy z=`QRR6Qz1BOo@6E-;fgF%(4b;Cw4T{QT-t|6Tt>@fA}7)Abe=l-mq<+knE*1;2pC* zj<&jYTQPQEa0CZPS6L564sw9Hg>$8u4G~d>@Q3Jy z{X;z)Az}pa4*Q3OHE=)#A_NwZT15na1JR8RiUVV|aCcljsibJi@DnEtN2M(SQPn0Q zl`sjT*5SI0Fi(P0I!y=WA0R%3@uZ>Bj<{(Zt*C}$SjEaqek2lpQd)?^rJ~_V0eFI( zAt+#DDOsZjtRT8EK#-uHO0n~uJE*mTgqsi{KN1EBTVqxL=nNQaVQ>KSF?hmxgS&{y zCv=DUL<+CyB-yj>>1)hhjVM?Q#+9Dz7%n=8NisynhYeosa;Z#2MrzlL^WjVqX@Ecw z91-+8b6MmpOff`cXERMuFRTJ3Yt(=lgj!&ngYad!a~008UA7zmKj_3Wzm>*Ej{-6p zwv#&u56S`^6#=LNWr0ya$r>@B5AljVkha#~&Y2C0_&cFnmMZ;6pj6K?hL7a+N`WKV|XZLg6^jctS-&MN$K`_ghf4B>|j4oJ5tK zMx^LP7wF|bh6Y*<;FQPp6|||P`&bWEAR^Fs)(dcLt0v`2G^j%*o1lhh^$$IZn2wPl zS#`3PSN1#ZvInSJWTrIn&b1;7)al-FMBGls zOoVdj2+K8R(PJqQ8E|JM2GZ#4;NKO?tFvFkv~I?RoIKTF^y3d&jcLYx2M}mXq*cEo zEQSB5&Il`l&QZ`$p#F{+WC}+pz3mA>gUXXMAPWU`LkFcq^&P+A);(f3m2{N|Eh@@| z!%!wpS-DXa$5w!D5jS#Ju`Q-@18+c%@7CJY-qZ!42wp0!iou+s)RUtdL$#^{HI2R2 zfP}JXrUFnx^><4!#o>16xD$-AQk_Iy%PF1N9-f z?fBb4EVBpN?Kg-FG*m7_(QP>hEwqjw1DsHGRHUh0c?03`?oj1n!ANe?$RMRD2h#5p zLD{hD3ftJax~+69ZC2Y(L+f(eToCOTsH5w$01EKJj}53xvIaP9(fKxl&NhGvM>O;T zPqf4k&yG>L--}!{6Fk3l9y3k7=P6WuQsk7Ao;&(Q7F1X-=*iV?8xhZw zK#@JLMRN5mnX}1gaOfVS7VB%|1~LadhnV#YN8W2g=pN)N)6ce@>I})DYPvq}9r16~ zX#?00kLVz|?LMw@r3Bu4grP{c1xP4j%7(bWYU&+LIu*dFn11Bfd$&-?)xsL(wj^6Rfb%B$$_Rx%$@}~eTNtPe5hQSL5+Xcj8S&WLF2K3N z(n8Mo@sgN-t_kL6|qh6S5ND156t`{sbDzlI5Pf^zYB{s zU`HiUB0T)0QIi1bH^qnv0Vwj}v3Nb*3q;SrR$&|j!kbHQN8;IN*sC1}^^1?H?J7(Yo9WdpIB5+wuMWd6>u80KwYsdSj? z(4d;ZLj^x8mlq76rjpaf7;zAvYI>VO=l_{3>!xvOoOQzWp{AINj z{NEFuF4E04+m7Ic?bcRF#N?$$4EG*5NG{HwhrWb&8CB&@PF=Ms>l8&VNK}U+)w4=B zfd9FbH|XjMKf1CXDW6j0V%c_jpbOsxx-bA+*;xb5 z2ZIR(?bQ?n_|I*&FTbf{^23W1qF9kY{T~w=Pv>Qgp&eS5)S`ZFLrKouk^}+c+On0n z4yx{raL|>^DyX2wvN+FyhaoS<BB{TFv)%&0xg=^v9` zZZf$*^LgWirJyoAa&>c@#nF7oi!&9lFhGs&>Uvuq`#qZA@J8xZ^XpSc5eW9zX z+JcjpZ4@(s!M1>@Rns@F)a#CiNv)@+v2N<_>k;$JYm}3LZQhcF-v=F0lDxE z8cd3cJ2K5$`SC8xiY=8hv*qT;K4Vux8YRyDNpv=r>f7cVZSrx%aT zmiB2WPe^EZd2b3_ODD$R$1jtD3oBU`aT2d3qlQwJKZy`LR@kjDE^k3nMl~c7LnzR{ z`5ht^hWM}H$DVM14?abfloGKSgD3W~L|<5di}m8# zSlQtRFi3@h=#l;s;5&>GM@;vW`J(!g#32wrI%1lmuv{*=>+8Er!Ul)sU20)_I(9ae zgiSMS5r)rsGbX;4izVhO&}*tmnZv%MJf~dt)!An9T=g4ZX?1kfJn6gXv5J?N}GMQS$o{4WF-bfh}@Exd)E` zvGM8IeT>ZZNapGooI>vff}K9h(Ao+cwd9Kj>A)h(G7Cj%oy;U1dS0trjv6ILA_4}t zW|@DSKj(N4*Wq*0X_-7qVb^!azc(CRjac|(yOrr3x_fyy>&J`YOE}`Y)Vl?s)7=$z zaVcudpWKtCevWUQLQD`TJEC%{Zc7T|6Iq?qpV%Z>ySv68>pIMBb}3)SasT@DccB={Fmx_k zCts7hGnhsmw#~3{SKA)uoCB((A2+u}*+*huZi&$OERXdG_q@GeDRcW z#9SOZhbRudQ9J40wAq0|M~KnI{Q3Nnq(@h~AF)l4sSCUDb`mt*7!ZWX2wO-9IP?}P`n*#$qkp;*d6OV_dU1F zq+VkdUrzxqBMsFH&*fAnsL>u+#(M8@?_p*Co(FuXxYIwkXXAWE&G_Mz#CB5%-J1;Z zuTZcfn;iErw>Zr|%~xr7Lp-v+7({(_!%$o4tbDTMWF>_H8)eqdY^B-~7z{@K_TH zJw1+_@bzB!&(`>{Fp(5LH@(ajx^xvm^zN}*@#PmeVWy-2$65T)Zm?E9mw+L_;e zPa$*g+B2LDwDC9_XR&0(jW2Y-TLk}uSnzrwu z`x2?+@}6I#nA|4uR%d~9jL1BnK@uIPhWxy#&f1G;#Nqg#&#leV!h5tuS>rt4>`Pnat5BAGT%PwmKPh3 zp&7Jkd z=(%x?UHsf+>U=a@-+&NBZrnQcd0@5mo`*dKI$LSy(;47eqhm}+5&tjQmIwcbukeRnh_C<;Hgoo83CL%)g zCx7XRTi>Q3z*o95EIjT32tLTJ$#%C}Zq%cHz=+c<^!Wz%_jy?~oxuZ1Hu&UjhUCF+ z|Msm#T3;!fry9PVWvp?J>%^*d<)b=A)(9Q-Vz_uSL{TU{VFrNTPd$Qm@{r~E>;^YO zanNQ|g;C}!4&9%(m)zySKV#POMh)PD?jGJ>_qP~x+-bMC&VIJ1?oT$7T-^%FK!jcXuH?-V(MkLqe)0AanKWvwS)8$-VMH+K=DCxpbgQhnE-MK;n|IN+&!tJOZdtfVd(rJ#r$Utvnjo>XH_fPYL8s$e_ z>bs$|zmkLPHAWmA8V z2Tb=ZIB7}%#TS2ue$G@%6;8a>-^1Hm3+>@uA72nZiZSmo8g||7ALU5yO!{^ks~S|*x?c245vc~XvB(< ze~vr;oXSwn6A+^`B=O&Gz$9qNvF%sBrZOwrSbMSsV!6MVYbVehn1K^y+N6$DASoj^ zd=T}XE zGK?doIIpzHZ?IYNCso!9$k*jT-Oed+0Fvjwhr^zcp>s=p?@gNq z@&3L(N48s>_Dct{OUczFvQV~W5YTXN3haG1+5`O8D&If-6a>wvrJO*O5*^43vjeo8CVP~%6{q?);h#)IYon8sz%FLI6aO6L;# zF_m#m6XryqGoB~?4Slq~4A5Df;Y?GfC(f;Y+dp1%Zr4X!S-75Vb<#V-&eV@Vz+ahx z0QAcph#-c1<70D4$?bgz+de-bL!pDlV4?6vFIpb_BzgH;z~#mR|Mwq@qr#uxxYX&c znGFkHSBy&n>+LvBd29sl?9IB3CMPvDoxaMi4b&8MuXN1i`OaA7T~)3OIFGpL`r{jx zFa06Dn0icB+Pm%LEbVK$O=hx79!*r5IVz6x72l1;Zv(%OAM1*JL)6Jo-BS*@R#La+ z%bqzkJ;aP^bhetz5nnMQ#31!oG*%k{MKxG!kA)fc&bIWOqjQ|0YYs9?I%Mo@9sInL z8}xBBKhFRK_xR!A^Aof+W~G&Kj;yWJ+}lY+Il6mA)i?EcBCKhd>gg;6lmB`5*E@bi zUZkl#2itkeN(2499bo>#{8mARyL$SDp^7$cnoGYLXDP33$i8oGvY=pD_GsQS{h zIno>o9T}c<<$SBJoiDK6Ge_GR+=!v1U4H+H$2eE+fV`ABUXnL)=m^dIsU#LMzruTN z<_^CQcC(QMv*E`6u*<~L>0q7$y0H7Y7U^2vw2+8_=TFWTtIwC_-MQ$&XY*aN^2&~J z8sh;w<7JwrR!(y=7d33?Opq2LAEUQ2b*7`7!%d??B3gO%ThqP# zn3U+17CFJdbk7?0taLm))HFPFE@0nUcr9Tgtp5*)b?;n=D|Tb%0f5rbsePGnAlHVZ z*t5n}Ax#Y3^bxDWQEsvKwAzw~B(Bj0Ys+PCu7|_CIY}&1E$54pj$?1W=2VmBV37b@ zEz6B#TS&mR+L(7wdn2f6*b*jKSs0LKG}Ge?x%cAz010n}3gMo}U=xTa1PsH_R<`=5rkI~@)RleJr;%81`|u<3Qa^!F0tSO|yvn1P;Uw9W z+deALu|RzO{iNdc^@4vtge$`AiM>HF-1TQf2}t_DAGnjK5I~J?M!t|hge;4L{2Y2L z99R5z8+$6OTp=CdD|&B4y;v;XSM*Z9cVW$#)jp+|~d*MO`f2Nyv%}egIYg)Rn(f9bwemUO&8o+E%fXqg~+Ns(jNw*UHa`Q06 zVcEtF@^ z#!wMZzn1j;3^qyFsHUpYl3%I?%~gs^j-`#DULZ2RUA=LYhL?V`@uV;PwU>rpn2MiO zvq)RGc&tQsaM-Pu`eGA@d!*cP&EqM!G6DhxGJuJ_Hby27SM7NkivE6F(loea;?OA4s|$D5#n1 z&X-SdmwW6JOgN&|vxhoPGd}7u_wi2Dx!+B#*#BgHJtREHcFFDIWhs;BaqN2TE_A%T zyR+YNRqGq4ft;qof+s0bY25>8+rCZeVb<{Zk1;=sA zOsm`L^^nUfqo)^rZL_GyuX_Y<`#Cv|-UgA3{TDxUC+N*6sWe#WTVD3@zYXVdf?apY22QhEw#EDkE_(h#3-=8a*y|9k^l_)Z;YCI;l z6HZZNC}TEIt9|GUp`&)0pfT@u9mSDpmg$M4%6za}BYOuj>5F|x0&!~Y$5-oH*`bzq zj!hbdhEw)(Wh*IvrM@S&bUQz))DJ#h+b1c~zKc*!Zwz#Q=@;Mpf*F*XmzJ%g7gW0& zingaO6Gl-RjyLg!1@a85mc@R7OabMG0hCsEmOWg{#!E&@S2=N50qwO+H>V^!Apt2l z#XIYsT_zA_ug*os={zvhhHL{DSu6Cc@fmk$sOLwt<^^H!XCZ#NoWl z^xPoY{)Hy-#v+`~mIR`!mOe4N{(YrW7F5W{caaP_`8Gs!$`F2bb7je)Us>|;il$5Cwru^G5pJ{^@d)$Bhc#FDC1~N>%(v2k7PZccWo4V7JKrA75oR{HMQ(v z9=);6MQU5MOc*6iH|U(o)A?3CZOGq>=4C=Dk|Xtu$ae8uirLdm&tFfql)57P3NLD> z0G%i8&AlyIt3r${rUg~aY&)wWPd_oMyHzlrm;yAAwWxo<~C-!aiZ;k_uFs!nZ$Ht6dd80|&+{{BS+y(hJ{C||W0U|FZxT$@}?uuKm-KE0dVJG## z#6S6kMp8n5W@rP!@gfEbVq=V#V=ax_V-pHl_h@jFvye)&o>@6uaB%ZJOEP|1y^YVKt2h&cxdc(3m!E;5x$R;W#4W5jaBNA?GHF zpCm#a+gA!UbvYVx>LlY4@*v7d^kL4)^kK?LeMQenf2GOEvmCfN5~QT?uIh&ny3 z2XnN)0e^JV3xTum2Z3|=j)?QzWZdh>qkqni%lt?Lq4gXeTGJ2^%pcfj2mY0GNc};n;b&^0kMa_lT1t#IQ0w``fR&v@dhFq? z(hH7h<&H21xJ|lH9d*OSbh+owToG%SZxE$SWl;2r_Jw+@g9k(5A_^_B3+n9>MK*h? z#8uVe>bl)d&x#RlqTm_Sx>{TDZWHS@{6VaRb=vLvKN^Ncx}c+Uv#f~j%6A4pB}DVj z22mt(R#PzfX#J691a3`K6o5jKB?2l&zXaMJRK^IkfJS|d&8pf`l8AYB{0g$x-EVba~jF8ga& z$JbiaE}UcBrGAhKr*O5pY2f*=m3z|Ev;h~l8Lih>es#E*K<5xx4tV_vyd#3vPy7Yu z;iR~DD5kqNpAcgbqu-_L=ppt-oYb1bkNiDDG6wW~l|iHBgYJefC^xJEzm!9X-b_4m97AcIw60Ujxdq}@ z&hPSQd$w2L>%ICSmKP8`DQdZU(%Nu(^05a9cJQu^S8K1GR`;%DS0k<~xgj?nv4Jp~ z?>pjGHcucQWZ&Ihth4+emal5waz465Yud5IH-HtbJmmR2ta3uRb2Vft?G)eWLXUGy zamNeZ3oj?a;`RrK4`;%$KoV__Csm@c3b8^K8Yrm?an zRELqJ7db{=Oot82ASpu2fq4*u$rMe3A7T-Z{Tk*BA)x8z8Ydg&vq9tO72?g0;KLvP z!@Quk_FXyG60RsD>gu~Qz_D?!wpYASzoIqhVE4Ye(Uwv6>xEyr$%eIeLsO=S`6`Sn z7`=ORj4faEo64(9R7sE+dCUkJzWi$@=rO$=xsEGvKV?02D10~+X-;TXC<^0H(m3BH zpU(JB1rzS9Jk(DKMLK4L2tEMz!-)Fs3S)0e`c>|gFFt_!)^Tx&?kPZg6!fNcdbR%v zo-5{jJu}$v=tRWud==!jNKWuPGc?C(MsRkvZy8NzD(8>Pyx}bRYWOPZq$6EE(~u+S z(yZ~if|ea)nq7L;W}n_>(;vmZqsKR2@xDawZ!`*FkiSs%zEt(j;08AKGY7BP+Tz@7 zNg;RV=c2PHjDcDg$8Jd(&)0ZYI<&E+t^$?nyl|JhX{Z%b)26XevxKEyxwgV`h837X zl1P7!d(Gux3tp!5-eZ9w+LEH0^UN8VLdsh&=9MVH<>n5vl)Q}d_H*2f^UTYYul>&^ z{@E2z2RzRQTUp2O9_|~a=XP4>b<{$Q*-Rk&`S)=yixH4H9RY9eV{K-v1bz`c<7&yH zc!1$pY>}qlED^zdu+B(ABcW<74pXLEhGF2Fd=&qLV!5(DJ@Rt}X=P@-%{ipU$$$yTjDRn`6g~zysA4)br;e0qx`6N?Qy+?<{Ct@g}S?g(6 zhCJ@?d3KY(32)UXH&{Om`{)%VnqaccQD>TMMeK$tG~TG)TRS}Y*-=@ZG;dtr+4-g! zUWC^--n^b6tY5=`o*?f-)~~?{`X%b!fGn*y_b+VRZ}4k<(0-)^ed7)RGIlK^JJ}Zx zOfU4@Z@S&@g^hzGTj0_lsIxy58@Pm~dkISkF2H=NJ>_|2cVNE7p7Qd{cVNE#-pt&L z8_)=_$Fj)c3^X#?V_9Nx0UBBF)z8A>c@Q7zXZg@^=tG`Ed_b3LJ{0|ujAS^ z-DEp`-3#8b*}HP_xDMg+&+gkm`izVV;J{|y!}47xCRaXI?)vIEYH?ljkwfqyN${Ec z(M0gUN$}bA5l--7Pw*-HP82qymh?-lMJHfO^C|?B;txgl``8PO!<%sXjnT1D*G_-q z&DQBw=T1P&jqu1&iCVvr{U67dD~GAirkj8N=fnA&vvwJQqxHRTZcLxN z%gx?(bqLRN+tBS!E}?|i_3P)hz{{Qpg1(HntEtzHo!7M|aNh!gzJbxJArChoO5=@W z4+u$kaWvz`*x?C{%fBT1^Vw?cZR45PVQTExA>JSGFHtj|1idj+?0GD=X*#}Q8cB|A zBacHx^Sdbx+IEBZjOjYQY}jNm$|EMFjGE)St~xpPHz(RTEcIO4zK)EDF!SmdTa2ZQHhO+qUgDZQHhOYo%7&wz-mLe^s^rIRCDjI#s74X3U#$ zIY*4%=jczgw$u7XOuLheypxX1pMcDt0>__(%%6tLpU5kaip-y^kuL*fF!9Sk8p=Ql z%0LEcR|;xZhF(C5C7}5&Nb{4v{yXjZJMsD(?V8rGs1Te`Te|?t7{+yB1*EQyuc#H= zs;CoOf02U_Nq?y%B~jBeQ_Eq{o|8jz(Lh-ul{YV_Ptf~=~LgeQ}yYSKZ;QP;9q(QDm@30 z%C|||7dI7-_Kg%4@aI56sdM;XT=8#4(oc!?Yf96tmD^X$!DCI*YtF%I&%tYOk&n_s zZ^_lzng?zw0nNJt>Q}k+w}tfgsr0u-)ZD7{H^;@h&gd)8=&8@>DMWO>;Bh>A}>l^o@Y@?|$XDv66f?MY6F@mC7ecZaTS(awA=yTxpT1 zg~d`_xlt}Um4{W?=zJlww9&aDW}W-##y2k2ld2c5s$PZ3#*`fU3vPq+)$M~*rFD_k z%gT+vsayKe_6Ey!`3Fk|FZt|(|7wCZ;FMiM(0)KhEigvQ75Bp_`Ng6X8V^*8-cqQ$ z3ZoFlmL4l<)19<&3eU!>-kqxad#dtH?qMpo6jIrue!ijm1>xJ~{RX>THcWjin@>|Wr91`zElg~p- z@v5eVRlPb!S5>@1i*(8_;#=OxRPjse;#ItJE9>TzKj+l1Re6_wt9|}fc|NUv|JP@S zS$-B{{&u){j4)9R~Mag7;pE z_v>YEwi?&?%D(vOtMQcl4AHo=i~2}XS*5*ISH5yP^{uOD-*908D*L<2p0c0x#mFrE zG!>tk>mQxe{e_FaUF(lDDrG$x4~dn=&I6vm?pFB4YCkt>Ki|Pya@#X^DOFBkXUeZr zhb1$yn}=e(|H7w*JEHXa;k2!tGM(5Qd)zdq?)Cs>+xN&YFmz=*G;w{-ywkvciCx4- zXT*N)fX6r5=48IMKG?zB9K2E+PdJhIYa$6yM-r%yJP82HlKbl-2~b56v~w}Ikob!u z2~b4lH@fMciGLOm`)eWhTUOsk_^U{I8~6#H*@GX4s6A8 zXd;{K4$`+H=)`guBI&Z-|E+k=kIt0O*S4*J+nWjPLnqB;bT5t_Ju z&cu7NiSdaeJ(~4AVsM-yiSel;Wo+42I*|}GMo38S9kU<7?MI5y?(vg6TjYAyV?Aq# z@fjkQ%UXr<*HuQkjb%xk4V0=a7V(`8^Q>ae6*+`;*O)=RMdn{^TGfvbk~ zv(1|^>%LK@^By6nW2nuZ?V3K)W~oeVXDK~HUTbR!iL^R-CC5B#Zl0SdhvW4o3Vs|L zg&>HkC`#y;U|*S9b-MPCYQq0@XPqowO5WLy+`%e_W8{l5JNjxFde5H}< zV@A_N2_lh;xBK^H51r&+%`NO&LKzAOg@v}8PdCxjVvwAJO^^D z)hkP|Ix$fAPZ^`ezv17M=2#~4ew$uevbN8=p--wXdX{xH>PWD(jVOhK4fEL?{PETv zC*Q)B0Bt*uD6EWhzcsXkB)7{a%Kyqgf%9@z*`b5jx^$srJc0v^I48m4JOszTOZhW& z44DG_kl5Sk1Jo4CfewVVy6Bi8v2Tnk$}t(YvSI}1jSL-dJxmCOzuiVfMUsq~J^g|q z%6T6$FlH_!jb4AX6vfJ-*zlRfdnUXbTmV*$v7O%QF0 zr#2M1VV4kMmO&-Py%PmWSgFnh%O{!{m~Bb%L|XUGI4ND{VIqscn?=`)_8Gya+|9SB_yyKJ1T=UK=oAc3>X&o=&RQvQM>jD9vJhBFGfH>|OQES(glLSZ?{+{b8!YeR1P9DVn1#*hbfS zErMIPhdT;G>8m7(gDdKbXChtQcw$98Bh}C=)IRx|j$eaLjJzn~9%Dj+7%%xp|K#** z*w{!K)z`YkVpchyL|(&BnRQYQ(Apug(-`=xa_VU7e($lbYFJDv%6hrP@Z%4zzSCWl`%FZa<>d zql_BZl3I!5Z&?&&ZNgFv;?X-AY?%~qK<^|Zg1F^7oIefk(=WLi8ui(B)a>0btOWFE zt5(e6+jH6-RiD-SxS@db2NyCMjqr&5_&=^c0%-SsVlQ(SKIC}TRnBm1r4ITkumOd` zeWZri{K^DaAt$V>OccSdOut#K-gO=4Q;QHD?)BTRP~&{^WQM220DLk7zqSRXa+*D* zP$l8=E2xYEC9_a`&k3>1662;TMwsGhWa5^KLcH1HWZr1x?uQ(il)GJ>tHf+1btigi zuI~mPo-kjBIPPlEAU$>Cw-4v8_|0SJRUfR@K!>fWUm^}Ysti-fbxH^|@ zrMS70)f@WlwdDeS?bZ?ObIGJT81#qtM#i^uAG=H9LL^8RT;g;s2h)i(`dV2*BUTI} zP6~bXoQr;2m2fs^=V@;+VNl4M!Tp->*E(W*Fbdj}!FV8u2KmKejvY}4qp<^|Q$%2C z5J(Xn(^{r6ns05e1nKKB<|dlB3#hya=Ljd_qGVELyxQGccSI>Q<0<{XRNMA|`SS+; zq<-i!dwkJ=s7tbCCeaXk7t9m6@xmPa5D$YNVu%5X72w^bqJUO|>h4e3(DF7j`@o8- zv4Sysy7X42XK-CeB!!Eb(FIQ7ogVY)MB0c)-M-J)$TNh-{R||Ng5H?IiLlrEQSEy9 zKr+VO7mUsscYMBnIo=mXLdD+S?`lu`(6&C&5i00{828?4FQzG1o6;``{|KUNB~q?@q+b-IU;fpLS*Bb;NWX}XeFP=NM)o01-H=3w zxd!{X7}5KnrHfNK=If8O&XrJLp9kod-RvH#4GWlNH7P=T!~FM3p6NJF`Sc42Xb9jY3M5C^R=&mU_E0R;*-M z;8#D5EshcJS8%#$v%uni!>f8^&y!wSrV9Be7?QU>wr}ZgchdWRI^Q4y-8v=`TCeg< z^OrAC-#weg+iHS}r6gFI^;Qy1wc8(hwpX6`hp()DTg5Hen*IGv;+4%4XO#Ig<=REp zoxf>qKHv!3PGxcKXQkodCBU&Alh>RRJc}c221;rrS5$#CKRnF5rK;Jc5=d^O}WH$VA0LSN-d>VHU+wmy1-<3lG-H zzz_oehjd*P)q=sO+R+Obp3m*V9~BA8O>KmxZ`YBXLI4jV;?AQPj)^HC3s8P*PP@x7 zG7GGB6MB~;o~f#WsiXEPOB1e^UR9lSTAt)9q>hgrc`2c~lWCL%^XD3tZCc|XX~ zRWR$cde9>)Mm}4?Tch6UN-p%r<_~S?&B5lvw8uV=8qfgmqrOMGO7QhiW>~P3-+0vw zuK{gKn~TnpQ;2;rJ$BaoKL}R38?oJ?GIX#uWy|HlmZiP4_#7CvRr*s8ZEGpgR!v>& zA#GTaMhte8NL4MXXO#f9IUay^RbC=Hz<_Ov`0)m15eK_N|Wc%jUmQ57`jOC-j2QRk8^U(Kv*F`x7MA2~}o0w{8uI z%HV&sAUBJOkPZtjJD%$EFe0LzZ?Vt~&}^G|7w~#~($yH7dF(ceM3&-VWyS+rwdzthiynKlVBR^PDQc@OHxe_dr5iCPb3neP4;?|&)IQ4HE3KC!QhSgq%e&tl7_v7tH4+-+!R zdl;~~^<@!VtA!^&J!8KJu; zs)<@r`;2@4{qV*`i#xR8ii88PP6(CiRc6iGKsH>KW|(Yo!MnXHXanB1@XWq-lf5SR zAZ!AhDfu@;%$CY{(~MQh@I2(;KKB+BCXL27A{Okzi;(c5coxn{eWZ@~rZ3QEiV441 z+*nm(PygzXY2|~}_~_u9_~zr+z9$j_Y~G+qz)U$6`6m${?K9W2)jSN0^IwushRK&B1OA^`_V-V4~JihY6MMx6Ke@Do1IG1cn}BD5I-P z=$|rFBDrZ0(b2_BO_X6nl6Z*3erlWq!P>4H3AJAQiYUnJ3X=6ze*V`SpYiYKj~9qQ zxgIE`Vc)=2BIyhSrBUI)Q6L%E*0jYbJC+oUED6}uwsUDfqombeGJ#7;A4+a2qU6L# zF>lhEhYyH`18-^*AQ2M<8>`LA31+DYaZ}qN1v!Rk1u}&iCJetksO{?vnLZuc*Op}V zwA0^f?b$jddzXqk#IwD-(k$Fa7{RCVrM$|SB)=zuq+1+2$%ev#g>5L8YVDA7zG;%6 zbML->N5=Hd-ILrkSd^ZjZ?}K1&g%dm$SIDJ9|RoT16yzC&OqEQ6$}a*BB~SJdqU>Maz7CY)#k`;Sx3#`=JaGDV^7{%Z7n;EDRK@eg0Thnw2X zJNofWF$kE*r{z>RB#^T3E{L#E&5PnS80npXHIIHnb^(8nI>BHSHr9x?zYGBS@0(!B zZ2CDG83@SkUps*1f0ZrsfAM6ry$p5LmIW-BlQU)YH(irj`>JA@}PF&GMrYcJ>eJvQ)qcNVipWUB!J7t%J;8B z%q?pH$);AF1PN>_R51a!GEVFeu!F~J%ssSb`pV6>QhNT22`l%ideJSF+t&{dP#E6s zii15=QDxX*uI9HL^*)vcaDy!Wvq6Qwi^ldR_c*rEPTQ3z82Msec$2l4yJ+liq{F?s zOApN(8V-tu33G8+Z>&iVFt$~ty-ZIcRj9hk4#rVqu&~(CMIOO8?XJvbpJKz){q%tL zR6KfUYIKl&8joXooiJHRzJV?Rxt4UPtrIFrRP{ibcD7ANH&R%KO;oPJqDnRYwxMdV ztOXi6XJ^GUl&d|j$kROmVI#Sbl9!eiHT9QH$*#MsUcI}k0APhDy|)djH$ zBdplS1WH)_z9#mFBB0y6fg{97B-y@_GOu-AKj?;XP6eOXH|OpceP0;7Y*0$yabQvl z^&pTj{_p!2QQEpf&nG%V|K4$W;w8pHRqdtX;nC;kffiLA3O3DNxJu?LY^Oal`ZU}q zq)CTQY8rAlOB6)-c+H?5G*|&ChPe>c&{p9|Yb*^%3X!J>Dsyb(n4VtACKme=yz+{A z92es%%(3FXj%ih7Nw!j)lf>32yKT#G15TS_5S2Yw2F`t0aNdMu5%m1}Kn>nR)XoE| zMvy2^ZkZsZgUTr0ad?C*n8G@SsOX9O7pEFQHd>4NIz>YeRHk$_NcmzCS<~ZIlBxI> z${ZpV5n`ccn53-2Ygy1!UqfpZ1yEC=IKQbs zowPD;?R7Rvphi|sc3myqjKiEY;lJWUqyb@-H7hoD*t1B)7WL&s-4*P)8)spJwubw- zpCULy-+^pMs++9fSz;Y3$_A{&j~_Z2dNP#A(H@r@x*R#t4W?&??cTP_iN*nAFsDK= z!Qq|Fp^z5lBg-oQYFndYVOwB|pALE1S!j+HpF&XW3=Q_54uu$_QXFFaGgMf<&tw;4 z*wfB^5Qa0`p{ow8w-f)n#5L#K;jp=k7J8O@Ty;?S|c&*2-eH+!S1Po5H zy2jSu9DCXNZ``^vWLCSDcC;He00)&j@iVJp?DLfwVyjq?-Jr`}N4%|q0n>~Tp5<5F zlrx+(z9nX*pva=ja9rw6$kO3;9o0JjW@k0S_J%7t+1i-EtUqxyj?o3*OsPT9e0M4< z9ptfN0w%Ez#liIsEr2u|o{tj@$c4k0K9q?0UfjYDM0xDGK96BwaPeVG_Qzb?3MJJI zQ{0stLsUsfnP*~E^NzIP7UQqw1I@w#BB1~d7<+rtSIPtk6Ri>w+C@w50yv7K7?Xax z-iiTG<0uHUJE$><%=-*j%;q{@`ukVB-|y;$AP6vH@r>QPpv%%JmXzEH4P7Z>QTX=pGW*cMt5?8alJoga{7{ zjvfh%zd^$-NwY{Hd>9qTo9Sz7G9(A*1Rz@h7qhoRA;W0nz9hthn)HurmXGeHbva6{DwhEkBwh;{Rqab&jiZrj)m+1ahgw&Bo(;| zIE4$Me}qcK-ANVQv8sWwPf*fHPW;jxskwg>NEl<>1Gv0`OT3YXz{!}36_TcKq`FDIx4)R>eK8g0-quc9=l6P?$eRQg0ll1L zVZpqjr!DNow;APo$h^_Trc}q2i|V}mZ>nuyzYPzS zIbU!v2^1uh6Q*kBfd5kEynDkOwCy!k@|L7NC}U34HQo<44EdM%wV#1k9{FvDIr(_v zH)1*7-Y~QosDj$>jNffsdHmH^w9WoqcK5Z1$Z<&%&0!&t@9oONo%sfh}et z*%I%YKu3UXig!%M|t z3KJNdKR%uL0@D(%)k4I*lN_dNUcHyvk%(j<&BTFKRJ_sCUBoL=jvK#RxTkSvEp8xE zImBRJ{O-qNl_>){+O|w^Utd?{P*Go5S5>>Nx~#dRcsuCe8TkC?k777PzZmd(m6BA2 zO)V7_`OV-Z;#RIXesTIG68WgC+TRCKTk(+J7`NC2rI2@ei~X@S;NIW-TAoM&HjN#B z(>?EgAPLdCAp%4L(cCVvvvsOg>Nf_W1t~(S4vz-v4y#n=( zK*ge{UGrHE;*C8KeVipDz@YN>V-b(T2MGr5=y^wU4z>@PN@k1+1?JMAewmSjBJ7CV zpd~Lb8D|(TW$i)kj8pH<5!}R`_JkooLj_RrFQiIMl>`PC*)oj~e7u6YSF$=Ncd&5*K#M&j;M=>rlAj-+HFC*Ou=`Xv=d~tkDF??>})kwQH%*1@! zb3j7R8CZtmr^)!iFLaiSUR(0HLr~?xe?>#*XE6Uselz5@beGaIm$@DkuxOK=saEin zl6_7$N&^ewdxJJ8%AH5!?4v&Mk<$$-M+j`8<$Y@}wBvcA!|x*kdy~N4BMe={vxZ`75_&Ctzlw^}qO_M$vL_kW*yTicx zl$8B4;J%KbbSQPc-=p}d&7RE72A#>oqpZzd$*0nHHC?dhBlPOVqpyOOtnhACQNgvB zK8mvqhcL1xXy7*&{#Ws&G1*>NLF>X_+Jr&%d2TXLoM?QD94u zc8${HC*3wT_2RYON4lwD_5rZnO}qAK`jTwHH77k)Ui5&L1$T`pZ?_?ee)mX;5t5D3R3vdoNcMgJ6M@$70eQYFeh&4qP;M3WLca05e1$=wi;wo?O#dmSCn|IG0Nz0R_K++uw7vwp09 zKW+J-ukJDFwB`EKa@7)vvr z;k(TQV&IK>K{^cgmw+-9jqH`U!^vso2YP^p?|qq{8of1eRg<1^!xFGr{N;yd=Q%Hj zAPUkd10pa(5-yMq`Q6<+tEB}8jQ0H!$;*!Xhs}$BQ3*Dsz0_lk2h56m)FvkPv^eBw4u? ztxzv9r&)L=^YkcyDw$=L1hzN|6e2NgrrT!#MRJeI=e4-M?Fci5sheqD*9Yi}48n=s z1tO`Hbkc}>p&9=W)MMb{qLd?`lInVNDc7)(i^QrJ3e1>2D)9Lrm=*?{f!vQXW8Ih{g3IOYhl;c&LDi7VQY^m- z9lYA zH&U$6{cK;`=SGJ1#;xs0hVJ3LFdXNqlcBYvzM0IxJTaf|>CW5dW*Y337}mHLca9|z zIOk)3PoJv`m;)ZacMjXG35U--HZNp0L}It8$U}vB?|tNE@Ue#N4$HO?YbU8>ECKg- zdBq9tIlOH4Vn~+QuK6^-Eu2G`iXEU_`>K6iS%U5&N-o<6a=FH%enV|Jes=ES|p$_9Jz zSFM~^@_-rEr3+N=k9)^QCQP|sog6_3tONtY=x=yDcS=zHl7aEsd*jyw@zQsPAfFZB z@@ql)DSNw+cge^zKd6kDpD|!R@`3q{oC0;lml~Gdk|Lx43c-zAfLhDeWUQ7 zO<+H2fd*{@8f!rY>3jV!cbDjIMm%@f$e#+p{!1VPO`!cGgSXIkJm_l<5Z{8qKO_SL zntL+W0|YX6P?C819CN7(9(+oqkl(!!q`*O0)za~k@P!+bit44hhKM}QpcG6`H#D;7sl$?VsIcJCO9A< z-T!q!|NjI|HgNs-f{?R?+dtDpm;Vs7wW|MhSrSM6W`}Uc1;+&qN-3s-bi6W5PAP+s zfC9|3$`@q(f(6$|T#XAR=}ekY)Am-9zP&U!i_e9c2X2>*&!3Y0sgU2TGhLOE{RJ4z zTc~s2_5bL7?ep6R`2M;^01A0aG@5FPo_t)1QQ?|P7Tav1*Olka%FJ)0Cu7m~8CsDo zH(YnwQg=}Sm~^D^v1`yDZ*7c3=+(?PEx9>+PdZbx6Tk*C)x6wGXKBfn^<)BRWBnQ_ z>qiEEN7hP)J0G~ApaP>}8ztQ4e6d0YaMeodB zlrD|ge?Zz?iTI<~@+0el+?UP0u0rY6)aDo;oT`Z?3^N3c>KZl=fE3B*G2w(QUsRw_ zI{KN)QFH3#Ggrox%ViNw>zbU;L=o-Lh*I7`?!ju~h*bVrEL2~_!F1>T`VCk|;*$zq9qo);r(9U#56cm`0YUeut$zZd|ZE1DR+=R_M z9zDC>?yuRS_tunOgdnSOf(=C+mUkBO%t{=$4DQYvDOQnyX9slii+MvboS+O4iYVrQ z85Qn%A%zoNnP=F;k|?SAVO8U|T}*5Gfnd>Kz6^a6i==Z7!0C@N2U5m&%&iI@ULzwHA6+kjc2}Z_ z&lFy$RZ>b(uX-PfYN-tRWtTdm3I58-;K;4nMaf@pqT7*OMtZX>(*#NF31>oFC4uFw zs*nbAp&O^XgTl8InB!zOOgq)IrZlI4m`Uh;Uc)wQh#5y;zeN@%RF@pyVitthI}18N z?Fl*#e$S#BNkG6I*c^S)pWa>;7iF9avyZ!DzSbZc0h&>^r3j67)W`;gr#px$51l0j zXq{E19)vUEnVzrn+TVD7BS=F&;f^SrY`7_0uIZMdqdCMYgxZXE7M3ON0T+sx3xpVJ zpxYp2l>=hfE4q6{9$qikMX2-qv-@1f2^9eIvzTz#F3CHHF4QyLV4WBZtXm8NPM7iL zgtC_a+CyMK0yg`HBd4Y0oJb$DmD>Ef!A?i@O~B|(2aMgSGjEhEH(g8*a!&=2Py-TI zjQ01SKApf5Jb&LDlH=|#u@>F@RXUOGxQ6SXWMaaI#4ho%tW{f~T>2Z-J>lyjV3LBy zWohe0LKv&s_|o?eBEwXMln2{nD$iN5WBY=HKlsKlkrH3fom}X=l$~S>#(GkTsgGET zXh>u?JYRodyPqW)SV42t&7`L99=`f+(ksB>d>z7RPee1M^?+HZ7r&;MYe7e$7yaV( z0E@snr6$ZTw1E5C&-0@fs2^fT_ArowJ`4%wwTo=cX* zc+`7HcAVrwF-8rkmUtoX%1Mp19v^{g3qb1r7qc5m$WYY8<6anz;VaazCaU+by-@%! zAW&`RuF&=!{$>33V4{a~g$TMaz9CIJXh(>4Y#mQ_E9odCqb56a|7)X)liY%8M-}mF zT^~9I=qyT%T~yxG7>Vt{yv$r2e3E06I9W2Tzs_JcE>?02Kj{E44WSJMjL zqhWD7(>+Jmxfe!Qzvbu~Xku9A{l|QB%M-6s+!shSK=74}z^FL}_V|iD=L*y{H%7Ip zBc~U%+?;1?=B3KwqtC);xfw1>`Eb*o!hDEZnBSK-zc2n$3;G)4_3k-8$%*I7K~l|^ zAs_gYP;K;+@9sY`q|ByRB6eUvK$`GCK+OM7reEqtwr*zPR<^GHo_n;a>1p7op>BI1 zLzbvu6C$H5pnn);05#IpO1~5Ric*rc35jTHl^?<^3&mP76+X|_n>ILzu76LeITlHu z`(pTlx#;9b-ju>*Y)NV5e4Ux)^SN!l&E;YE{(H$47(HK%IqHl`(lI*>jo{(gF&*#* zuy`e=djb%4Zro&Jl*%{yMV~sKHV1rLm+ueEd0r`%DjOCD0(}`66LgWzyegJ z%gk#2w95dRtJ14jS(q8&;NR&{o%m7!SRrO)V#B?giW z_g=2?01o;2(19WHQuo?jC7hGfiVUWiuGWLJy<5s1R=c4>A2`#+7I@To>8y42bW^yS7NTa4=p?+qd0ezobV5+$vr*e!{Zaw znPQ`<ka`?b4>NbbIoFTxyP984J8LM^KRU3DGUk>(|&zS zGktNhuATiCR&Gm;`FHVTT#UlIdfynJhrbnZi_2+aW5)PvpQ|WIC<9#8;-fAaSD@>&EN%}$g}l2Ya{d5wTJYvX;yaPU5B!}U z=Gr8Ye+SBO!T;?Q>5?kmCq3zEV(Nz(HKOP!WJvj$;f;xNnTu!>C?^_` z{KraLJlmR(x0Y;J?ul(-SFofznQM@7vnrv(q(k&m*3`EjEpav}FJXeyCgT%W44@E^ zzrzzBFny@d_V;HjFohA5aKB7JSXo+98cHgOk%E-8G}JT@%=?lLDjqUZC-Y8fzAP-z zGB6OFNYEx#5*Uh#kO-U<%x*$r`i<{!p-u$`mxMgm;Xo9~U~n8G*_UY&BROw;B8NuL z*R>R{ZOOGMOA%C4Bd%eI<5)gG$Yme?`?K&X57v+#zi>Cq?~DFr{#Fs_@*U}2kF&(! zUDxvyp~nQx;n8LZF2|Y}ec*e3)kU{yoXqE6uhH41br!!|-rrTLE>$7T+pms5E|aV6 zKV_CI<)kiCCMQ}RyU=~e9R`Oc*u|_IA_k$hKYRZn7)C@Vj{f`w1oVXj1Z4BSJ2t24 z>?LC4YGNsFWp89_>-FzOm-9cRkI4XF=>%atAh!Y5Fi}NC9Z^Es6+lr5DvflGy#8k4 zxol1*>O~xneaAu6LEoNo8}fd^C+X>%gjbKl4yh}axAyZ|9*aYg$F4_zcaM+5_vhu7 z1CaVYE@=Bz-%;T5Eh>a((_C9k;eJVRW+(ooAak(}_>Ps$d!W%v=|k3GNe%99-}9sKEl5=GbTxN3V@zyM{VB;ZdOQf`(B@LujL}81I3s zLNx2}($2~1LY*00M*3qI6wG*WarPt(z;54l#C%`;k@=NqacWvyG=0sU3?iy;6d_MO z%}7JzIT?TSlS3wJp^0y3qcZww4JdFOlBgDSxYGMit+IQL&gh9SK zS)Rr+vGCspNXH=Nr%6}Ok&856xQqi|h zT;KU^@>@4opurOdGtZgwsXbm~XerJ^lGHXG;~T1CjgDic)FH23n@3kGi|l-ub`Z1G z_2p7UHy4;-tgZ?^s@|bY&r;^IEbv4~N*6ToQt6;wG*wOR>j+73*JI=X5)@?#2I$jR zN9khE{WQ_gU8a~GDoRMT=CEHU14$UR3BmTfl)}vtLSW6JkpUS*?Av`9!s_}%OnQ%X zlc{^qf06%4qFJ6r?jfBacL=KikIOeM&{*?5Z+vMN&Z3GHaFf3u;i?hrlGYQ5gs76S zsMq^+wo?~rC_4QntMEdD1x2RDPu9Xd`C1I(-#lorXfHwl?HpaQ!pzrTc- z7r9561g(n--hNs^0DfE){uZ3zmsqHt%->~pf-*bJsVdQ;Z#40f?-37eA($KhS0C-_ zEfTi^l^-S$O74pu`4cCG>9CkVGHb4h4iljZG~Iq57ctY#3T4nJgqxVwb8De5Y)gJ* zj!Ccx{-Yt16dX_XP2tKAT0Vtnrhz1CY#qVKZ>IT(!1KaX`Q@^12-+H594RF+{FY>N z1C|cBV~hP3tro#Kg6>1R`TDiXMB7KZX5>|r?RBeOZF$nR)+Sc`2LV>*zxZ&4AL z8SFB8iaFPY?S8T)$EY{Bcda83q_d?2rg-bN3LD#^0IJf$p_kPo2w(;L#9ffJ;RS|m zubERd?TcS`D?7+RBQ8{DPJpLF%@2M-^XG z4vB1Q`hw1GZh!1(aVMFw&{h)L(9kMgH8}jWq^K&gj%^jUw4M5*Rsb~iFkZ)!MkC;8 zG%~D&_}V0`p)M2-qfrV(1_w3%7KA{vLH9(YhD>n|1b6{gt~Rb2xh>7{w!WhtJDCZa zRNN2lW?Y`&kUd{KMs`bW2-JWCmtns!F?lW9zAv=%;ZoP*9rcAhzDU8as87B{n7|0Q z7Nj>$v!z)DlXrie0HB3b8U9&KRk(MpY+5PuxBHRr6tvKj70r#=4A>f_Zy;8m80qJ1 z9vs(_!aqKD9A$fi-5*^c{_Wujrp@@D{M^VML59Sf60c99e2al_OX%t5+@T={o?qld z(y0L*$^85g3GIt7h^WGW<15+B4xV3i=xdk4zN6MZ`8kMx{Er_(TL0wdl&C&3Q~0#g z?4Xjqgs1$;%=#0WjFjs9){jja6XKE_`Us@*$M5Z-S0bA^zq(H&wcmdxWtNj(VfQt( zNm?k}_8k6u5k6j+x736L0-{F!*I&f{x9cSkz;+K(P4^YEK0 z=Am$M#X=-J=!__`9E^4GNLbSdw`f#bVf;Qw$Z*fOj{|C!{dro6O&7;@`yYC%=&S)k zxnQ|!a(26{I;*Uk4}yi49FN@?S_J+(nf;F)060L$zrV+uPK&>}&-{=1E($Lt3L@b1PLQb z?Pd=5qM3rrx_rhLIf7Ek&^30s^j8g6$z?O7X2pVHYfWxEskYLWY0Ek1EMP^odC(Jm zEwUR_Bq$7vi(Ua998Q)t$cPf)b?cc^C0E-Z-6XXk?@)S^-OUyW^6cKe_bXNJ5rSqN z1{-qT+_{qL&hb_krqX^T?-}j(^qf*f6nADljtxp8;M5_MLXw7;MXpu+MMmIta6{qC z*dUY)a0ld~U>9je z3<5V5w~rRF=}xnr za6Xk`e^vB0yAc=Jfw=_FljV`Zc}(;3WHw>#nRHy~`TSc@;8ToIvl5H*gc0^zg|;Gx zRfX%WZrEa4{+xS;4>YC=1i>)0zX_GvaCsFLXOv^>k5BH2V63L53b`I&vZaz0c3bxB z4tVa$i3y0|qiO}s<~=)$PpRlp9%|F%g77f(;{MJGV{eV$DK$$JIddE~E{anKKh0So zRD*nh8c8ExiwB0M$sc%jp#~~OrY{K4l`3G?%48*T`zmj&B?e^jarCtjP}M%QrsJKH|*_SYz#{Y3NgeTXlXAp%nv0L4}jo&x+P zFSZ9~8GLWM0^Yz()do{(tvAy+>*Jjj#+p|ru3Zj$C%By%-(d-Hg!!iY7BPa>ed6vg z7esr|pRMRBigraxUnLsgamXjs19Phu6J2H9L4fYSJJ6f>~n{UqYTjN7{%sP+sZsA`SgZ1%F{SWhXfe*cU*{J6PwALK!#*-jMg+ zmTnq^_lO$kh12Fzm&np+D}F|u`t=qCh(AQJy;-g3q0Mb2Fvw@?pmOjT8U}yC5y~0j z0I$;-VDk6niI6~CxD)e@bo-!hPyQ=Ds8q%$C#I}{x!v=G^+bzLN70Ipa`xVDMzhQj z&t-w@1wR1g78}_~yge;6gND^Dod_(uh*|}U+(C7)kAjejKaLXfH`$dYv)wXZ!}@0+ zwi44}?2kE+K*>Sl8_{X7v73@yegMjH(%lf?w65I){!QFy;aCZwH+&#Fv zyF+jb4#C|axVsnbZXv+O+@5*UNvCIe?%Ve(R@JIv)%orJv!7#iWS`We&hVp}EUBxS ztl_J1kxEP0=~qE=YI^EWdv)BAy%dM0r%HFE6(VZ{=G%3gFOP7ouWxTVz8WvTRFfEY z-fyFu3Ru|BY2`vzs5e7S(;VN$h_6_*A$r$hKNQ z1mWn>x3w&DI#7ZwsKBJRMe<(kvT@s-Oxk>3?M=aS&Fe@Hk-9kKyweFBbLFONjObF@ zP~7e%zEIUEKk$Pb!pJPHmCZS(#6GN^oKA>W34yvT_?S5zDR^xx^ z;e=e!_$6eQt(AFyLM8LFaWOIqyDtP?W=y)??FdSmaYK+S-l|!0H<9LtOIS=kJWML< z%;eFnij@v%$h3}zve0)cm|s2c6f})&%HFyzg`!Bk&8zo%NzCSTY=weV7plxXrF)7eK{zwIE5%*Kt77*=|j@OW*nEdcBm|_T+v6;U!kmJ06S;-54eHIykjib#0htg_^QH zoKtQSIY=j%b=B@}i~N{UHaZ*#0d~}^9Sqb$xgil`G@Mq`t9{9=mok)2WwP~#O2W%8 zx5|{UG>QS&@W8yQ(p>Fim}k65tuZ&b=Q3t)RIO1tB>{01Q%J#%k|nkRNXM+8UdAR& zk;XhQB02LpbdTArMnhF^FWW+Ox?u0KA=>$jvWu4F!B~OOo;JS>W2u3!g}8AlgJf57 zJfavJjYQN=38>uO7c5fsPbE9aExB6~)iEd<3CxRYc8+om%5b(;TsTC~sRk?hBSlI{ zlj0;$qw-m>u#M{(WuddB-(KZ&UQx04u?zt(9}Kgywn;b?Hl7$;teM^*>#|HH%Wbj5 zcpzFFbnf(OCX)8ZSB0Vs#`5@)kgL?OebhOP;yN`4KVlitX6^78Z@Hd9=`1;Zrs%s^~9N zFO*vFM44YlLWnS-jk(oXolk`N^Uy#Q$hV3kmA-YJJL)pu#nbFzQLihK#LG<7Lgz+S zC#RvP+IzT8Pr4d!Zqr%e(-U_!<;{#AEiBl znJdzra-qzQd{bUp19h;&@uY|#aaHmBVqbatX}*n0tD|0WLZj+{BzG0@;Z@~2ZnwI% zqvzRN{`px^nbU`c(@T1`r6i!4cvNwZ+K;bDWpxjq(TI<9G~t<}P(P+?3E zXWW8MCj|hq{@wmswg==LPX=4j%O{W@D>`r<*zF5&0H6g40MPxr$4(U-O+M%u|LX@F z%ddt^z4$ffa9&FhfG8@4kjM2b$i~I#@`X?Kn{jFy0*7+F?#t@eTg&TD^5R(rZ0^KH z&LYp9R-45{CrxCcbB@?Aw6>S;m#>yxcgov5@8H@EzjazC8r+aTlzNcyeu;IjH3Us@P};zy4*X;= zXN!bILi}1FZ3B6Po>eozpwQL;{#?t_PGN@z_NwSL;|GN5;lmmXVBuRZ=M%(PP2>dX zab!>OSOUL?QrJVtE`vq5Noz;?!=OCyY9y%v&N0TfA8A0Wd}>Vhb>HP)+tevxV~Jza zu9_@jsaBKBd|-OVy6Vg^uE6B<`O92(CSEAvC;m8{hmRjj_vI@95`>>RjSV>89 z_fw3J>3Cidlh`zlO4>5$!09{oS`jIDZU5=cbu*0Z*szf|%!%EgALKH1OHZ#kJ{Jv< z>2#BGQuPZ2F;c@xrXsS?E5?d~L3!6Bi3JQ-a3g@0^1@EZi{87WC1>IsV4jAJbMn$1~Xjxw`BMd!&Y=C z@v>v%OqOxpE%+SCj)fBu5{jS-(#t5GHeu@8S(p*%Z}PaB(8v5P;}?4#aB~a zKDOfmZsKD+!?ML<_!f<~-dn04ONeUH^~Rn8sYDFkWCpf*p$nn45btdI(JkTAylOq| zCV@fNFu>BvjfOST1F?aYJyvQa1)g$**DyMGv*v+V-8m;920}Sw^m2YkqcgdbJ5BHQ z%&_#fmPN|mCGZg)f=B8Ngq{6XobatO?jiUv)B7`+UbYWIm<%SGay_Hps7U}>JV&)o z*_y=q0^KQUR|t$BZh}Z->dkS@xuHX(g*p?7dsjsfXi1kXNsmq1jvyH9I%f)Yv&#;f z8h$wr$ir)@TM%C1Q*L3ZvYEI+4z!Lsi~>H5Tg9**wB_{wGg3;hLV>5RH9+qy;!z*C z_?RlXFtEt6Y8Heqo+_rTl3uoy#a9#3D=q%^A0wr?2vZ`0ZJAfZC_^_M3Vc_}jmRrs z^V?c?%JD-{VW5n>Z@47d;ImR>y?a5?tcrtKt&#-0dM*AjQaYqcZB@eU)|;wNx{FDM z>a}Iuy`|fnwTxOHGtzxbxuE3CnWe$pqUjw^f^I9fMu0f8V;#RQfX}fZQU}^VN`X{jkZDBJw9G4X@YaG5P@f2HUz#-w5+t_Z*+tx#px6 zddbede3FsL$RM(edkrI+CNNnCm0LqoUUf2*gJsG1tC?N42jPCS$G{ z#az`U0)z^$O=hEed^p5g?_8Md;a43LDn=xM1*_aLQxi`&T19BhMv%OdSXqn|FMeK# zXV<4$@$lT_QXvBXHh+7Q>ujlKY-?p{;$ooxhhQJ9zqPkV#e|T+PzazU*(Z^M_jRb1MU4=O1(L-mE&FE z43jISn}f1N^PSt77hr)+WhUy?`VGsqu{#k7xb&KngRpAC<$CJdeCdNujeQqO=bwe! zj?i#hl|l59u*vskOVbsrG~{IaD>UdCMET0KJ`5^;%-%|NPG16A;xOek?5Z=QhY zz{FU=4XaYhN;A3GUO|w)Qh=~4zdBOpUR_18DaBSC03X1vhH#T|_4pXJcP15)6U$J% z0q5G)0Or9cnhxw#*b0}TG-V(Qf$qkzOhGFMfJd#ewntG)Dj@Vp!CK(X;M%)Np%vhH zz|Tj#b5x$8by+g0B$wR84ww@hfMxvbllDza?#p~92<#v~wI$?Yo2rxgBrKmQ(TkeT zfy^dZ`Az98HGFXgXXA6NOn06PmWYqqo%FFEsw1b-ayl1$cBxJfiKC6?Mj#6Ktmv__b zONI`(+@X9~;*ES(dhV`OlJq5*4_Zj3Zdw=vpAcM(McpC86v)oB1Z(7Foo-kI8uVh4RL2 z-L?uKb8cD0ks!CV8`p4b?9!7^8ZnIhs3_FxyaN zwGLYCVXxxkbL_*YGE=i$t9(j#+DRF3xOneSe?QU1+E`&^nd> zzAZ##4Km+1HZH6%3zdt*k|)X=x<+ycB2wfGBmI5719S$Z6J>~iXyK_W%2ImPHcCP$ z573VP+Bv{f^Q-Yzd?W`QFb^yH>P^>?XH${_4Xg>U9LOAvT`sXhIvCZFluZ03*Gp1* zqIfkkSrA$G9TviwNzg#uY3=DeCsVVuT6?6Z+6fqYG)kyg14hUFhs(!t!5pAK@Dq~3 zp}wIE98w1v?8uWGJa6n2Z{jLf|68kM=S+lKPsrv~?&CDguBn>Uw(7Wj`Kkx50X-Y-3+6K7IP^|@Q{rH}V{_{&!n4z^x3>*% zL>W+dfd%B!?~RuTaO_HCglupMOysC0mL!>Ox_X6Ip0hl!qK`$edmc z&k6OdElQpVSXAzA4DSqRrg%^pC*)m< z3)b~6nL=k65t}dzT3aa%q1IS%ny}K<+s(bsEsW(N1>>bpic>O)sabdS$7)P+2y1#2BReL>2 zCKB1J6RPz`y&z(7r}w)kc;c*Dz2il&Hw^9tWSBBsTdwa|tLq2l2lnL^$=V5(B*>TRZ;1WcgYG{^ebpW}>t+jq@Qd6c;=R68$+9+~78H3yzE3<#Ce@u(2l01QeOxH2QkIwDbjyM#+0sv%H007Cqt)uhL zFdG_BZi>^>ygMU)$*_?$Ixv~9VKGPvyb%)s*@$mJb>R4YC2(RA_20jFWkgDSY&@-Y z(}-%ZysuXCnH&n}Q)_RbX?kQ>QxjUFR%~B5-RQQ{dVf6VYIQuM|8@OjXQ}@_;~~B2 zGIL5O1|H=M82=jd_Swxw&zKX$u>oUdHoUQ}@iS%?^e_)WbF$`?UL+?|D0f!M*F4GGFBWFOAOl(uv2 z{Tozip%geN`^p(M{!dz4n*3JbG12;bc|sE3TJuR`Dzec)SP4-ab_**&h{8VAaW?XU zc2BkyQlRdQ^`}Hg1JwiQ=dW`M3r$_56eL%R$PxlLIj`ob_xG5YCygY_i_$stSoqtV zqu&uT<98@yML)`e zulU+H6?pauFCq;wXRhI6VuJb9drC=|w}hAVm@>C&NeKeDp~O?8wT={I5~eitF~FxMw5@GBP^w ziDn~V+AAvnI%qOO?<aTw5G<= zf*cAkj=3MCD>F%!l7Vt;cm!jTv}K_ekAw%GcS2-~!W?j^VcO;2MaU-fac%pea!s1( z^6Z_$R=$rdRY*Hc)$@Sr=sAZTK z>>j-31ev+;uJzvGY}iisbv=3uc}*f^l4%i|yD+O>JSbWaUPDN~iH5+N?`{sdN->@Dx1U!b?6*vP}EQCW;mQ ziwF8V_qZ!zPuj5F)Vk9>jT3r)9_EYpQu!S6@zO4n!yb?{fy{#+)%0tX?lKtx1!r6o zbKMl13o9ySCKjySbRky8k%CuWotk6I1LqBrx-B!@A=kV1tA@$v?75?hr`j8yRq2fzx->}{C( zS>4#(O3dt=-Z|A5i0On}S_dGsqI!ik1U?YvNAJ_*O$hI4zZm8kKB~ssI+Yn~aEHaQ z#P`SA+mOW9!1OAvkM!?fxyvQwRX`Gkn&;FVJ_>fCwryO|O-(c0I@jni{JM&-Gq`@G zohKQfmw?cU?WNf}CWTDEfAyrx(J3o1+t?kkKrdt}Ml4*#GrT`(C&>S%J|~l4H;@^c zb};v;%H3K5V7c`uGdwHbz`~Bdqm>#5kHEHM7ON0?S=S5U2)1T3s=g+k|tPOL%65p#>>y|?2pc+|pO#aWG73q*nP3ur~< zo*yW-;8*@Ufg*mtmeDTb%y)^x@2osKYj4l-;1M8x&Et9ioLOUE-@i~CGgLC0y|u&J z)O}I-=Of4J^9T z?kKVBOrVqqTkDLfGTdeJ9=!~*G^_-HMS ztUay`0Yf395R#Jz&Xn$_S=UHkLqj~n7*Qoho5aA2NF>M&JBiHJ4H5N@1jrr+lNf{y!a0p2fBL$;T=bdse9yJ26~1Sm6>uJ6|L`V{z1 zd_9S#;ZE`Qt0rDerg6yG&>P{raKrP8Tx)~Wn+Sj|fP+?pYekvy6q=R_O(%xvqPaq` z^3$_5iH+PRrAnVzfsD>a%l+(Lzso6iS00iqf=p$?D3`+Gd$kwUd8v@4%&a>e3buq? zRMN68s6OE=ZM4;!xuPK<-(D2nRohCu;F3Za)Ma@X6NG1x_2FRfV_Ib2RMn?-z%ksB z9CU3!j%H)mIg8F6@w83pz+RSxc-QQ=Cl29xm>#ycMJa{!4E$WXEUbvN`5UMRL5%ju zWs8C}gMplnusc9DXc4=0JtoMvPyFwli1|&lR~c`uaSYm+#GCWjNV!2V(Nv~GVc_~U z`~Z0Vd<1iEl)A*|173){ikdotkE9%4!j|3A*Y<+k(`FF6jvHjS)7TmruXN`UuAWIVF_O z@JJ$y$lPt=4J9_Pv~Z}I{fo0{(^s#I81p`+^Mmn8B#^y#RKv9>* zCn_x+pj`<@+oF-rw*xp%nbI1o ziSqHi(%76THtpQaH`NikF#n~V_rwb18wWxzwduvdWLS+HoEyP$2-y1bhcUV9Q1vWc z2T?EK(O9Hm@`2f*OQ~cB20_~L#`Q?pcVoQKEOtKbjn4bYcNXkEi$ zsm^A6j`s`ts4AA;SOI5ofb@`5a!(xY&rzmVemN4KaeS~ef!m1XW^tWN=0a%@$%Gs; zSu<>42z~MnJ<|>QZA^FqS3$6#%u7D04d{Qz%bDM9WWLDlI_DEaST6eu(PqS^ZiIpD6*$}wX zM3Ra<>*m54-jmM`H+f3f9A);Hz}Y`pytyQnKB6aKD>NUT2qe+CGl0y&eW;D9?R0J# zwi&|~4@LE1YKAV~VYmJ;ZFQo^&GK+&q4Y*IIh}(sNg9XLh1! zPxM~npgELJk%UqX>k;x~hlTrXXav!9%VI@;a=aKJ)$t3QW~}3`7Oq_c!Z170lbUvx ze8Wu)1!z)%y)Tl4%!eJ8WlvtNw5mwQ8aNW{=%pHVVjPhcv|AKVg<<=PFcg$ZpjJ2AlImdY*O?6GEAZ2PTTYxBgKT7AUM)yWcPwtw_gt*E?qVO`#p zSsAXJu<~LAIZ=3k)!M<5H*ZvA1a(#TiT4Nu)}xsIcE<9&BK8Pf>c`7kb6ZfXN4Qm> zaH|D&^wGM$YpIQ=KXfHWk=W)7xF_$E^SxVcakL#$8Jtkb0rPl19jOp+En2uIfBe(* zd&G6)z|ol=-X2{hy1=JoXSfGFJMyv9)mx~@!Gam`;#!VQEoTEvgKe1l+~R#oarB&Z z&oetbA3Aa(eY!CYT$8+-{EXY|_1RweFYPTTSaL%~6dxGo68O=x8R#x#QQmpipvF4T ztT`gXl%8N+z#Kp$iR1Ru4tRP@AVP_le7V-j6Ez%0*ITGPH_)I}#lmi9!eTeiWVRiG zR~NDCspYCIeD?Df8&E7NU4c)d?<#Ae2CJDzf;6ja68tqb%{XE$2vjn$usZ@SS}{#f zH4M8CIGifehdj{VtW2Ozm!6tB2#1I3#&v>1Pc1her8na+rVT!ggYoXK9#`4Dt@ovq@t+ys?6Pyct_60cA%&mGD#ZhK!ZNq zl@3b-Jn;f!w3-xAA>lyTAAB`1wLTN;hOoLmhp~nzGHWxp4vvH@vr=!43$ORia?D_Yjv zN`sP!V$F1APtId((=iZ+6*H>ki;jKkiv;V`bo<;CrZT7wx1!1#b2#+nx6@%7;LlxL zjDBi*weZrw+1txD5FH=8H|7NtLN+)FX1BBv$oECkXB-QtpT<>*%?<@?qXQ3!9qEGE znKHC&?dDwi;Yi$ChjM9r{LJw0UvUP`tkrkAWpeWNE7;1hWIjVn=+2&Z1)7d4K#hHB-}IITok7 zdCG{56#-hVan0ORylt*n8I7M$f33f4CKhK#THn#PeAhg6a*vg08JomjU8>^=`f&G9 z&2DDx!|NqBICWh6^or;1W{3WThPUbA8wZM6Pj;WW56Kv%D(7YO18H>UeQ54q&^N&O zQt^r9CF zyiU%|itnr5(yiG0K!DX0xjd+6)8^Wv7G-<@M3I}#*L4^{5B8C%EXGI^s_4D0SYEno znX=-uDIY!~YRorz8AoYVDeEnr*N|~^^Mc0FcN;9#wQi>4&cmS{p_=h%7IS-6?AsyI z{`IF7Q-x!r$UARwPtWe=+btuR*O#-dt1h?mkw|Xa52U>Jh4@7j*_51j9<29Qjn%7l zm~Op!1<_cpQQRni-t+={c25GHYr9!{6dUH(lUw-Lx?U~qTG?g&=S7Qs9(+ialN72R zVvE?<(5^~)qZLLC$!+<(cL*KOot-y@&QC+p4-qZOqQV$Ao9AsrqNvVaPrl4yA0qhc zFXQ*v_drt&eakZigDPI@q}1sAb|@1^)(Pns%ud@UYT&8kxs~M<>O%db-Dz@Yp>wd> zV>L`E$zxK~ud=>4xG7E`aJHz3N>K@^R!9phrewBVeQuM1q2AWy(Rn z*h|HS1_6h-HJ~-61BU~bb;WxcXRPu6<`OoJhH2rYJ-<$qfgg5lQRO~r&danYGkgs* zem;^p&o1N=szaV>gz=yx^))=SD9aLKmLGt<|5PtyC{ zCp=NYc1GWWzLbJG2+okekXfP{2qjha$Mee>gl|VdxNN_?4_uiZJvt9@3H zaUt_IxAZ@66SK`%Z?XfgXC9RW@43R+WY zBtb;5akX8Vh$#xPTGjKyv*o!Np=GN9F+P1aQ7y6Lme`td%O*nhWai$V_GFNp*YhxB ze3CDpsDZ@I#O|&#;2+~Tj$3!>QiO6#~FCANO75#sJ?oa(0Z|+3hz#}djVQc zpV%5&-x})QYPB=t7@T*4TtAg%1>>#-p13pN2sY~&KX#M0^tG(^QWI{L?UH2oLam-Q zvGt&#Rg-@euHkCgc&`bmb!V*3m!}PZb!)FbCI7B_YJKOeyz)Q;ep)n?kZzDT62jKtG*hm9$Dp_b**?3+6Xw- zyE&5Hf$N!LfiJ|=c{{H|oG*}gs3tbAUvkyL@NWr4s*Lv$T<9Qtn@c*vV{GN33CrB8 zI)rj^Kqnq>Su+BEJRG3FK1FP%?^D;h)KzjGmrzoG_0VfQ@>YS(R@p&`L&5P{B|P*o zeLS+a+{cF-T63$S>?YHVA1KctC~a8@3=2A{+@9q=CA(#GZUF5(MZcx$*?y_Ux8l?0 zJsZ?Tem)1oI;rLgGvvScp1@##@V|g&s9$Zxrs6bxHcogu^!XZ9$C-8dd_`2KUm7r)>DgPviIGZWFr{X5>yNJQkNI z?lWV%d6Aa4*$}NP{AY!M4hCWwsaQM}|wfVdU z^|FO1zlYLF96dN_y>Li!K%LQ!IGEc z46n=qd$b7g@%Zf~T2Z=1B^cqvgtWr1^dYPR`m$3@u5=nJu;?&2Akfc4H%RB{ZM`|= zL_xG8?yXzt2P6A9uG58cU99f{3*Zb276-D$*m_!e1mS5_t&O5Gdop7ebmH! z(sc5LernxpgR)XyuM_(Q;nW1(CINI*hcY~QbR}`aUn$pJr8(oSSMld1K6{ue?Zi*G z<#QAC=cPRps1ViB3Jx22b(Ic!&Ba=9d#Re8w2er1Z?Y8QM$Dv zepEMu*=^U3y*)XzM|~6tZY#V#p+kP6YR&6>qCImw)fBpYbk2Zki^Tsl#~X8qzd=U% zjTiX|mRDt6Tl^c9k}U(1PH&P($H-T)r2>5f(R$uV#Y10Wo+LQAbpaBdFgTTU5@h@y z;o?&w5}t%`#xvcjq8tB#-Vaq!F|WUn7QKuUcl%t^@nOvXna)UI0yWODsxan#3J2&k za)yo)7ra|Sw~;!k?~4qgvj+2YXkKMtpSP*4J`9Q0AX?$+21#q&bm6H>aCrr#B}A(@ zuy`H|wN)BO)@~N^@@oLQYlU#FT#v_ReO`a&!c(c4rT&!C$1|){VQu3>q-DuOP&L(5 z@pi*tD;+shyhNu0B^`F_2DZ<{3N#Utv=Qcqcakr`iAP!}Ij-M|S579-x@jFCA zKh<{A=_D4NQn_r*HnD_!J4VM^9Bw7LD>t0|bX%Ty>sU%e2jSt{ZB-2~i&L+`IWMKm zx7ThJeOpQ|<-ckvopA{7PypQ~a{d&xT-@nNdaXT&s3Vi5J91;FbOv|zb1)WeAsZj8?2vY{3B6HsGyy{aT)sXaCUHs#$T!gMT;`)U_aqTWGkc=Wq{vuaogm)hy!}_5M=# z8b=9nP=Nc;2x1;4yt#STkS>$~bL|N(Z!)eGpp$FPsqfA?qN* zXBDjAtGa4UcCoF!5MMeItN`!!EJ!|ruUa;#MNd6aGHe<&w_;vw8omapTBb{RdhxHxDgZ#p30Va2y9dJa zQ%XpYk48*dn9f4S!N8L4&%ccQ$1hvi8qxl1qU+BSQvUmdzs-=*{A-5n`_vleGp9>J z>=gY40C4d9FNXh$V{K(Y>ttbGs;q9dNRQ$%T260JUK>;6i_FDHzKl_A9%Xr`R4Id- zSF2CVE{1u3!r91nMq{aR{k|fi)$S=Gw?0M}rVt+9pkul(G<;?{CMxFGOQbq`51f#| z?)+hqyCGzI{97kMB1BKb?t=&Z5gpC(gInwCw&N4!Pwyt8Z1N~%Kx$CbDXE%YQA|so zs<6R~5WrK~b&dfEHMEJVUI$4;^PRoQ<%bAf4iyt&Vf+*#zP3QVMwpmg)@lf~5T4#G zJS~G5E4=$!(ezqWA>2nij=`i9_x(;OU77C0H*AsIGo}MIz6;$O9Lj;J8n#Qg^dX^8 z=fTW*V71?7s1ky5cq3}cGOQw{s=+|&65i`l=1-a1YBS;jXinULP$=$xKz33K5*0iO z%J!hQcb8mZ+4jRB)biO06ierd`e8f$k%6P@%!VBPHZ{h{%%Gv>TqyPt`ZmQIjjGp; ze!@BeW~f^UFIp_GIJe(sRk0mv)_INxO22qunUl|!O!Kp_Q8(2^t(MhHP9$Zwj#BIr zpEdLM#-1mqM>XhyPV!-@#e2JlenfzDIqaM&JtGwvwmFMj+ipX7OEhRE1~E;)G+zn3 z2@RbaNcA~KLT2gQrWa?T2{aud^yW_oq=Uilw0JO9J7%6=(Q8DW< zN$kqIY8n$N_7Vjn#QdF3{3u#ZUO770KnZh+txLCWZ6NeqOpjV({ITZ&l%n)CWGQ-8 zOXoCPM`%^56pn0nUB)Z}pOHM$bP~AVLonmxJ?Pn6GVHjtrxBRt`z*|S-0P?3(j^R$ z2-9VFZ(SnK)nkFJ2YeLSIJj^&RFXjG1=;a9l&c>~W?3cs#&U%ei7=k0q^64K3I+BkaKS;JO3()eFavlr@ zu)j*0kCTOB0+v0@E*%l=j7Jy}rm@A68a1G{pJ~PnJ%WBOW&bFxd#JF}=Fgujo}X<0 zbt$v9wX!y_{a|8Xr{EwnEI}hqEhZ%=TBLX?MJ+MBArDU>rDRPlr9xjVOV3pEhW_)L z3Pnh3C^`B!^h{fn5;T+}L!(v9Q;bY&;&cOaG^di4!*t@agCnElw8PR{;?$J=k`j`% zAkZ)JE(R&>LtkCU$DC8u17syYKv7=&y2gR}pLd9WBR;_IujqR+NPysTGG<3oYOCi@ ze$P*o-yw3n$$p~(PN&Q#+{(gtq?c>K=%V08~HDz|8?-q&+wp4Ay@oo`0mT! z3eI;pCF*~MD>_>7*&6+Gb&Y_rj)~>3t?U@uo|S1`_4R)?^q={zwe4?| zpV_bw008|D{1C$b;uh2Yn!eFDL{|T-ejDuP3`9%%*XgXkh9`5{uCqSFiNSu(K#Uas z8Lq5j`O-_J{u(|yP_?!6T*~qxemgkq zJDiLAFTj6ukAl6mwUzCMUt3u7cAQOlmLK4;yn2Q=zWD7o(04eu_`hFl_(4a{?75CG(a|+G zP%!utuj5j_2YU+%0O+FpwlnfOK11?v-n8*sN%<*;^3QruYo(0tpEGa|5&&TMgC1?A zzgdrpv5C3C?~i@|vo^ri+q3xPv!)BU=ibi`npQOa>kvSCI_Bp8GWaKDO#rW{5&GHQ z-5>1n>;13n{Yg!TXqLr0@@y~ixeuoEgS`@y{}&;S{=?<3?a!e9BhcZ@a~3fALC&4^ z-<$;mt^SY+{zTNJxofu2bKR$b{M(_$-$l7N{mr8OPawuW>}vXx_E+DOqwwb0Svg2Q zmtPUDzgd@JpYs28h$_d4(gYs<6H3m zs&xEGjZ}#1K6mzP5ApfVTmE3rAnNbF^S=rC{+0ic_9xneMASn5;fvcp_yU;tzw*VO zG@}yRMPk*@x3x9_0O0+>o<#b8Ywvdz#jlOQKD_o2doFUs&yLso!I;M6-|To{E8E}K z0e^D!KObEFT@dNw|DVU0|E%lhLtVe?$_D=59r60Jg`ZCh{cfRP^Z)An(4P(bd~Cya z1HL!^Zx38`uD>A zH-Qd+R`GL4qVFmykpHcUKM7FuGymsEAm91P=zp{Oe;F6#XFWe}lm4y;7XRAUwkU!3k{dr>S z9}Y^P2LPNzo^v4J{{v7<0|W{H000O8AZ9s8t8@>~nn?iw<&psa9{>OV zS4BcCNlr#DY-wUIb7OL8aC9wmWn*=6X>@rmYGHD$yajY5O_C<45-Y{bQi++FnVFfH znN?DWnVFfHnVFfH8A~d$bk);+xAV{K^lr~gyyuzEJS{$VPy5W=EHWbHr9eOdKz}>F zn{tQ){ksF|Pm>W*7NC)k6{VB^hZ-ah?jJQBfz=fCZ$Lm+z(7DK{{TNgz`tHiMnG0V zR76RcRz}n%ZbBA>0Y>C0?jC+yEzJU*o4Mm2p^-mGSwBK>c(PR{f&8xV;YQ+2xl=97 zyUOXu71z}%Dslx`WLRf~zA{V^hS)Vo{+m~nc@Ci22mVk;hT?4Q&VL&BPIxUi2Q7Q!UgQV!M z>NCI9t^_x)890C$KvvbkC;=LrwNshI|@m?p2y2fq$}2XYRS@>u_df^ zZU%ru%||o(;%MRQ@$Vq!{sRQf|1HEn#D6jyo_`fez~0``&egzL$j;W;#NGLy zKy&}A&_dP*PEPg)&gLSv&W;}c1X=38i7agL_YdqGC2Z`i|0kEA^l$I~?|1Z1Ovv`H zB3m2Snz*wD`XpFpwyt59y{7S1Nt7EaFpY=X@HDyo^Iot^7Hqk`Cf z5mD)H{U&1nCj8xK|6~x=|G7c_uc`N6U6`_olk?w4R5UTNb2K(_{C8W~-v&WeE&EFS zBT<6@0WkvoCjs=&$h1~==1wkFCbR}dMmBcFw2FU#9W9KUO^mB%rU3o)FhIL+*+wUw z0|@P*YbwTxj^|ILM@4;Iv zJbQ@Z4DqGCy_t*T?GtXxMSNwrjA->2c|W#ZP|ImDw>?JN3OmBAT;DWvH-w4eqk?Iy z(lTOYvM*vrqgUBFZ-|yHki)pKvjEM#(*{shxgd9|T|j?brxz@TU*exFyB+NBG53GF zPIDJyCp%kO5qD=3Tc$*Msl@OU(t()22KJsoOgEm@ZFT)?-c68wk7^-oC%K9OtoYw;$HM`?o%#O z9z|$}{YD^8JeIOS^rGfCY6D$c(vHV*57zM8FX-ptVnp1y|5)y(=zxj*oj5~aEePqf zJJAjCNn8K{GNL-MnS@WEb|OA3f5h3iIx?}_IA|lhV{g$T#}Xl6GfZ>GJ__x9)IL*w z!nmbi*m>~)(qF6b0gV{rn(s@bvB90O0X<=sW9;r^=5a~n%Zg7`=|QD5&7c$}{uhZz zlgMV+e1W&MueA~UpoFz)LhAc`D={K;F9owCEZG$vXu+^+y zzw(P5HVZ>)U;CJoLn^vOvPQX(qez+;TgJuexz@TxyJ+DurBQ?0i9?-;6mlE#khU+4 zF6UJ3N~}34rc~pjDoX-a86G9JhxoeI(mMIm_sRk5tDhQ@aG2$K8=juSWnT6T0U$9G zeSF#<(0?_n?%d`c>pwy>_5Z=F7Pc17v=V<_EDWqIJWU)Y=p)wc}_A1_xJq=<6%}h~gp)LKvXt83wwxJs!w1AJ##43bk z{{vB5uWmxF0FkN%DLFq-c&XkgzAkMIztVHn^ZDwcWyhdcW@l6UOb|hb$JCnSN|r+q z{79dwY7=L-Pc8ww zii)AlZ=6G^0&thh;L?+jRl|MPBq;^a`;o;a+ROt9IU_ z!m=Myz@_QB4Ee;S%JqDZ|0uv?1fkLG^rv%h?Z9!<0~LhUn@>T~M(`}T|Mml3K(xOQ zy3joX16t-{I)T67R=^6Iix8Z|j*`$vnMgF@j8=o!jWEaZ?`9!Mjzwm0EmnHo@pYxZ zd98vb4^pe9Gc1wlO)zXxZJWsioK76G4a^wD7$kF`x0bT(=FR4LEY|x)`a>1mg4qgO zsfX!Nj)Viq3o zYdHB2c-;}qaDHEH;~on9$xLqOyg$>5I5f5m#wX{>WK+xR7kea6afn?-;(7;;v z-p#!x&O}HG+acDp1U0?nIumk_e^7hN-0{a8d}`CnucFvdG;b6=(sLqueve1O={!BCJ34b;e`J z7cJe{5T)}T+jq_QLp>6|qeYYbVC!bUj=J4z^%->f0d#h5Nhg*uP#R~mWX1P!X+y`i zuyOD1Fxi&^3BPy1jFF7q)JNqt1jLt`e_Oe`l?mSSUhs}llMy7Rbn029Co3UD*sWyF zK7A_KkJT_tQ(U+t3q0mAWksW#F#%{+Z3eZ7y1U@^NJF?il)gjaueys$19vYg!n0WV zK-KQD(UUDt=!K%-j&PYDP%=_3OtxUMS|SF>e?!{2LI;VCdU6O z*>pEDvHx2@qBZ(kSC!(1?E*gnPllI}xsZ9Akd9<&7ATB3cP^nWaOixgIBb~mU86(F zV4sfIwu{Iopiou2u%D}^pYQvyEU*pM-QdLaS{jF$Y4q=rnwlLTZho4OBkD+QRePow z9MzN9p{i4IGY75+mf(COzlpA9$i$0@6aTPc!GWgDB&BCn@!%^yc6eP}|A zFxBH?xJA$3;)-Y1ert-pJrI|8kbK(G0! z_5%GXLJ~^0;=So1-+$C;J+&%=RC~>Aa~bB21(@$w&oX~+GtykhFr|1R)OB@;=Wes@ zJ`3?9%I=tRr}{c~W;0~kbRxwR632y!s%M<6uZKCaG9qteP@vy_^+TkLr}?~kD_=m5 zp6;ja6)gIRR1P+2l=qWxn!);=O%J0QZ^LzxJDEn-OMrL2*Grp;n^BJIv6!K2@Ku^G z#nirv^?HGBdX7$RA8;HCF@Whwg22IlflMm`H;9VJD~B2p_&Pd>SfFTlMnvoyzjQ2H zzWg3}NLEO>BveS%BxKAxY$?USCUSyhkl?AIE((TD7!(IbyN`rWP)Zk}u*f0s%*Y`6 z!=}AJyWR2ULkQn~8oN6g8g(RK*#D5Ndg{Ab@ff^We9edon-9oe+1TwMpGx+JjbMNB z7p{MmjWQ-qPJb&F{?5ECB^xVb0R*3Bi3BNWYizBZ1$3(iBpWzTL(1?1;hq8vfM z1h{Ivx{MKQOT4-qk>3V5Kw@)F&J7c3s@KYXOuig^zDZLjhiU>EX9GE>wb9iB!?|!E z$L&3ML{GFERnP{YS6TwY(9k-Aumix#G^nQZ{w#NrKe><6c{(LgTZ;Q_L-Ek{grO#b z$jHq}aQF$a7ZP2&t~)|90l=kLr#zGu1GudAXmWAG*rbFEX7Aiz(pztow_Q9EquzUo-cj_-GaYS!k0XpwDo5Gl=8=kx+JFcOS6xN3pxZ0oCM z2{YR>u;SLJ^*`t;@1YB>kZz=CECVx+?i+%m$6_MtEUUgq-~R#`s=1)KE+w9X2`5(= zJ_(&?ei9ALVuY#pdDg?7*yUx#n6x;F7{2)N9o~BAb`{6wu;aXTetgySCVfHf!I_%* zj3GryQ*OE#Ri%p2$8k7*ZbWMC3lyXJVmm(RdxoaW8_pIrCuUYdkx?xXqbdE46L1@y zu-D;F9NM|Ge!^tWU0}mmsy8sEHuX#N{T=!@4nu5E4Z=|q?K6RJ6!Ds%9Pv(`AY392 zmOQ@(!j|*8CDSC3oBFJf&je5p8V8_aFqTE_bFVmrX?ntsyPIt67kprz(+smv)x{ z)vRMQQ77ic?TEkoc3XYW zcas1_6qrMug<5o&nfRNqTB=eF7!T4L&xzM?dsUhdHkvMjkcrJR2g`1Hp&5X!P4~Hi zD=bl|Nmg%%h_cSTNbu@62?5Ry+i=nN4R;jtrK*>`^4X7G^VR+Zv#4J7n_yX&U`app z^h#11d|SGClL4l4LIho{CPhZ)H4_bsS}mF86kQFq*ft_+jNkDWOCm`nTHHJy35D{EJl2ost4L`>3i%Wf1TC1T>->RWMFQX+J7X8Px zK5jhogk)O|zq;-8K_RX(W+mj~^UMoWBi=`FTbQ7^LF_z(w~bO3Jq<)tdKT9JZC5I) zD;Zfh594KQ#I-6LK%0>l)dAJPHzMQJa}b48}KIVhE7qhRQ&=0M|X90q9~4!0*({#jnTP zoL{qPpEW$9q-;;q45#CbjB0iE60KW<#p-hR9HcGT{X;mQwAQHQVkq2iub*reMjc%+ z#Rs_9#qQ1B(ao4UAvi3PsXcIaR_G%tA71@};g9-8b*lY?F{Lfs!DlE_+e0MH*(02= zB*`nD#Ro>717*~(}9ALuydr8{TndfhRLYhoa-Z}!HWVLTw3$UpQ9=>2DfzvNao2D_uUpFog8g9kAB zK0!x;w!Udd(#d-wJ`v&j%@IP1<(H2jC_8T|YjERn$CMF2N{~C)9`NP}n8*}^?7pv?Q)eYmyMH={Q zhU=6bgLU0IR)tV}@A-Kq?kk5xe%33J_Gvo%3(*rUr4Ygka*t6dKN2o^_B-+inVkK~V4vMA9Z_az{^k=6&AvBWJvDz6hvsCT zsgDaxe^`F>NOp_655UQqt!HJl_6RwWd-V9kKCC-C+$!M}B1E~fl+af~z^`859KDQV z=KS^YLW;Lqe$X$K_|{YLQ3%P=XJ4+6ES>!UcHTiC_=aCb%#+|-O_TP35LBmaaJ+mX zB*5isn72Q*13D7rSAx99yAdqPDB7M>?Wv)qES~^&`GJdM~%u+tm zo%Zz7@sp^00{JGI%s2|ueyc*UU6P%7c$iq&+UWBAe7uAQTB)-v2rUEV#$LJg`L<%? z{S)41vsVm_x7lD1+39()Uk2I)TS(M>eRgJ&(P+?4<9V~6dZ4!vrYPHBG--GR<36dB z03&+Em^^u5S}5=OF65g-c+==fAC}sDX2)cw?O5R6YNxn2i4ktukSq>8dxQbGGRdw4 z;c2&Ogz|zHb$>GqvYw|yEUU%I6(vc(T+$-8ml)kbM4PROP!;R2XH?3Vj?$?|+Tgbx z^KcXN%yKG)_C#vyA zyixEKM!*=5mYKa9 z+N$EbWZXJZWQk7HYH|S0qBVvn=Pwx!>}pt!JF-{q+Gg^ym6uZkX5azB2sca1<;9 zCI`VIn|4q$F`T%Ry*`6aXHq5M>Wpbbwi$bi0zd9x2igX-0v2-|QR5FUj=Qtx*4R}M zSzuvtElq;87JJ0#m3WCEvCP1BV1B}!wiFS$^!Y}Hf5RIgw5FHFV>g9+u@$e&Ezm)m zLd;7n(|73!^P)lJVJRqhPd|XRGYb4fJ|6Q+vn_Z;$>PqBH2+Gnsok+y2w7-^GoHM$ z4^}l(1G3wv$%i>%SXaruq&x}#kwI7H(<8_7B=`tS8=t7~f$Fgj!ba*E`vw~@^bYsR z=0<;uvm6j+gVIdj+UESo5sYU&KmljBw+h;&#oisj#yYl0pT-TJ5s6V!0)>AI@^R%; zn2~dOY$o_31>R4Rrr^J;i1ZpcL5tJS5jbIN75N+OH8_Q)Gr(=CZ}1V%=APWdFX}*2 zl+3;fd!KmD>0!MQpWvzi;~o32elIWUuBY{j}xo> z!Ge`-tt0Y`#u!M1d==IlX)0}`?1ZefgfXc(gKL`uOY*%0FCojv&D5zmbwzwhuW(J? zg6DOpDrY60VCCBU=%Qwo`g{(`Qx1O2x$u~&sTW$9B{QoU%hTh$b=M!Jm&V=ZZK~g; zt)jM*T!!nyahs@x)`eiO@l;GNh^3eubtJFa5sbEz9<9P?%Di@$4&vbu(HsQ&;X`f| zQh`v>KO}<~Tvh_uek%-64tOAG#_t4=CmgpwSdKp#t)-0hTbjQ{SFvSzf8ecYx(d$3 z#>v5;N55vpP#d)QV*EOBON>U! zUiYFGgA5jNl;|XKyjg)Qkh&8dLX6{ma7t3_Mf)FE$mTEB`qv+htkLZD3BjTgCDc+q zR;ta_C-#*p%^bio_Z0iG3BOmXLdA10D`$|g$Q14!d^ zPq?=1c_#N)KhzMa;uyBi?RX<*Y`~qXXa(hATHNYK_l)# z2hSDvRYzKdM-0BbXKow+0L>msA!pD&>Mma;gd7V+CWyI93MPY5+N>S*R0CC^wP8FH z&J3<;a1Xb`xX!E>Dm#wnEJTV+?xwW1EXPWd_vP8ZBM)9vDlK#n<`FYh3ds_LNqPku z)2M+c{S<{|oK2-R$ffS02HC&h5zd`EuDCJbMztVyRxpS!@SGipOOA+GKJS3*2w~6#U%ZS;nS?Je@C7unxMtG}| z@S8_%1qRiMF2NK%-#zqm&Z*A>5)U%OD2%BECI|nGK0YqoX#f^e&IkUnpXza+8oL|P zFZq7%JHloc_GVt>FI(|F@K=Tar-!YaVVTz~duTTYY`N1y`WP&S&qOlVInl4thacfRcBrhUTj*=TY-*HjBNpsCv&z9u53qjzp$1i|`VCI9k|1;G^ zaS^>FR(*Op(yZsj)YaAJ{qq%;A8ZNc)nP#p+ymNTv)&#%0S!Q&POkA#WoN@c zW6?yi{|+;7;EC}v&6;8t6v?y49FQy*kL(p^UY_bzC0^PEvSdMgq1b>K)L|=6k_$!< zq-pG~c?u|NosKOJ%E&aRr6u;ATVLCqOYcC8B~gRtVN-V_%6p7vC{og|RVzGHps_zl zO?WNDMtcF67<=x?E<};{k5Z;&2);tOFQ{luD2jN&+;m8?_)=OnLBtNBf)iz$m_Gn% z3#nm{LOMOGKE)TR`fymlsjJUF&6NN`lJLZ~&fMO~vx)ItA}7gip&~5h4w=&MaAYK>px!3fj+0sE$ydb9g{~ zcx=(0IdI6#$;oRweB6(A5tORVWUIMAS>>%{dZ?%s;4YF&;@C}FtgxxVE8FukD`uiw z7EH$oO2`POO3gjX_+|o`Jazt==eH$x12i4y5?d&O@c^V+_6}9~fH^ES4`Y1BC&Nqc zv(G(NU_*CE4?`R;YV;r<;asaaCRT7Vt~c8fv#9K~Hj)NELhnnGifnV!lggnznEEL@ z&n_YA8a7MkGJ7)0wWmwzE9M!#{Ude6Z^QTs)HoL;v;S!D0#HqQ!2+nPT5$jolAsS5 zU;Hc@;|#C+4{|1Vrd`YXx*PVp%&D0k(hwl81PwuB69@nDw90a600 zj9dd)2LA6*sbHb4GNdWS`iu$F{rO9@)h>Dpwc%CJ&C39FA!g0WH`R*AM-8p33Z3lV zOTU}eS2S}HpT17E+ZqnR!+M{1y6N0Fo-%*kTwv;ct{m`z{#@H{!@1o>^=^x&+mSzW zMAfTSy~RSU8M;8})E&aU6$P`q-<_f?b%&nmR0)b}O~|oT>WgUkbwLZ}_G;no%v5S3zgceDX>h6i>t)%SMmgZi`&g7+RBzBBYEG2q!gBJg?P z)x$fU;6;5p_y&fra0@|+a!>&HUFweDh3YNcH=z4Y8wgt0ukaOAp@jd)JHd6Wq6A|K|3| z`$Z0H=Ohju;NxI1%Fgf-LvOWc0j)_a5QztC9!Hx4*^rdv^!pXaOZTRevo z$|@MJ)q(TsPa_3&3^~V;@29kcz8Qkk>5qjP9)FQ}6NFu&kZ`MxuO{9pwTUU08j@Oo zT%$IiFlYsZm}@bFTpL!4Wu7Dc;x_CS%RELb@e(c~MEj1S?BXUyr1kjwF{`f?4=}tB zscFMrDOMr$sV*Hu5UE}fvJtQc$1!^qqw3O>pFs3 z7t6{NlFoJJPtOQN=fx(rm5vH&bk6$L@=Aqtr(a5la(0@_g6iVDQV>{SCZyzKB%|_S zyM_r`Xv_dZVN|A`rS}4>qUJ|1UryY71Pez&N19#Kk4UTUi+7Z*`z)|ke3t>T?H=^@DKTlf|O@WGt1-Zr! z)g4~@g!t!!ixYs7kf;O0K7w?&${?DlV}!9`#^k1z)0#d@d!%A6=3urkWzHFZkXLYX zTvrdK$M;T6tL*7uf&=b`9yA97TC)fP`iAri^Ic`8e*fH_5=Ux#BFr=;QsBbe2<_X# zyPV}vk74W93|+aXOYITj6Y?nz6VcegCX@-|H&ZDk60FJ6p@>9~t)sVr8bR9HiZj=? ztATUTqh%;UotdrZKSKuS&BP$cMMrI94Y@ zc0Kv!KdM*?i4lv4hL3-o*=!P+kGnNyf7l}MF=&b%uHPg%SV%v^;n3fRIx-aSz^L(g z$c`F_6N7aTNi29J^I0{h&22UNty53jo;aUCM44(Ih7x4#7UVLSgpLglzlw>G(ZlgU z=i)t2zhlRl1)1iT4j#-IY~1s`IN@gYwb+iBx@RVNWdnS*o;p#qWZb$8-YDc(B6^W- zxgbIPfvqEVga` zW}sB0DibTtM5qwdd<38)O~HxOPSl~(l#64$mTwVhVQR2$yc-->GRJZ(ObR;RIgC&w zu4)5I5P1f2xLxQIcbGc~Tr;2GfFwq@+(i5^ihLPeBfUG1YS1W?Gnmk4o?+xS?fmik z0P^Dia~hf6SFf(gL82+|z%~D^K`39>ysY zosfydTP~xlNthH=iNHy*n9gIX#3PnHDWoZvD6rR?|BMaJRtHNJggYQZJx10n*tuj{f@g+SYL#gw7qs1jl1p^Vn8jEJ)*c4xQ zFt>468nhqKt{!Anu{LFEE4PpV5o4|<<}tOCbTzLhlK|T@Pr9TXMqvobh#j_a{pK2O zbJ7c5pE5=0^@xFoQ@X*(J7E6`ok}!XZd_3otHI0PFczwJx2w8^Q_++zS02|VoyFlq z8sg=VmV}i(G%%LJ@y8bO zK!kdJ5KxC?(B~ju5~>NijZ#7GBylEZLtg0iEF_b-bgFtR96o!rGgti9-@bhvA?B^n zMS}>_^akTD#rzD3&Fr2gj!+?z1#Wk%#M4<523`lgH;HWPk*-cPy(mls@&?IU?3|yI z%j}MpO@s$GYrnj!@>~bbmj|8Q#|6`1aY&vofjpHjf_TAPr)!;yQf*r*63m`HmUGX9yQ`j0k0}dlt_V4 zQGT1{QJs@LT)h%X$67MnW%n8&(smd;VeBPpC-*i^&-FZC7M8nO8Afx<;HDxhj;zxv zjBH@o9U5vY<1HbKint)GP0U=UV!bfvS{D#5A*0=kOoyUZ{Hl$NezzGhq=sNAr>=Ks;!-;@Kel@oQyY`abNBJ z2s^!1vhjOmQ}>(kuL-xk$p~)|^yzl? zn}-+61v-5g9jT7tgA`Mm3&Tr@6~C4S*%ZY5D2;rX7%BF#WV9oQZ!VY}x>^O0*}^kM z5pkOIR|cnAi7lKg?#wa0pr?zwmNr{&6_yS+%EbaQT$Nv$9U@wj=(t!Muq!4W_g!T! zk@N~e%|g^)^q0s;)=+k60>iF4SR5c4epmxEOM13eiZ2Wz){L(uk7afxjCDs$Rfr;c zr3R2O&nypa8G8tywJ_h=G)KN$znJgiIncH7Zz#KqQyY0i_oL4w}ds5KUGDEuD{#l;TEAS zgynE2Y$|aD{e%$d2;Lyq48I8rVb+#o5ck&Oj;uXxm1LE<8~_)&Tp9d!2bsrKx-GOW z;Pnw^kGkxW{`@UFCsHxZ1+?LDu1_%w%nr?Nz92Lp{S-9@_r$be%-JSE6PBx11~Eoy zIrD?(Ecta1xscJiFx?$VL5Q#U?i8NoSx%N5F0N*fM%~B3dWN@@>~1Y9YhfM{6O)21Hwx}+9C^29awj&jwu&JBbBOy(LT=@i zjQRJ7$r#Ig2aB61(Z(e5!)FL`fJ|k>TH~#IdI_XiW~eJ!f25HudmLQsQN7Ps=#qxnS zC95(m8$22{Q%9av2ZcC@8`&P`dXmpVb&*Je<*JWWoNe?bKU9o_YzD$7^)8L2HDjrv zt;=-`abUkIqrYyV?_&)Mm0H;wcrt@%Y6u%$rh3gsu#%I7(oA)3Q0weY#MwhB$>b` z$uxObuRyw!EywZy*0Tp$oX*EL{s5NN(3)m>&&yJfj#Jf9)>PJ3jAw9PcNv$V>i<2m zVq)8|N+rpjt9t=o3Rb>P)4B0QIxC}8YYJjJJNKwu%=95(K6)5+y<5PO%}jlp5nfdB zvyc6i9%pa9?^C(ri=%bJyo*Tt@#|O(tT6sM7VkRLO_HaKg`EP#!Y7`hEJsxu^EQQQ zIfi@r?yUD@iqoesYr+cK5}_RYV3&2BE1+*J+hX3kGUe=I=Ah&k`INNjDWnlNFXHh| zjXem2L2(ry&$&i@sI@?<`~yeBF#J!wDOW&WU{A(8KR^@G1jOs%v*^{o@`d$|#jBeP zPr~y&op6Jk$sDc~Ii;E zyS_}u0xYVqsI#qa6|*r1FN!$SYKnD|^8)nLiQ4_!+b?(FWg+inlagvyqu`8vyj#pZ z^Yo>3Ft_w2Qn5=9=91Fr^T=>_Gvc{9%pJ5a+b|XDn6cVq>S#3MYoe?WTT3Nb!p)WkQI0i8SCP{ws-?Slp{GKGWf5C`w zn5JU|J5^2(iGnG~C_sz9dbWm--!}U7Z1lUt@*^V6g&vB#77es@@B3jd25_3y-PFvz$<1%;mGrR9AAg~kBEs4A z_ssp8F*oL7KZVklq+?e@_uAsgb?4!U{e|LwE~)#olUH7=s|`YcOSwL#m}7V6j1x)Q znFl{}H^g8Tyx=>(@cC`>JE^%h;XBTDaf?ALe9uokq-VEN8uVLwX~kU~Fdwil0TA2Q zTdQ5)n?AeKGzLvkY)=XCf%jJ z-NS*JF}pPG%Yeb}#fRLMCw%DHjR6fCdjRqQ=LO?!G=E3l`jYkqXo7yRy$=QN=DC6% zSi}aD!1LegK~dklB6?e2hO`B8&$Q=0e~%=;4v4FQFjy?0=X@kTt&7DQ;RYBf#T=M_ zK6lYKUIVei^$HU!dj}$Q0|_Zg#TCUf#E+PpMZ#>!rA`L6(c_b*!sv*40=536??_K| z!Hw)YqlY!?mDUDza-f(T8M4Qs^|RCluc*b+tOcJQKAQETB8Y6N1yR#_$Px8?V0*^* z@9Sq_zl&aESwRF2tJHRW@v;>V_{MMw@*FaDx&n!ryd!v1sGF*-r@5as52ECyuW8|9 z0-vdXu$I>?(FS=5l| zOa>XnfN8Q9Ej8*wAFevZc#;IfG2yp2@K@o7>FjtyQ4(H8KFv%2LP zSFoTnSwm*1YYiG1mpN~@gXM976-agg3O1y{Pki`ybCs-4OBP}|{EDDOKQL%&nj z0;wW?dfSnnR_JGScI2jOgY-ZgZtcz*BJu2U?Dqc}MS717u`$;9rWokDFAG84OH&If zR0z6N2%1%k6)-DpWHy(YGrS$|)k*-5CNa&Sk5z?w`n^fh=6+V7H8V)oSpv^il+6P8 zpr8Q4Ng6L3b{tXjTv1+yUQIxzj~grm$UH}&+g~UK0Vd=z&L!+Q>O~APFdoa)inNHg zf7vQ>UV3r__;sIH99bX9(;<~2r>l`{;v^5r@Sv<~x7;~?&ZZ|uo#LV4Y?x+AzrDP+sMZvqvuq%fLIO+H!tyLX8}NG8J#aR5}5P7y~BU0xFF46f0q=OBpHTs^C>? zKPsi+vy4)yJ7b(rzB!)nfYI?n9^4VIgW)GjQ?UMS8l|ltrDYuZ9gl2mh#Q}agTRr~ z$DTi+*ohh+%pDihg-(XmPELx%q>Sa2!d&Y|{)c;L@vPP5&R1Y|#emD3fTgOSYQYnM z(hBE>n-c@V>p$LJ1mAg&{WA0_DVL_a9Vc<5qnW(8<>5F0euK4=P}{tuaB6Mog4Uky zxhoxw6O?GfTrGFaZQSLQU5_pquNQpjA9j3S>@#6;4rzi8#;WAu6rXxRf86_p*$Nb# zaNW&sF>j?xy$%{Qn8a(pk6btsk_(g*wD*ki1#g)eIn#^coxMK~U5lVcOyvo6erFOc zRMV?=r_u`Wg=yKfDx9eq_;?1#>Cfq3cxH0{w&SmMi++psiK9ExvWM%f7XAx(z7N=c z0@x{T_g;F+XKV2eM_tf$Z$N@L-m_I#)X5e3MiX#L+g3ZK_-#9BeR^$aLvcjoW@nJ% zFgueT43EuVlK5?xc$8+zh_<7B@9=zA4Iryxk42_eI{y5O2}98Ig04*E4(Re__jW$4Vx@tSGc~RmX_02FId&a{_CLVhW`t*hv=n+2{K~MH*8qv*rcc}2_O$4<97Q+(= z(h>e>`lq4!2( zbeVc*#y`xQD5*@8DpJDr3@E#IJuE~h|34$V8%W;BO5 zG(eaIBZfLXh%I`Fb_vzW0pAXSkV&D@C(&)q5%A1W+GYtI^}FN=zTf9c9S#)b_Zl91 zDuw7Ij#ehOk4<(?8XlXFGU*s|ObRxSKrd*o2;1WjI8$#a7cpg%x-i`+#hdv}W`hxI zCQlunsavAaZ%h_em!h+hG}%*g?G=h8Yfs;~J_wd5^G~3{@3|PlNFx{-lw;&1@8VB= zbQ7%{D0>l66d)MvE?Ms%O%0lidSb%N*Lx|zyyO$Zwy*eKH|2WCoHp}MB$V-K8!U4+ zOf}|K4@v`ulZcu8J%>YPp~kiMN4r?JYA*N-~)FE`5^9%DZED z?tKsBM51((0k>5u-y2$KpJn{r0Pjn+Z|}Sn!(5{}lOs1tg9v zuLGj{0Yz9z@x&it5G$%6b%U7(484*Y6KF5k^VhY~C_*64hMP-u7v1ABaau;)E^P&MQ>ifp`1pE}mN@JEx zvF4dA0Q&VsfcwQa@rS-;RmEx>)1L%W>#_vH9Rai`MsWRL5PSWVj(UT=6#Kh~Q1`(b z^W|3_q{d`rTo7Q=s4tX3{*i>fj9>DLgR*QFuL{a)zHQZq9;0NDwCi9TB!SaI7f)t> zJ+Tij6Ndm=|G0fBi(Rp_U7bX#WqFBAaGyedD#r7uB(z%^Q@xT|AX+XCWX&!;4oE$o z!P}l>n_E4y+Z6ujR+Qkd34_K~M%W-h=%79IjIjP%X%qxCg(6$xG1OxHwqeW+nsuvr z;S0u=6kN(mY<{VWI=`Rx+Bz~^4l3wpK{YGbWg{d*Rj_hWauc>yspnf#YZ-|=4d{!K zW@}7Avf&XWPyHsaMcWfz{AYet>|gX<2j)KU*LU}lYIzaATODuWb`$`=qPlWb+F$$t zJ0JDkse%#^WOzgOyy(*;91WFRhdfoYsBH1PTcBbG zTPQj=Kcn_D?5kU$*GsWh&hXIOwBRbiHe110ByTY5aMTB_IXNUoMdKM$Bu|*?=A8A5 zc6kLyG2yIBrB&07EnX>F;f4JCr{*Q()MbUTnb*=hBWXZ9^Iaqm`CBl}Ned%-^?i-P zKgxKMsl5if2IJmBiJk`R>(Bv1AcJu?g(BThXi>=2SR)wBaKJNT?@W9PZQc`u4Vy5b zk443!WXZ*nhq>0@@H~1JOJn-#^!g?DK6vf{@B)6%yg}(96yL9jpm^hh-P(fO+QM2w z0G!oUK`J3wL`G9Egk+f1B}iIqu}fIiTdB^NgUjvlC|X{-<87vL@Dc-Pb=5?#OmY#X)0EmQ?XON__iITbDk%yq=iFE@N?-bKZ(X{e4sf~Cm z)Q@sitdX*RiJbNtl^n3-ACL);TnLX~3y;W!MYG8P=$!%`#S}pBS(#4DCS-?MrY4z6hc@ z2%;engQX4lq5{wbQy}hXG#HFhM=ybhVr_4)`eY>5ElY8rcD^vK1nPKRs`Ex(*pN$G z%olJsY!jZl(|D~D9U*wv*Fko3PVn9t=jm!wQRfx~UWcNvCdZ;q6O&P@YMfG4S*@$B zYPLgiPenJ6@onfDHy_FzTRDc&mwfCvyAJFI=>uRBxVEJ(#UfoNK|>Tius<(uq;V`&Xsfm`ejVIb~ET2DSjEmE9O19BRbLW=ubLcCeIhRv6lh7qJo?6APr{+;+RQfA(f28eAsYj>>+DXj!T@uRGthT|@hvDij=qlp5ROxj zx2XUrg=3Vv&JaDwh^L8zyo(b8PwOh}KY^35Cpn_ry37D;oBL)v;!IJVl&Fg)Scj4X zO$#n!YGB)M7&qCVZtg|h(mXP*a!voMj=Pnj%0d|wz_w<*5UN;*l@>-3!r{fnSQkch zC_kM`=*&%_+N$##)DPZ4F*@UDtcevL2Zkm=15;v3-!A?~eY9&L;@f!G4^;WxR45{x zhESIVgL)%dO+L?dkxS=@RSMENF#7pLi;GaVdhY!Xd-(@qYEN2%Y_9q0&)SOtjsuBaM@Z(E%By6U+KG~RQ!=|Fe+)TtIb289b z(O80R9C;o7%x)x+&Wpt!t97cNSS+WIh4;Q*3?Coh*XnaW*q6pCA`+T*-P+Kor_23u ztsrxn4rix{z0)fnrlDSr!@7q^YiKfzm4uJJayjX(Rr;4-2e$cZwV-}$fWBQC1y>ah z6o(%JU-Y)jql9#{7NTowj8QEXIjl#ar-xM8zB5vRUpXPp>7y(h9D4gNL(6W1Ex1YS z30Tn%*QC}~u|V|B*}+b|Q~FG0rezyIdl65;WW>G~5#L=jM>J^BxkmH_q^Mu@zZEF} zS?}j~bFV`0c!A>lxJ4Lhzu^N81Bt!moz(k%x8QT5`0MN^n=$WDWlfB`T8127?J92l@2F;9%{lUjc-*bz%%)d=0qw>W&t2{CgZn^ zQWtb3Z$`a6RJXlN4=Yli>rzi6XL$Ku_**ISA5iUl5>NP?PWmic@Cv=%V3EFTSS?>5 ze|oB*Zca6qq&Kh7(X2M@d*wl{}%f_IjP=vKjfBK_5We*t-|W+mV9r52Nv$` z?he6%ySux?!X>ya+#Lc0cXtTx!QCB#yZe{!-n+Zs_dDm}?Cy&>pJ&X=Iew!?)fn}! z`q^LXG+9zrylSR#Hpzi{A~Hq}5+jjW#7$0`jtBe(y#SRXeMqD^;-1Oa{Z7OJHWIUw zX}r=5XbE`?!^R6D4&bMavYH%%?vC3ZVE+1FX7J+slp(;t(xCnY(fHr~i-|qZ+{)rV zMB`?vM)s?m$Zrz{j%b_>PavPK>MSchI`hWGvX#qeqKNbp{?Kf)+U zLZM|ev1zHofjl8D{KY8J<-w4G-=`PpKCt_>+kn}QAd!-kPaO! z(Xl-26h{)m1!6Buc|fxTw-FEQRLFkafUv(vxH2T0T@btY;(dracp}}_;Gwe9UXXO^ zn^HrHPO*jz1>k41W@BnJ>_s23^5?2B+n%$xr9dnzymg(!;My#Df>5M72DOU}$945< zY6&P9qF21Yf({{=DtV%qrLL8?$ex8#rX==gq-2*lF?$wET?fm83vXFh7;$Q`I3eTp zpv9bd1fHtfM4J)<9aG)C!s(%A`2rHx%|>b(XxYMAv!IdMHLTWz{RgYS*X&B-n? zV+=S&WAJbE&`haUO)JKwsiN+}v9g=C-;q2*h_}aB@^b^|*@x()11##HJW%58S((Q$v zbUh#;sb-wh7~OM3$9zP&xg;J7Q8f08ATZ$O>Zoywu zcw~$%Ow#+mMaU586+K}*53?SN#MO;)m?l%=<+QkwlHy1rW6i=x+Hm|#hb?NkW6akO zhP6T_!HS{sW$VOaO#3;vin-|Vs8&bHy2Th{8n({$A>UEaU;^0q11xfQm? z1_tcQ>e0h}<1&``iBa7&Rn1I*Q%ttc1WtBLAhiH+#me=}rLiXY^;;g6aPc*X`dbznI#KSelvGDE^y) zb9OOuur+g{lNqCtg97_&{v2{YyN}@~FfeQIf1&Qte~Hr7{!I@2cR{+6oD2jDvQO=% zYoqomZCJ|>VVwc7>Ike-Pip+dns%HLoB%Oa%(JjNvp zenog1s@|uYupng}T1Ucl19+dF>KD?K8@-LL9qmC6eA&z@0FL}>=l%C=MR(6E-_Yqz<4r8zM&SVEn;=3Bx%#Dizh6^} z)6Ro>Bbm7}B8ml)8#vqC#vG%k7;NKbSOwXr3-rt13WkA-`;^-Zn!(m&0>7yv`y*X( zvQRMx%j5$CWN&avhe~xhKcADcE`%|s7-`CC?lb*$B7Kmx3}PX{z_8%|h3W!-%l1_? z1DaX^E&k){KPFRMM+ILE`5m!-w41qGxCJtga;NMR9OtWv@E?XmIqC5r11Xhunep9W zKsevwL#hRykvy7dnZAXo1*}!5=B21V{SJzKM=Ae#n~tNNQh|irWIcJyGrMl!x9;Ql zu-HY!|A{%um|wZ&#}6p15cZ<4gY9sGEF>uprn0miM5*YygD?nb!t(SQ=(1!?CgMV5 zaK_GRgB4LoIJg$A9kwe2-}*QfeV{okR@br`Tqbu3MJlm!ntyBDBIwn-w3m#(Flt4y z&V}3kz~Z6NXSSOqGI=a|_%{A?7qbxqSe?;ik+#x?fi5*ex=F|dzIfZUvTpsfbo6PQ za$!-=LZq3JVD%vL-O@asgyo1{#bNy0?C%gmyYP1S=Y#<$f}yEOv2~6C0Rh=ZibQS& zA^_K~HAc@i(wwCH`cU#oVmuTyP%YLdq{yVM;mdDa19twzl8NK0&Ox7Z`K6}alg^{1 z{&rsawW6X_q{cv#)c~__!-%z=3Y)t%YYv@I=PQWYp zO<$?X>G7H%G>s7CrSIwC5Vx&*)gB9RBPxGyZb}{S|g?#2NVJ^0S0p)k{R=DAIb-CGSju19$f0@iVswSY`&0|Rf zVng|1KL>`Lu(qi7paiYJpeqKKL*n*WZkham%GGCqD399I)57&FPOJKNq>2ZiEcC~s!u=BUwu=@38jS6N z^{mR;6F34KXc~+7Zj%8Qz4=hLaWnKlzDz$>Pf(;+Ar)KXgAGR+z2#76HJ@@KlQ5a0 z9*gt!8(EZc&FOSfxgy0)ll1|+)|itUubYLT*up?s!I28A1t$Vg2E%9GHPwl+YY(Y2 zsu4x*8It-ilIO%J;SYRk%R)LC4@jt3s}FwpDJf3Y0T~Y*Sc*>r!le+^agA~EJLu#;X-&iR@8c9( zJ3;J~q#5qRqG{#JUg$&oH87GJ453PRonENIQB236zTXS5IlK z(lr}G&gd#Pi|+RElvWH4}#_*N( z+!B+No?OD4H(Wh{DCNblF&R+#bkzfKG+u6t z6=`U?;vT{pt3xIU#$&Guw)W})0+2^Hxias*fR>3l4M}VrjdL-tpScZ(n!(0c;@N2u zWmLK~!{&%Khh){<$5;dwbVkd7$lf=Tagto=lUt_z)YNb+uUM46znWcPW3UuJbr-oX zqg}Pg-`tZWJe&Pvkdz1pQ_#(}vRxV~MkMq|th7MiSaZVlw}p8qHBAde4|5G=vL=$X zSMTahlA~mTjk86l7pv}2CXHUasF1lx3F>r{8i~-TT4At;L(2vH7oTf;C5(J~x~3yH zOX>ULBWYWQ*u`MXjnafPvs`$FYoa~#FNIx;Xg?chycew$XUHHul&>wkUV0IepCd9> z=Zk+5I-6jwch?U@0I>Rs>oefba0Yix)zx;R1~a01tJ*LGOW-0`-NqTT(4kyQ)=z5~ z1L0uTzsSr+a)>Y=r%1r`C+ti#F=4V|_v=FY98Uhgy00p^p)4(%8-0kUjDU4}4!hu8GIhYgR7UuPb9mwcd5_7HolW)Uv8 zRlz%OWVtpUBowy-)+|AF&|=3DXu`S&RJ~G>BgWRwPXEq?Wt&wEV_7wkGXzhyBm_TG zX3yP#{6yhLocR%<%Ka9_81ZE?CT@&K{3+y71fAnD1=~}i<$~r1l3gdIu|X=C52v^> ztCKsFiqg(4?o>+?ytxT3R-R}n3v;Ax*#XyI7s#AMKe+gx0CD1vx2gF*Tp&XKzdA(! zRfD5Zby*%=2%Vn;zJ3Wj@CBT)q`oA&g*S|NS~4($*WX;5dKVIs=JIPd3NWvA<8Yq7lAUGqhb00eelBmNBbC|M|*ryFOi zGj~pp7y;AKwo~>@GGLIcr8mynp{s|PxqWQ0<;X#ctNd5JSx0XifG;$cOFKb09p1S< z=^UrIu#yrx2HjVp5eo~ew68%24d7GXgx(x9Pq96ILm_dPT{Vy;cE0?~D_^AAcaP&H z&yktkPH8=h`0z%l5q+Zk?F29wmDCGgA9(!RLa!Rg2;XWfvspJ@gf?eo{+wdLmCRJp zT|fUksZftQ7?#OPww^hK$>y311P$6Up|@+zia<38f!=dYx?V@DW%A{y;0JJMF=N5c z<5mbNLF-!Dm?>rG7b@xP4Qsy8SEPLlWjhPM7&fBbMzqeY6TOT#%vyAoqSkE&#@>Q7 zwMU&tQ9A{lj9ij(n|M&aFv#&0>9>~YrRqWvE??fd+cBgcO_A9xJ(>?^5{o^R;d!LA z7@dgV*bZgQNMqlRLn$wp8;e&hcgh(T6}Vt%G|?R~0z!YOuYrgvU=#WDTy1`TzdQnI zNzavtWt4y$bG55^Iu=s0*|#qe(QQke(?8}J8`60q+#2)JvjZy7XgP@!Fe`7 zw5+B!r`Zhk#ciHm1K|$2C9DYY8HkC;Dw$ z6H;D3Ez8p`*WKuf&gTp`qQ4KiHILsLmym@hYpM-~GiUeHM0BX;mx>elYjMl12t6ch z@G%;~I0^nOLQ8%!yEFe2p}B$o3o^d{k{>&}m;ue4{vknDYM1{Lnc*jr1zf^bfBgYI z4F;8B`R#MAVip+64|hiOiR~JNCZzbuJmI`8h)3%F4L#(Y>FLw+D2t)?`_-3GI)DC( zd8HmF1UileYfZBfLh8y_6cIChZUxXsg zbogN~J!rWvKX)r<-rl^M7E%w{Q6>s|hwRSI;*4%$Qxc|UZZgYjm|=~#cf6Agw6z86 zKlhzJP~ujYu;PudC0DP$@;m{nvIn`#0HGhA5TMrTT*fvCoNsMR6P^>68@}YxD&hvX zg+4U_EYG6ap}y>sF8l+yB=YG>gqz=>z4D9!Xz1lotpQRIvxQ);vY$SY@rk5yN|=~B z=`QUw_W+G*^a)d-#W+HrcE0OlM$YxrsJd|eeH!bRux-8nOrsz8zff;W_8--G|50yC ziC>|Q8JRzH>gy8-lNvrSoHUq%ydO+hI7FU=`0M(*VWEa{yAwX-2OK3iv@e(-oTJoA z;IBca1Fuf6i|n_jyZ29EFp6}2kgEKecZTCZuF!>Os+I~kvZr%8fHhEODge`4l~vNq z&gxv0m(p~3F*i)7*H9hl>!KU8k;9%1+EtY*Qb_{xH$i!_FIVL3OU>Z`m7x1?nob5K z)EM!6=38Pj(0LN7$ysfi7_osvE~EC9LKsRSXOwD~UUea#6ZX=TSKuPG<4?r}F8c>y z_X5;uqfKzXQ~h?9eXjLjgiL`Q9@EQXS<(H?$@Rg?{)N@s(i#n&eP1UlLy?oEo|kLQ zTM#cZM2eb)`!&@aI#Li=y`qPs4LvXKnKOlmrJ!CG+|;^IQqc~D=!4tA`SwF5-IT^t z)$v!<_1-yNjm8&lX0h6&pjBo$$0kdE*0Cw}e9xG;C!s~*7={=V^i)`9r}&P5us;1k zbVNTI?vUOvGad`A5qsLrzfP*kQ}s&Fp9$UjUy#_c{BsM{`tK6kpnQBN@NuQ%CVVJm z?Xoh-(Vd~~iMsvCyG^r3P+ z%$lhI9?Kic=+j>|Z%33%LKz>pJjfe2Sj=7E0Qc6&VV27Wj`Wgxrywy$5G(7tL-7Dv z1aroU7@Ls2V`C#~iDm4zk47t|LTKe3c>T{`hQVxrla8;8OZ5P>!e`FS{g_FJ$#0FV z47zH(R@16Z2uD!5jjpbnZ#F7AQkz=xhymry85dADP&+`5Ubnr717-Kwsc9dTjL(fb z(p%3)^bi*ziaJKK!6qiSY@8FlO)UOecn=Pn{SbIaEEYTQ(L)}+HTNGptJpnIZ8ds# zTq5rt^Ee;{I$n{=NJ)T6wqh|Q=^Y0{(yvg&c19C1m>G>3mF}G?x}6qoF1g*(<;2W| za01x#TPM~uk#NMNsko9gSmaf>VWd&$ za~2sygW6*^K7A)UA(nz?KKxW6eTSyG&lVM1fHOSnm*Q>s+W##jkPsic;8eN%-+o{oIfu~sru{VtxZCh z?-uM;(oKy9W?Z(*tIdnh+V}B``&PhH0(^5}M|qZYx30|6T2@m-`?T4e##x_M=~Iz5 zL$o;n*hbd!8`GJBF8NEZ&a?Ly_^q7ci+3mjiQs!4lR9v{#j<4S=CK@Ssk8u@G?tv` zROUN5E<-mcN&ypMO2ctkzhAV-u*~Ze9>eh9VUC7QSgl}a^aU!t@jZZPNtVx;MUxIS z2t`S~JCr*RI-1+xTLW;AJ3hOo1v~lE+w@@z^D2qjnt!1<2fwRqGwcaUhlRLI2Y3Tf zV&EJWorGsZXU;BpVrh_DQ1w%gt*Dj2ck02NRX2j)Eu`t0@OgD7Xbqi(`4J&(ljceq zG)Z`ldtpZKi-WPbuPaUUoQnsMPY7w)!_>%2%~;TG?4(-g<%BoI5@dUpz}1SVH`wTY zVB!j(#E@ZCpepM&K90uE1DRWHyo*n~Xf=LHBa%vWp zqjU5oWrASm<07~Wu0A}A2q$D~V>Mjr!uPR80vsZ4yG?ymdKu~ZFA(mus|P5oPpf?Z zA-RpG&Z7lHK2TRWCND357kS`=en6N(2iP~EQZHw(b&i_K)f3%KJr^&cZTxLdAD1o0 zHWpHXy8zh1lD5IPxOMEH35?FB$@azd=>zGbI^JWHiP3kk6^B*AsJ5*3lY21W1$6UU zZonQBvR(CRJ3cYS?%-jxwIs%B_alOVp~rk@C#7=XJ%N)T@%xY2t~b6z-=DZWY9G(N z-Gj~$JT^H7cqlaJWzOs88>(A*KC|zZ)FlGOI7yb8{RG}^tR>-xjaLR1b7v2dQSx0} zxLY<*h4@jZQwr<3N&)vjc*#R_5udq|QA^QCp5Z?C-bKN}m*O_j2Z|iN0WHa4w|gxO z$Qaob>_@#Dl^Y`hcKi6B?@-ekWMkEaM$5)2%?}|N_%B6@RBT)AMNODdbI~@M+3!|r zAmx`^l%UJiZ%HSO-pl%|tXxB5Phwj8pIWW6`YX}4CmZWgw`N^%r?Fu@wrWxqT+6eW z=qV`x3^`t(8u)EJmJf-`3p=_KVDWmB1u-_o(B=eHkiqbmRM3T~|`2Q3pi&+PK8gukGoz1Pe$W5Pth zOr`V=d52n;!Nes4m%kmHU?6dPZ9f2ThUfzYL}$)vGe>^5&H1Lk^d;BAS=Al9h z6KubOl9y|#?2C&+A^p^$Eiz__I*Sp5;CHVPgAbl&_?(!@sc^^S@N#zTEgz%kyzQx5 z|5b+Z`k%+E_K(ftY_9c`V^IBt20wn&z^%;e*yaXwKga7BB&0Bi(T_euW4wv-Mi%ST z6Xx;;J=Z4D&|A%AD;A~jn=&~JXZ+Srz?)Kx(T9E5m8u1v=m!w`O{B8h6JB`A1~AR$ zOXEps8q~x&EQ1?wiV)4yT_U&uPY|X?ZdHR=C=7rr%c8iM_k4AIh}V*(C{Sjkq^Kf> zf8#!=netuFf%&*B>!#X|_Xyg)HtSZ*u1c7py) zl6}d}ME(kbJTs?VGF92W*oBJ>@jTrO+!yxtPxpL{=Cbn|v!xkubS%x@yV%{0heKbj z1bKSAWVYJAXjh-Ccn-dSXZT`vBw`+Bi7#3`rZ5QWp}fkw_N(Z7yZn=_HlJD`Zee$r@W7PP$K&T>W|LGme*> z1(^k%?vTF#lfBAVLoo05`_BrbG%36dl;=ZLGqb8;4mE9)J$IrXXN&9%C7>XJh&qD>d z!&5DI-x~Frzt1{MUiZ$x&^gH78(2}j{A=Stj%Lv$qKbTo87c4Ulp)VaXco{7P(7t&olzf4Q7D*{3qJ8WyXDBKwWIj-De$$IXm>r0r1M_^CS@O=cJ+xVs?pi=>qh{EXAZ_k~H29u;He za0n~|`0`e&xXfU6#u~4;08J0_GrHBDZ>6|d(`k70dkP!LKJXSm&l|?{E*pjL7sRxo zanENmNbAVXL~c4IbhZ6?t)q{>#-LZ)km?{PFt7+jFffh(oy(8^XgJRhmUlBQ24%v5N{(7uLyOUlaWHFT%O8sfa<9&mA+___ z@3sfVH7DIFGgt@<{yENh{bkkQ{%q{R@8cET-~CDt)lnCv&lpaGy3kTh-sm&N=f!?+ zJpfm5w;YQ%BP1|AUEYJKNJBSsyYQbwz2Dc6`N@U z-EmtPyJ~(>621(V^)5hZku+TY*Bn44?F&b6Kq=-UIsmD zKb4D!J0;HCX(T)EatC2RJRDDfp*oLkKBy+Ge81MuWx$M zL#){~ula}eF|K<{G8bT0iC~>QtiR@8Nb6i=)DjKqE_1XP91RONmDH3!J!78^ zz^5`}GCV?9uPl%Xp-6s7Wz z4@q;xA>yhV*}vB<-YO5#x69SD*oSLhZ*YmKjGVRv?I)W6uS_x9$nGnI1vRx<>L!hB zw1hQxTv1D?Tp7w|TyDPZrqL?QqRUqPi?o@hDWk8P^a%DBg$sr33>-NWHb>kM<{gfR z9sRPm?jZC1$Wl-TE25FyAhoiKZZS3hl_0yzl6b;v4P1AU#?w8Jxkkgw31c8_A9u@&q`)e2F9I;(DlObcB;6zymL zMNo5w?&BG=z12^eHPUSxoXssAP&!w#u_+Dx5cYMorkfFyL0fw_&Y(e%herL1W$au9 zrG6-F2Y$wYTIl<&>ODs0StJpDE;^kK|5?l1&ZxyL*NF;8@fp5ISQCB12yqkcpn5W>wm$u-se0j@-re--vncn(3~q=4)dm`&7Hmzm;hc|%kp7_g6I-p6{fJ(U0_!kl1g_XC+U)`2#E1&DoDS_nt?Qyez|;bR`p zYK!M=(l^+V7fAz8(3r@r1vK^VAS6j<`8#$=;5CGRtk)J3?uQUaj73sXBxW@IC7JA+h;iWC}W=x5p$#+OUp)TX%DRqliO^a8homO{yVOYmw09(5A1wYRN z-=y&t^+*T?5A=rLGNN4h9P@BVRgu30#Nm?=-*CO~ZPG zMqZXW8@|nAWjg3U^IZR}#LUe->zJ$~p>n0_X^DaWOF`X8$o-aJmLm&o|BxMD#r?CZ zx%WEx&c0kM8D89Kc(^1=Pr zF^Z5#Irl7J5#_{~c^GU3yY1UFYu7>jI<7>+`^VoiZD z4jmj}*(12saa@F2h0>9lcX+j`_juN3iRMs5L}ZI-o2qwY0~`S!Ex?xb7Qfld*JPA^ z4UMr&1<+CaW$pOp1PA}Xo|*E)YIxYxS#@y7+VKdOO&EDt227(k;qG-FWmiohA%wrP zyme*q#e=_{pl*E1D%P<6nbWGyuYzWSZv7~-TC{7di}U4CR*@gtRAH~{;SEiMTP2OD z;`c#nBtR)5s}Ly-X+pu-ZG7G?NdB|H(U~%`c=h*oUaz6%>IR>VVhhy8qmwn6oJV3g z)NxeHmBTry6R=7j?*{oNXoV+I*vn;-7s@Q!29V{Mf&h$65hAi=5fZYjKL6K4u4@=( zQBL{cZncQ-qS&|-+qnB3F-F&vZ!aG3Wd2J}YmYH(aA5=oDE&|?LxBmB^#~^D_Osb4 zZRFklheU^(W>NeS2c59^3AFy1rdAn+AcI4237lQ5Y~gR=jG#G^-Y#Jy1!QT8ocKHo z*b&*DB3U+Jk+rVPDP~HJsA++aZj@;x>XwQv0?0P7XM(F0NsTG@D+a8R8c+irf|4K_ z_z<>lfFxtOX9z4xlz`AJjcU&W^7_ykDd|0-np;))X3ZW}-g~r(@OAsx)14%%6ti}0 zn9Q3ll@x}MHl{2D+753Cb-RSXotmr&aT-f+cpCuxtS)Qxb6ACJOfB7U@k^T;T zPz6`A;Ij5js9c6PE(Z}wEF}lZ+cpmAhW4ZlyR(2(wt6<*gx3okKa)fD1>&?wSOtb- zhm?vgsq}Z>-qvZ-%k@FaN~wU)sMyu;dtXaGrqqEmDVCNp!|v29FsoKx)plOOm1m`$ zP5c*JD<&$UaSOR~21&M?_OhLfWLW}NgwOrF8)PpK6fY?2k5XTwpJ{NlL8duvM|76~ z9-AhlFekQZE-H#2mj-`5o(`esK4SSZVgmaYu3-M==>8*7R^@(tQsq*5Oof?MjhVfU zm8G7QQ`rFlQHhzAnSED!Vv>%LhE|SogsF}}l73_h{BJjX`OkIosSsdbUtz((RQ{Ke zEj0ew{_?*H#;=Ma`%W;0QInG77Uf!HHsw|#kmp&67!Wfd2K1K(%;lNtDi{QNHzM_q z3Cif#WYBJL^ZJL(}Hl;_7VA5XLlIwwgUz5kchb?l4LPe zu$cZ1`eMPYgPFiIq;tZj>$>PO^PLGM`=q)xcj_gE6^$drm|MfPvDYwjk%&XA-zS$8 zx}2N>>A**WMxK&xpV)Q@PZtPR%4gL4+9E`!^DWm>EW*o?I&UacXI07+Z{cnzc6*XF z4ZfYwUW_#E*Vp)Mm*I}D@ASBil4IgGA=;Hcw}I_V7r#JfqPcMhPIv_+@pSxktta$I#L`7fg3E$_FIg#&Vk&wk9kvOxZkGl%8CY<)x*umn;tV1179PRq2n|6 z^2^twPH5xqdx8Chq!a_~OODhdXM~KY3AUU;MG;)BXPz7Cip_MoN;~@fCwn=CODxm1 zURodm83c1Cm@hzTy$_v%5$E5gAx>sK{7a3o@*}<9=ruiHE|ksa>30Z4j*@@4A?7CO z-M;R`;9XY{wm-CK##Ci24%`SxXPiR78#HT}LC9dH)EaM-5Kf`oTT;Sr{xI$l3NbqJ zv$F>cEXti3VV??|gX2nBMs_n+;_G39mIV%30UsI>$#)3n1R+wY<>SmtR+FKv-e z%p_AsY#(t64XSA03gS0(*2PHt@hFvu=cR*qVEE|M_2|&EaNU_`Jv-HH?%ts#;_3nl z!jU+;+h-OD=jO$wjvr@7&_vF;&6a7|tZy;_&_v>rokH!aBZS;+@xE|Pv2*=|GqJvM zr%u|j>iZe(Y7t%f7N3Z8@==_k6i5(eGjZxgu z(&ZncXLcj!9)=5iiDLwfY46Y?Xt=g{r1krM^sxo;wgf+f9lbP+2E2(dTR{+Ex7Zru zk9CLYjhJ34DF?^KQ~mM7!AHW$9YIphMb?IdRfbV6lxLx(!&uDsfAh{R-3d>4z`?)* z{siYr|C<@v|GX-$OwzNTWkv@XG#4J#xt#nmjH#}sM) zA_TO{B&pxNgX*)DXv^cpKMmW0+H|xmTDqb?x4rpV=E$0^gwyaA-B9?`I(9aH%{N%KHoT>!K?j^^l(W32*u zg-b&0^AC*5MA5=+MiDRL0A|@)Gon5mLPha+8b)cIwlZnigA|J1ni*SmY<;-~{|{9< zt@D=xW6i2;eSr?L5iYwiu?@=zqccHcQJiD3Pnx+u%AzaS-<+ufBO)p=m2W?J`W*3V zzQL9@vr8Lguh3ihGjU6tJ;g^nLlHNjN0%XC)uh#ATwxE-ZwB~$eICa?vXNyK&j{{! zxLSPYES=XrLSto$btp>X4^F8Wru|*Q(?7?3|6;$S94NP|T+tRqH)nCk-w~lz7r~w? zfxxz>8jBQ|^H)wUxMjY9|KU^u@?VgE{8!)T|L6+**G!d4Wx0P#X7ItIa?HqRlF}je zPHuwS$uq_Xhkz3nE)@OIAc?-;>B!(7R3G#6B;vK)i6#tUXDn8Dj1pS->JzfS*g0R< zxB45mF7LO;&)q(2mA$>mv{gG9(I@5|MkbXJnVQvx`GInvYci4$-!a3+H9`?bE&(`f z%ywaDn4=mZ^6GDbqy-z%K7@B3q_WuoVm|cfBE@)Axm>V&cqGY8Kb4X|4L3ViuzRHyNQlmqpL1q%u@g)o%2na3F@T+hwf0977S&aOEiI_1A=4G z;W$`$Q4iWV>YZJhYH|N`PXltP@23jCs|g3Kc3>uh)196cO*Yu_0MQzybH>=W!KkK# zXf!6{jbVkX<^;tOv?&`KIs}qphO0gJWt>1B%N@?XAis>s9)bq!`}fFoPF)z8r93WV zdEcp`L_2jhS9?h}(Tzx}L1qPg{#0q_qeK>jT?fK=TYb{su}AH~(~WJF=`TysEy{Ke zT={_HUmDjAAoJ{gg*`Jr8LgSV%i78KeW|PQCI&s57cv&Z0IM2Y)OKBUnt_nVL50PYQHDjS@2i3`FK|4^UhBH z`Z9CKQXjDm+%q>YNfaa?KY`AG48ZDjfRWP z-S$^F49-+F8&i1vS-QwotNIB=J^WJl*lO4UUcZuvqLxYjmrKRjcjtA5a%T1o@?XQ@ z*pduVffmwYAUqtJENOP>7}tWISQpWX8QoPuwAP$J-j*QDzI;F2;U2v;lcf(o(p5`d z&CpRrgx?0do}%>(Y2#!pBt}t|ozXkL`C^UeY)<74eS$aMNsTp0NDS!gTCSy9#LL_W z9)x*nCEYY!`UXZb%_6u>9 zCf1>yBh?uips!54YdF3?YK=KpmY_S4%s(hlpD1>7Qifm3Se0q{e$^B}^jF}{7Gflc z{s0Gr{1=n~e+N#}?*E3{LAjM>CbD*~#vk0U0@cV7sbN1SPtww$R_5swxI1z!oeZ~E zW%#KvLOFi@{-hMAKkp~FEs_-q>=x}_4qc`#@t@ULQ`Fm( zoDnT*{b!^ZfF~+pxVNMp%XA;mshh(vPD;D*_4+W;rNbtnN3d#pfQgq6FS=v4zm^X< zdY?QOte(yAJ`71RSw%cnt4m!HT1341TDWlTP?z#(eIed5qsBrxGi=NdOy{E955)Dp z{fuqL(bx7oHVimbqs99pUyd@?hS)PH85@OA;WklC(q89W@ofI=Yg)*Dq>FH7K<`F> z;EXfUS~H31T)2X}9Z}~}*wZAX zUgJ;hzgi;yjXqCf97;|c{ST&D_!bRusE4ER^F5uH{s-)ZqW(dFhY^}_yo;b3$ElN< zFv?9PiVlXH*XxQbtHByc^Uiqb)ftk5<15&iF!oRk=*rtRus?|{6#Vpz^FK;(2YQ^M6d8! za}e$feov1BC9^+9W1o#xZ5Oa?eGYiCXO{nL$`+D=&XkujydtHLug#rxVmaZ$o)EL= z6VbrUWLGS$p;7>UGgFCf1#Oe)fpZt^4)yr?uPCPWx8R?B0s}k$6BnKW_dHkKi z|Jr1aGn!Z!kUME0cccfv|9y`okyKTWr;ca=t|hnkXi02vSbwOsqW6Dt{4GH;SQ=3B z2;Te0=oSB=aN~LPVbTDMuRjF>o&YA*abR$s)S%vRy$4~f8gu2uh~0x?(S(08M|-gDq|+axR%Aky;(&}Y26{wG+?9_cq3g+GkI)aP z6WQ{`k}PKM-{^ya%efC~r+Q+zWqa$6XEBV8un8YtyH1*?dWsO+y`?pDiW$+~vnPw8 zyeJcCDSP2ivSs*X9;az0mt}6~QJG9@6}+y5AK-aJ4qU6EzDLB*C8YQksam(hm(GOH zz&v%zt18o4*|)R1)mS3NI2S&#c~G_ zP%UzExco{~v?vzFa;jR-+qI_GDPmL=DhX$Qa6zI5$)%*HaOrzcCbz^iKZPMwc~a`| zcQ=~*a6Rucg_3UzpO5u-A;+t0Vm_O~N4KH#e`hjV-IpD$kDZjg#iG>S7uD_%Uim_| zUKO`c!xD?-P`jrG?)$f+hO1smpW{DF-9Y>c1rh!`Q?|DMchBuO!#52Rb~6e3KcQks zE~y|@N_rX8Yt8)f$Fzy>86?a6dz%+@TNa#UhtjufOy<5iKe7V2?piIMYaggrUmDO!69 zbnza%$oDp$Kl!z&DoS;#hp@SnX}=2VBY;R*`T0bH-Y<5_gD|R8bPH&Ij8Stn3HWyu ziy$x#{6=5vhKe|k@~5Cz(I@s&m#I}a-O+gq%dp7GJ?EI1dIdj8`pUUjA~M}KpxmT4 z(ZKg|yC&)f%u|yun5QeKyqP(JvqCtT{o|p@Y+!8h{*|b`3Hl5WowXM68rtC6!7CtRn^$Tc@k(jl!b)g|^4 ztH@h01%qFL5sUMj_qPSd(ZPtg@|0WI(UKLRbnZSjz4BwPUV!eXU#l}mQJLDJ*1>%P znNRI@tVJp|>a2XYlT~_SK^pu*M6ceSn2y;&+M51O6~=~5Z?Hivb?xoXI11?>H5yUb zsmd+0oDY0Ln9d)mT0+xVuh2++DhBLq(iukArebUWddQjPh5m$nE!BBG_os}@5MGMH zUFS_Fa*V0`whp69y-77FZJ~bT+4`3_yZ`I5V!hG6trFFjMT4>{%W0U5w#SCNx2sBTI-c0KyQLAC{&7FXrPkdtIg5?5+SYP_>MLW=`(IgBA3yEI<1&6 zfzSmrieN{Qa`veZSFBL-l$oy5x zeEpZZum4$R4rWd+R%Xt2Q9_78%mB!XpS?`ONZhJnNazAvl#w;TJM5eoZ$9DD_8paO z=I%>N6*bi+_s@t7Ar%6I#=tjKWHS_UYyX7a!-8N?`}W7Zy7WAjvS76_x*uw;7WpOs zLO{L0OlQGYcRu%0;t7orJpP(Pt?jRCdhDU+e`_Ycj531u4F(KM7xVwcY8U}cjqN>{ z-2ZvD?aU+wbhR^cGIIH2HU4(u-I(^`jHiJy)MYcGt+nx(`xp?Eg(XEcinMHnCVW~% zQY5ub3siwA4$n-v^CYt;txTH#<>=ppuALpCoGs^pCH*5f{W4O=aQG6B?G+8sck%iu zOZaJHJ^ANMRl;X93rere1MhR5gKV$;u8#+W=1-?X7iieMWska2hP(@+XV$y|k&}(P zHy%RX`bk&H;EvuxQ62QVk)IoOW%8~f9)HsAj(&F8q076PhwKDZM^18?{!IBAOLvu- zcVEe^kG0=y_7i2u9d!wG@#`@=>SFXUAUOM*P9wruOYTAHLG<_16%3iwA7dFs!129# zCoLwKL)fUvBMoag9Koci6Syy>t8`)?V-p}k9D8ZY8@Vt!ydoo_cw{LACdse~^wfd6 z3ZrA^<%X7ok}QeFpmoh zankT64CNK(#rn9f(o0z;;9MowG-KHi=Q8+tb9BYiFQhcqY=PUW2m-pC6RX0f9rBoz z7p)zuwvTzIJCDgi{^_)ul5HxC7xm+GyXs>i;}ypA2B(;JZN?@Wy;R#H2~wDxj7?Y< zID8ZW`8&sK%cne~;D~OT4*Jk`Qp=78DlG$M&^>pJrK-`5nnKQk`!_%qDYE5c$EKqI zQKIZl<8b5EX6kh9!UIq}(fdhL_PsZ6S!Ol)x(*r=P=w!+l$7^Vr4A3J);(#hZGbORlyXW~3PPOM|l z^>0d~2VV6kKPidI8FYu@U@wyug3Jxdm-^H3%sB>`MIarmZl!3z#nT1nV}J0E3lV6| z#1flJxq~WcA7lppg&_TS)Fp$q;sHXv1iR4)cB9ku4hayT)yQ`BO;AkB|y40>n!u;)KWy+?nN&cXz%C_>Q8XE&>M2WlwJF$!>PT)5C z%P>>B!GT|*-vkv{uST0F{Sh$U0o!vS5y$8qB*rd3r;Pv1%XRn5KtKQNm*8-f+w+87 zd^aDu5qqB_KY{8v#=NSDhwmMYQroMfBM*=0Xdo(K#9rVhe?4I`Z+Ji3N>`zpfWHgZ z$ev%eJQjmskz8=Z;Vs+U`RrWJH}y-p`CQRW1~JD$HzsU-h0@_71%Sh2Mwt zx{PV?8rc?Q=kx0)m{`9EtIL$GA$b#Pl;ps}|3gN;ixVs?lYFB#fY^f27+qO#iK9`# zZsYk9Ga^GD&avYK29E5kV3ocnM#U!tu1|t!XNl=!D5tlU_Vd(ww2U{BWQvXA7AhJ= zXn`-xAdzc7^~W9XnvtjZ@jKr6Pyxr3;*air#St6|`#yne+_6DQ0S8Zzl%sh+#>oOX}3 zkFAKgCtzs)rdkFjckjB-}@DB<3?ZB zRr|QUu9oE@6rx999V;)*CY<&q$RJ=1JUs6LsA*$IJIH&bpHK)jX60<=pbE5jKmv(8YQt)p*P}Y_z7|&Zc#J5@jW?HgxAOu| zNzfu9`SRxlz)SNM_MN}0yGwk$e1k!AjS@7QJT?qcPr!-$d7pfJv0Ul>;Y{9+oMx55 zC;LKX9*DMP>2l6eIprfi**TYU1PhEY*qygdwZ6;D4i^cK4Sy+a@F zPdKIF_Vxohfn~L}-Q|tqDks%sjB&@xiu1+AqVSa?5FK{c%?;f;pY>FkjJGW4lP0=0 z7`jUViy_MkPnlwG(s_MDoA^$VvILGWDo^QbsVdgL(&1M$ z7<>YW0Gc0hI^m4;oYse8H^~zQs0Y@D2+tF&{;V#ybO7@^+f)MnFGEAWxBxZRrO}&JlAMK-r)n8ReC2CRW)(0NfA%OLH=aSQizc%! ztj{Dv1x^_$A8O8uBWVO4(*U?23+6M|h9%qP?3QE;3x<$vt9~-h>FI^q9!V!@02dH{ z&hSumL6i%26HM;^L=l>l5fkQpm$M z%TxCTOnA`FYTk?$ZdABip}obd_{F|BkD26~0@ieLi+rW%Myc)4Ymms#_(^|o+hRLu2JB%mP$yD6l_r#TZX}%amI~vHH3mDkBx;9 zJYx;q8I-A7AaSA|#=g+)m7?$)K-@v}ePIecZxjlBup^c9wDJUCb^R^_Cz~*v<&BRCKK4iD z^@=us7nVNlD?{#;H5!@Gk3CmwALfNOz(x6x@W>jmqOHkp=M~0xH!t1Ihz<*3${?o9 zIM$9bzI-Pcx_Xq8x85}2DOs*#(B6*DXIg$u@1yDiwfLOGg0#2q=L92uSAtAq)Q_;r_OG=v0Sx#S=^Vl1f*I(V){L zIIqQn)wSnIkK>6(<*qB@NG79EARdiAkaE_?iqf#>F2FAG5VnL`wS=mr9Y?m#;O;Re z!gEF#jqZ{C!(=J#y!sOuqn>tLhc?in-#*ayrNYCyDf3t1=h1t4n@*?uW4Bt(md8)I zkFVJVAhyuZ@Bl#WP6c(iZ64bBZK36t4%_#SjGECSLiCS>a7C@e$z>JgJ*rjD{t%n7 zFHLHaGx<1c&YC-EFdcpO>4Q`fmpC@2WlKk6*pt;>Cp2 zt_k5>A0r_qXdmi>)E@&OIn*y{;XmWwx?!?akb4+BU--ehm67`x3U_o$*KfJ8alJo2N-tM2KB4|H2gwO>Z8F`VLOa(mAq1;do>>$KvAISPA=bLuA2xM zHqt#tWl$QhNB6Mv!*i8Ircr)~ND5aclV{4OK%{_pkKA=7fT+i+dGGY)^wPS(A(>TjT9-gbjh?O&0q-6DNi&KEbf`acSA8g0L1XXj-)1uCZ=? zc6PBYv%^{X=6H%yaZg1z#fTAx81f!Z{@iH9P@N*HM;;}-Va{GT@=KZ!4G9uVQX>)& zw;`GA+pr)W=wjwE3(-CYA{`t|H(qR#^NXjV3OLQl9k>4DIMlOAI{xX)#}F+VeoDR$rDLW*bdQ z#MTl%cGK?oFJe`eJtlvo}Ojrz`E(GjhQ>pQbS(G%coM)3A_F6zTcJVxti*7B|3M4crkL9=cs%{(Sgb;b0QZ`HZZVa-0{B=`QPr_mQx z=R7gC;K`1SHV&Z?>zlPD6@q&v-Mg(*k3@-2nb{}PeOdi^k9C^|mXKzkOi*F_Rx-R9=2v($q=LS3zZ!u;2j&JdJn8*ZTgfRQ0?iY$n;9-q0 z$v%L_an1%3VXs$g`zm(IKdS1GnnIVI_&X-r%}s~7o@Vg~qJ_256au&Z$aHO0Y3_q1 z2>JXMpQ_lw6BEhN0%%f@MW#VjI+4^IdgiW!K&~FS^>L``=lNzwku#%2Y)nUa+UDuzu>=hkO54ys)_n+)_s{}7x ztBPz>wRVh{2v`bYn2}_!5AN>MM61i(cbUpZ+Yy}tdCn6+%W8# zIhl0``*Ss@2{V=P%(QQvVx`tjf;7p7gQ-F^5=5ixkB$Z`LHlj~)?Su#d$V$@=FO1F z0sN_q)#1@z`gaLkY}?6h0&m+^eRXb8(S+fP4M$(-(C1}ojrYid-QU?MhIiQ5hZXla zcxDCiUG^OMk0)YgT5=m#cXk(ev0bZ~Q>3fHL;gji`cA9E_2f0#;&gUyn{HLJsRCb3 z)~?#y*JN*(263q>x%%#qT<>VnMNL&k>I4w z=do=z#A^oN5N9WFsV^}p7Ol;cFFp{Wnuz5`IaS&`*L;a&-rXtM(yU0Ilh z@+fS7foQ7dGGBk>T4Prdv8+lMi?!-AwVW-|wFg=?&3!F0wqKgKZp3SCN%Iu=*@7?a z{{R9;Ll9lc5;L!BWf-xV)!xcE{yyae+T} z{z?X~5R`AWHp?`Ci}nG2gU3FO(|eo79v|PM%a!e~`Xq886_7VynlAiEl_9aGdKqT@ zGGnk`?u^A^D%tv+LCMkXCx=l_h3UzirF+MZ`>-tiM?$UCHK5xWL@I^^i%Zj_lW00P)|&Ps5kkLL95X6b6yOzyYgE*}v?{EHt$d0LqGE%OW!i%l}pj)?SC=QHTA0 zQvdF@^I1r6(AgD4-j%fF6>9wkllh1Ag~MhDG;~jIE1H3knkP(8i}ZLt5{O@z_EYNb5rkFdWjLi?;of-5wP_s8;ln&BLd#Kge zIs_w2o09E}2P;^ohX!}|HBp^!3%Mj+HUKV}vvlb(XGnW-ZI=&>+C#_(&oT{#or;So2rjPN z(@6DcW~UH5a&a9)t=)#q8E@fsh6C%P@HfGe4(+f9Hf3hPt&p^XTbw^C%ob$c&L<~; z$1xbO&^AaS?s!dPH6qAfupn{1Vl5K3VebVWtV zgDxpzY*{^V zj6UO`7rKtM$$Wm>n-LO0tV|8sCY-<@O&PIZ*uR`*rP# zP>0LgD=k!r7G?@C6~gb(p3F%~AxcMZtEH;O3Y$B*duqp(#rCq+Jj2pNqFr)zKH3C_ z^Jxtq(s`SF?;wyK`90Q;43B>Zb65Eu#qcg9G9`gKSio(?AaUsPR7YjNNv8hpk?4IRen)v)E@Zocac0ln)aV#h zIxK4^q`|4+!EJUGLZ>00>10qocVfJ3UncnrRfbJBy-`k?Ez9VR6)>oj)Ptw>e)7eT zd+UZdq#4#n{=Ns6o$g-=j59t{%+?K ztt__tVzU^KB>qI1;@%T+pw8Po)aCcvLY3#)zd_>Vm%Ll1P4L9tQvPxWFr6 zKEEsqdgcX7I~sdypC;*<(@l8nu6)6Dp!U``9&Fb&emE@&_1@=DE(1^4=NO}E*ToQo zoRT=!=#w@?k^tAGz?jb{SaOc`gSYG!j%H{O|AHCcr+A?U^t9I0>$t~@+$;m0i0_A& zsdkB%eG*Wz^_9ONV0~?jbYKOt{XMavx;Q|bp?|n++E8fyMoXNN*OkRK|0&sIwdVrJ z3`?;AKQ6k#HN~Ikoz63C_peE`f8AcCcmk7Rg02r7efyy!D^Rf__IdzSOL+7$d4cFA z`FomhVA>6c=&9TP8u6d^uHHWu)xSW2fLNe`fb{;|6%(_*k9dfpnbhC!ZbQYaQzn9cP0RV zq54F)gg(#jdDMu~Ouo^ar(~nPw@y9esCm7hMc_k?yL} zVLJJo5ev52Do4*y8^k0Bd<8M2p0_Jx-4}C*O+Qov-3(-t|$(xclW0!)Ito|uK&W|1TBf3muItu)E&RU z#aTMkpX!0=VyX>m$GO6O!Fa~D7aIr6HD;CH!146B%z4`M4B(*`LU{M}a6%`7FUvFi z@w@7I89&ztJo8VH_rF<)T!RpNwp3T^pZqdg&^dQ17_AEX6LpPZ*7cnKnANnwG%tnP zPbc;Ab@2N7`u*|OJIcfbR`l5eA}RIHdh$5=0aK#|?}vcznFEB&5y$(@%8OX~Aifb? z_}%z62QRQk3@lkaiLHa8l+LMckgHz~d*Y$=G>f>-7b@upJ_I)ipT7-H_$mDhJ8Tcg z4zmi5f24NU8g3UZk{b!1rcqu6pqpe9DQ0>FpVXM2o7I~QjC_eaf$zQGlEAq}5TjmN zVNp<1%FQtzO5}&;yQO5u9|0SUdvd#O?()x2mI1`=Y8)n5$QyTIL zXOnJ2k^DXw%}{ioj?WS$qOFod78LDzL7Pp(fXET@h*Ua&kAr{_)d=Me#(2lBJ4uMf zTP3UoCL>#iI1wK$2*q~im-+%CHlde*f2f1Tt|=?`yWP&Sx!M=b=ogo5WTX{|-R>J< z$l7Wy3r16E&%TWfA4Sv%b>|eG3@SYZ+eOn9e+uEA1y4Dr%o%>Yp}eT2OViiXDX*gK zME0ncw2EC0MrxxzovNUusKafQ8r0f}kcSruD(*`Xe6d z5$A*i`1;q17)Yg%T*9 z_k?&zugaW>I{WyZmFslQR5~m%FkGrz$d-4Ylq4DmfJWtPg6L=FoUc4D$FXx^;YFxZQIHcPF1)DW1jxl%hcc=1>bm)+Ps?RMzz<>gqa*wP;vcIw z0gaamf}~lkIz7$4xlVVW8z5NXw0WD+SL$Je|^QM>5is6>bk(KMT>X9SZZdNE?D zhB28|DB718Vporkdd^V@mhl{>S=!o+S6hX)vGQyyyzHM!8?C<>o^(;rcFt+VtnkNB z!X)-+yt6`-SS;Q7+-t>o2XF^d7=DIYi47DH*Wo02K8L!a-8ZVA(+R;n<(f4G!*a;9 z611jTXrHIMG5}O8irRY^RjqLiUpP$rE`dSE5@g0M2BJfg@v^2NB!iX_WJucOVSRS0Bu(sgq9kF!~ zE5We05Nb8B#y;JQ;QE#IGT%-erCL)g$M=dUEU_<=^BUvr`dM40a6O}&U)Vp_qubeA z`^DYRPiH6_aqg>z1nEPtxr+52_3I23Rd~#h&2dBpw%N=?;AY_m8XC5H>bIF5f+~qf zLF7d5kmE^9(sek(pnuGu;ot*v1WCBFVPuBWrZUBU!aRZIP2-Cn0!diB;r);S=bI07 zXvUNUpqeyMOo8{cuE3fAP)qD-bEG5mNC_Uo^JBP5AopS9HjM&!WXHRugjc-xqly{b7jWkd6&Rg6f60Q>i-_jlFVV(Nnk9np{! z@te>~c-l#5RY^;H)dieik;t8{jO$PiA&9Ds%o}$;CQTxeuuXYcQ;!c^B<=e8+FPf% zTfLt(o9sSwck|PXh4q@2e6qLf=8U1L^6RIJa_RTB<40Ox`;k>+T|}fPKX2rg8PAW5 z`Dy_LQ9z>`^iz?2bXXwvE+;kr$sZ8J(WAf~42-%c9JeN23D-dEKDY*wBZCQK+1XW| zU63m5J4`b=FVF;6|1)u#rdVW~$G<_Np4?kK3Iq^P7d#M<{=a)E{hzlX)qX3XsiA$j zHwV{YBZ2wFh61BO*u^60_xuQ#k_Lh|=%FK*nMa5cnqJuqh}{%!NcBQIqGRZa6;5E3 zuN#w8c~UlVn`%sVA)n6c) znDpi+7z_<{;=z902STuS7?GRr_r~ir;NcxB82)qxVU8L4kY}V9d@O+L7vUK43T%NF_I(S6V41 zs5d_IARYs>ooFsIFa(}BB1+0sbcR8b0YZ&{sl1(kn!ZSr+s^63sjCQ7U?l8Y!p|m) zkc42`#+8_gLxOl%S9+;09alh7pLfec_Mnc6!>P<=>VdOF+-6AzrxE*f(q{f3W;AaY z6N4}iDQ>E`7-P{^W}4^9ikzn~q6|l8omL*t$ZEl6IQI6UZb|NFFW9h9YOG;wS|U3E zze@ZqIzKmr=OFCuz-F?3Aw$W{HQ{QMoj7t-0aL0$06Zo_4O3yC)B@%q(anrF5Qk|p zSjIkWv(yT{{5z@>v&2v;Z3k;R4c@49IKNy!C{z5g5f9~uG<0ca7fjOcmH4Fn0DB&h ziji>6r7%4Qsl0d^rg`b7(O?m>(ka_HFhIs?zRz)F{tz8qvC?Tyq3;33Y3ya1=ys%U z)9-PiI$OasThH;HSP22-ou72;nLQrR?xMnwS#-dQ;00#uHz5YlLJBTs6P3mCDpm(o zC`$&aeVeASGocV|bfi3CC8Vr+S)nClJOL(TJR!%w>wXVVB|k{I0y9W*M6nR-#NROM z#NAL&g}X*k)WE7|;dWd?({WYfWb$X|cLuWG6bDwpJpr+w-(rk!CEp-yCEXBgrObrj z52*G*a**r_WhUJq^CdHeIDjl|kUE1zf8?0R_l#HtO@mYehG-*X%w&r%^9w+CRq~m% zC+Su@4HA^kmHAs9iSr*slr6-wi9KXq2eoe|UW`N(WE$!^JGFb>jzC-#d)GVP+##QF z_Q)Y`Rq1{BkZDFA@yk(?e#gV;4c6xtF?7{$e0t8wksg$$ITR}?$QH~O{mQG$6q{;P zQasUd+CfdmfVX0Sb4zB`Odccy*Zeh*>}k(aeTEj&46<;q6wBqt%UNy0>*qB2$-ah> z8fo@cYAh4DU*Fg0e7s-S1j}2`?)#+vRO45ifg}N09jK2dJD?(DS3i8u%Qg6(x}Umi zzt`03ps2~k>T7O2fBjRV?`4u79-F0>fy7BNHYORcubX#Qp?3km-Ah_d^xQ3CA zZQCp4Wo3&jSnC8_y%P)$2(XfH?Fw=M?uuwU6O3J>b}h@I_?(uHoJQ`!EbRqvjAuQ` z{R1lK%d3Cx&#V7V?=e6CunWIvNeH}@o9ElYhH9o4*qCV!%ojn^ZN#pX>F&IjO5CZS z<@Srr0$N=)T!%Ki02`5oyzCx+T1}celQ#RgtD|vV|9eyH(s&~$T*RNWaufXn=W2=c zLrR?~1OrFn02f+YB5@)w9Sry`0j!HGaZ2DJGh~=I=XTXZQ=|6N>B7b6<5G~_-P8*< zs?*l#Y1UyZMs6&~s&FbpO;>QWSEQ1bK`x&-b%6RTzO*BDG|0eUw3&!glbNmAcn5+? zqhkrlkjJ<-w7mX}RFy>}m>fYu8Pm5b5qvY&@2Y{GZdQH2?7_(~q8Eor?z>TrGxh0sgk& z7M8vIJ=-@35m;m4)k=FFr|Tv$1VsKn^y!&hfvg+@mld}UslNVJ+>zK~AFc%h0=oPw zr2Fq?Bv~VuzthqGI8D*`Hx`zOP~T7Cgjgs;Y^VtGA=pqNFnu&}M`Y4cyVR@;go!Cj z<0-J4yPfF~h8r?O4I>Q63fi1cOdZK_>I%=BJJVI(hRXH}ESNsa1DLB=5Lzo6XF~3avc}E`HVWJZIk1rBW;G9RmLot@mqKrrTPG# zD(0@;-(P zX+--mQ**^0>z3LrX?DIziQsTscmfAmzkFOk*&f1O2@!oWXz5)Zv_MiRrXgUQE zcDVUx$f4n3vP`Mrj_;K(*Sk1|U@izw`$bmEtlK;yNhNKVC(WVRfTh=}4S2^4*0%X5 z&i&>7rPE!7T>o^-g<+~*JA5i;La6a6X^-qKp^@D-N*ZGW;fV$O`L9=v7)wBMgMOG` zb0My@PZhGd8)aQi#5b7IgL|1F7bo=bK7cTf!xhS#pKOwNO#Rd9nr48k?$bAPuLQTW z{RY;q$U$6pkmcc)PrwHqaoM-qJ}58~KX2mVjsC`Odec9~#uvHYZ_TLJEb_IFC883f8+7SLATC~xRY#TDK~ zr--ZIT;1a5#-zdYEQxQ9L5}J5L-9J7G=-{Z_LyR+rIAm}(cY>u_5-&TBYC3K=m=rm zpehbwCy`Z`d+K6h*jXCb+N^`A?{q_WHy}Ed8xnCK2exmGl-QdZ2O4tNoiwA6_lW@~ zTpqR^@e!q1-X+8@0fj#CtKH>pr$yURsZdUD)Q*@WCdo$9dqx@#T*Xk&k|$?Ia6xRS zfjRwu)LR^|q?$kfCIbJ`Y57t97g)yr71H{*YYG33lrl=1Dp=kKuK=RXB4j8!x&`@P zf*?_fMa`Zo7-mHvRf2ipy6n5M0M?BG51T~mEPAaIj_4^4u4tA$$_#p2RD2Udt=1bC zfYUXHhS$_28N5WL_6YIuKJT?#&T+@J!_*|hv-b)(BM5IK6n%&t`1GhTc_fAw{Jz`7 z*lea&R?1{%qjtjNBco1F1Pn%Df4)E2DgzCmY=_Km2cU;DRp`sW2O!NI*0yjT}RVG-SdPr$xLR+x2;$CcuJ$0E_l~!^3VSIM^vwzkh_;RJptO1axnQoA} zddO>`3vMd?5`5vX@0`8+_Fe)+VFeFV~1PgXWg2MM{LE zrCig1<`MYJ;EHoRVVfTC+YE?i4kQb41vl1Xw73emxMvq!YzKv{bAPd8vnh` zm~+B~yjx+0){BXu7KOhQ4sq&+QWy!t2M_X*JwhY8qhRrON3zZ`%hbxwxUxK7XBR)& z7O_QB@v4Q9nSzORt;}d|mc50mJ4*p39*NBw499g1!$Oxm;4R*Wu?c=Cl0Wj@RX?5^ zVlRribM8koqdGNq4y`6Gr{hI83T?RyUB1&6ie_)%uf5%!aV=c(^ihwJEiL>W^bkX4 zZ~3{{gFe{T{G?e1+`ECSD7MVxOy$OWncnblwVQHF9VI4TdhnWx(0zDxikTuoA&o&s z*$P(>5*X$=`{ZeP84YEg@CF!F@MMG|!}$5!UxjSlp87assV*;Ft;BkM1>g3sWgb=X zGcp9ZG<=V_&qgo&Xxit5W!mKm(>hS)6p4%Y>=}lHHjZVoqZ{U)%*zjGy6NybW7HwI z(L{uP-zpkk1m5w)NUnj~0HKRA?B>hNq7~RDzX2DdZTp!(%Jp0ePfUEv#d^@_J&qkr zMTJbUnWzMvSj1K-y&SA`!Y%yAcTv#UUklwqsQt4&Bd>p$QE2kYSk(P(US)q-et%ln zhivg-mZ5t$Un8G-W$F_SL(-f3KOu;V42_6RX?waX2+8e6yU&VUct;}^oD!s?^N&(c?qP2A17otRaRnRbrmAd|!k3LoMM^X|w)PZsWN2XDLN z{AxwW0qo!(UZUUVU|ic(KesQMowfeBb?xTuK(49J3-lJ9y@3R6@OqppN!~83xKWjxPr#aCpu#*wM*+2Jw;QFY{No ze-~REF7wl1afNK#-sVGnQ=90a9-I21^8vIY1~}ap=utAD7>DQ1lw9wsa(XN70yQ zTgW!Om{Pi0uy-#gv-`UxN3^7Z?W?2@9`Bz0?}IXAyOQ5l(|IL@R?pm(*KIf);)pf3uc z=Z&Ccm;2(08~f+`9#@^sKA&G-s69%K7Nz>UJ(D;*mddu{tLs0NZHMzzecOSrm|ms} zsl(n>rIIctJh&j?4mT|+u~a=2!SUyjq7I<*wSsf zp3$z8m|-owC2L||OT;0(Gb?Nyr5pEq6$Ng|v@-dt+cC^bIMObNl}XbUD5O&a5~Esh z){73bCO@$&vxKXGA>Y091&~0RuCsQ8&yfr}C!kUO8`nzKYghZh_W`)=UEd z(W1Txpx@U~dagcsI1RIE#`s0e+%kNGQGxl<{5rNk$GH4fxQ{-onIZ1|O-6v3f&h%o zMCy(GSusthHB!NM%2`;j&5)R>o-XvjLw`ZxrNHT>I>G&=z+jby2u9#){V0D9$EaAb zR5ll!ClMwH9R8h7ZlN+A0)sN{U^(N+opW96?b#JkB4pWQX)>M-$}br$B25bAUUGU& zCrf?UMgD)zvH1&1CKM1LAfx}1xzWGonAkr~hWr;fb(NC#KYSW^=aa3qsm1Fk^YdSt z$Y#U%UO=N1p&3}OjGv}>J*IhG zuCqQL-{0kcrfbjs%=SisBx`L9&|$XJUG(+hL%Lw!ohwSb`xBk3;J~%QZ=%38sFV#@ z&sl}PbZ$pNQK>lXh{5wGm-)aQS!&`0v#a2Te8`tZE0f1|V3HAC0-*I*8$`h*XfKi3 zyUc$+`udnl(GZ(e?yd`lsY4Kc5nI4r9sNM!rD@uYGy!AE(JfWmM>KN%>`tFx#Ei}z-lv3o z4hO_QA`oEQLg^rbD`Pd2FM490_s&uczB6Oj) z$FWqpE+&L>Jt_xTBq0|D9(puU|8(@2!#O|gkZU1(W?Iq?_PW*$8ygsYj|4$)zrxp| zU59R+>rpa18J1xXbpcKpaT)ap;(6`qGrxjd@or9}GV1>E46pQ9GvZUsjx9YuPX>!) zgG~_LNE59Nh{UdMXtWR162Nc}l7+rEoZnCzO*WgH@%CG40<8sF{{CA_QfEu=a26B@ zNc*pl%D=V9Pby($YxzHoZnyv8NB(7vQja7kP+L`Go|GTtYgt9N1c8hrDFxq{j==SJ zTHgwvV(rY*?qc@7x4oCa(7{pQ{Lp(liTzDJe7liBw#v3Dh)>?I+J3xs@3s9}XY&2^ z{LBxeCD7z6Knvg7cZ`t?`vOAKKce6CD>(yWiFQMu5{y{c&oQx-r8WfamF!faX(Bem zjiX(rC0rL1u0Z89tg5t?#2?agm~7m2(FM>x30jt8U?6)r7Hf?=nLIAPal?bGtVIy) zU#Y9|nX7jVeO>n;D7Hq&Zay=HkoBusnKF|y_ACwGzNTxRzbWTJl1vM`d`9PyN7rud zWiro7gC!B0_Nwg*mF{a!+9~GRVf|32A7Ow*jblv52@g3qzHxJXl|;xbS40h#ZG8@D`xw{;hRmNs|I{N9AVKm} zHpwmOG5*{!vWSgk7M4q;0-#!`mU9EFG9X?~+Urvw+_vi^$SQPU7swO`Fu_6eA=~SM zwc)zkmcqNv{CGw65+5O1(#}ItLE;>vNVWJJppY#*#dnUU#Eb^gia4Hk*Q0C1ryTCd*c0N;xbnU>N&y(#+EDz z%%O-#*#(A4O9rEF;^EG+FFsX9yQW^Rka?506>W#vs~h4JR*XbAC0bR;5xzh?8Cj!> zM2J6E-W@@e>l>Dte^v>i9{wqND0Em*;wYo0HPNgBwwNCEqIneRglQ+G-G zQwIlo_9uEPf81u~H!SFC!0R?+kNg{U8sVQqZ((|mU&HmmmYJr$=U`9RyxTwg$%I~X zaLa{U<47~f`aq_3>Dri6S%HnddsBn%4QL)PVtex$Ft?cex+7riYBe1-8V;l!rsMce zz|u>+{tVtEtx<4!x+>txg++udo>Zj6xL0><G)2h50GQ%(C#jKt4SB z_?f8H9a*Xt(Ky%omDpAg_O*hD615gFS=t?q`Pd>R+G)yaEFSYjtAyC!0f#~D z3Yzc@{LdJII=$Sa+d8(np&7l$!DD z=ULk{e^ViP9P84}Ced-fu3=X1`dLW6MUm>zAoZ(C)ahBH z67xCn@{1zHHHZK_v%{)Fy~aIFFPZ>NK(fC?qFCi*u|WJq?vQ%sm)vISv_d*pq+tBb z9dtcQS|J*oft)jN1wruJd*XeCfAH7oU;HJ|X!F1M>;517#V>j9ig&oDL-2gtGuY2V z&^t{Mg3|v~n0~Fl$YAH5U7onMJy zQ<$WjqmzY3&<&aRchG8ou}yjYNFP{{wc+!M6a zv$imD{Cn;t{&$zF;pqp#Jb#s16(o_HC5(R8FjGZ;Fcj88bA5R7x5iC`%fw4-7H-W$ z?$^D9Fhp-4{>0k}XN~4@dR&zET8ASo?_>U!uh;i;b|9W0F*k7`GRQ@|O4QV~J+_o} zu(`uABEO03d3`V#C+h|motn`Bu>!L^ldh}OU?)?k>Z7%^IQ%OVj#BP?nf3|3qh<^H zwU!PxjfJ1!*HS5I**kZEZNZ!7T-zth2q_bL)sCmtMk~Y8-Z*aNU`KD#4*ZXKa9wL1#?;5xF|knp{ufV+f+7(&e9-k z)`K6wFgm2=E2;H`8BXv`#f9u09KBbg)-%R< z2G$tip{8-loB@OCNJlU*<3o2)F}KJ_gISnGuSizA3R6vLoR%F6Q$4<|PE;_?CJf z^GJiG;gWubIdB*V+woGq4T$T@n0S^y6#CY{8GR;cGTCdwmlFu2-CNsG)5k5@-wCyB zU($wepNi)+4|vrv-OUsY)+R!3QBzR7^Qzt&xyf%e+xm5;19DvRb4oZj&lhX{SoyOK zK0wQ!%YiLt@L~wc72l-D^H_CqGd4i2lvgOw@APKDsQGA6={}Ql?xpen<_v3aA`Sy!}wYV7ld(=Ku<1e(3i(RhN#MY5k(} z49=bc-zsP5pchufut}mlH{~v|DK`lzNiW2C;^*S_*L;0;)&SfQ#tSj z2M`dH_Ii|_E4rUaSF+ypIiSbaox^sDh$25h%hJF_llgGEC32!a`ryfwEJ~u4{S`ZN z14+l+iLQ+Y#F1jOL*NTkp-`6j;Ov}EA@>=PXGGqUUL%~qh}&h|k&p$vLH9TvxYliJ zQe0T=b}G+}J3G0)%J9diN>w&$myl!2Km-wg*{q%{E){l(v!iYQa_SOTx#;~$6Tn9T z_|=4Pu+FCP@lp?)0uhP(rEKK>XP_yHx_rv$&_dJ1alKiM*?sYOa~N04<65|@uC>#u zqEmoP@siV0jm6~D#vSM$^3WKCx4;)57ErK+*bkExBx18`e>hR=rq1BhTuCk((NuMq zDnk{d8anfehlU(;qyt1hdTW0GAff|9J$hL9778H|q@-zo8WpfZ$yz_kMZ%pFe z^!DGxC~E+2X|7-!4AKyOsNKAn1j*)Lmu1th;>8K1(G!}`@ZWM;MIN_}2qR{UU~yR; z)@_WUSL~tru1o1Rd=EozLFeyZ=&GDaCFcWM)#V2$DTjA)xH>^gXVgrHZMGK)K06Uh zw>ju7JabM;jgQoNhYyELc!VCBzC12jVe1XGeO(nD5{%F0Hr1f`{P^)KWkSOyZPcay zsm&ZvqW+P|P-|t@qRZa$+)eT{$n59rUvunA_E>x5z6#;O6Fj9oZzY&9?tpB}%2s{q zxmR@|YO|fKvcJYGx*l-EEP(Dbp3!+UP+vMJ@3n|Csp@MhmKIiHV~X?L#&8+zzSSr* z48U&MZ5lDegnN60rSk%XO%B0XHvkI745k%lkRuM{P}N=`uK>C!KJnpOq>H+8MWIM> zD|`JBU1kKc;&e@M&JuOx(Mvi)JozP;sBUo~&*{491+b<4a zT*sB3?nDF#e6(aDpP?3bQKZdDy*JTo<3V*-oj>BWpX*~JdET|fCqG)Uw?JfsRr;09 z2zk(!;Wb;Wc+hUOSOm*9(n7yd3i(|fmy)$PnpAf|oJ%J*R9)pQwDOoK51MFhmph9p zG7W489YDhG|6DHwM>0|hem%lfAYZ3A5lf02or^2+H)Ef9D5NsZ+R){8R8jAJQ&{k_ z;hJC5t57j>!Oy^yEAYKYWk7H3l)u%B-{_5L_RRbYYlBv!6ZLu6jE3%k?+=*`vDcCg3vcTzVx2E-#M=lfC!st9+s4!m0z+uV+oW&fhD zy7B%ji#kWFekK+DrgW%wZqxqXb@y*Z(zxYTWc7=YfPw!no9h@lIXfB{{r@S+wjGiH z!m!zoRYR&Z&8B9}a#g@gU85!oAd4AN7D`LPl3$zzM@yaiYGey;q=O=17V^oFa9?E`gfNa}7z;B(Qq zw4A+%W!r)+YgX;hSJpo`NlYoM8kFT2ypUS@cf+^{+OJnur0cRt-x~$1h29qG$~GB6 zxa!j=Vg%}N!nE%C|FDZQoSUWRX&|rz^$gDMWjU8dDhvI^Nk-TZ;AC>jElXyRlb+-pEC zw1xdCTNzz3%(8=Degd7(f}+qWxp5mb5Y^DKuszzkDqjH+f05W3^z(`Vd7>S_A|bs) z==Z~X%mgkl-c9RE31I5w(0Bzr;KE`M-%q>C)@sAlH>d+VA_!vQ1>G)8)O#hLi15oC zp?*m(7Em^$v_hnPyTvfxg^C^PfRZAI{ifO$N;T*cR{0W+4R7D-bCoT?{9Pv?>xfbl zrY6q0as{?Y)yE6AIkg@jc61t6<=3fT4ohEM$MSM@)Ds10b(d1W3MjwNW*spemt>o zP=BsSCZpKm3v>udE3C%akVPIWvHspn#|(*$ldRv^G*4;ik8(eLU-GbE-|P9L2V!G1 zqaFGjF1w?x=?$;rskG^y4`Yz;hTf|F8ZQCNa#Huv%zVj58u(*3S?JSmJA+P)cY)}9 zcfDT`bRX(})IaD_4jACv?wR0A-b6ywqV2KZ&d<&jLQI@Wht-0>l83Y1KCVq7d%oZqP>y zEt4t8qul|B2jt~@&8~T{V1R-VA5Iadc=r}p*HB*d! zDDpHpip>ayGQ_sdhO`sf%>s!15&E4hJHo7&S4?;wS6<6Wx3;lqY;?Ax6|r|6U$!k| zvjES+={6!O7xnjdQYPaeRf=S_174dHE)?$bKHSjw41z+Et$ADFmh(%2SZPZ*@P{2Z z9>Yj$cuzwe(&uJ}uDW6b{nUYZx?KF42*_QS5OJsLx3Tv4l<}$<@8u~ za;cahs79cqx^%asMJh7ZF{c5xM=^&)bW@rkKZ^vF2CwQh5!tG6?kyP^7QH6anH}s= zmsHsu84NFy+I}G8XKTFLzxn!_Z}|L_Kcp?OjjOhKJsCSm4@ab*;3Xnl^~}aIt;9Pu zZjqfb2&S^Wa@mz0?^m z(ylBlp@mZ^ASf)pQQD(iKUrJZ z?(<7fUqgKgeTW0i=iVS9tlt2M!&!Knn=lR!AuYbb3O(<~&0a$&-|jTlN8mTl+wwNw zN_4;Z8@G2M?Ll_#$Zjl{i}{(DyimMgX71SVP>emSR|LfsCpP>HD6Z?ZBn5+K9q;X1 zS8=l+C213>9lFULf*J-u0z$`Olly9*?%17=aH-?vsO^<&$=bDqTOp=Uja7XbQi(5B zWLva6Ylb+|UA|>w$PK|1qdzpzjYVP;ljT}idUs&Ak{7rb_G6^1x^LB&Tv$DKr$Q39 zZ636mTbY9*gkmt4syIkAnyDxkW`)0_DIatkH?&5yzpJ!Hf6JEhm#V;YcTsdSKqQqw zlpd8Lr*j#hBCmBGkx9NDf9)9~sLk(Ul`_SZ8HK+=_aq5R{~3eR4#VcfueAtzpr#Y- zb_^=b9vA4$Mz{G`)OqE+oN`6!>T`jX6Q-)d6C7byz7`{6UAbe_iGsI~htZrS%SN3? zboSDkV7tP9Vxu04s~#K1-bft~GHDK0wPiSNqqVa9!o<)(-dvCNVbohdy{;)MafG6* zk}Z?~yVt<)ow^4mS}@C)EU&7MO_>5swu`;Y2YC*aT7tGo1>T79)GCxg#DHJM%&el1B)NG(cc=-lX;HVY2`rAB?ipqm zyPy1+l7V3FA^8ODHj`qOWG_0oOgJkY0sNLgWg6?dmNHLefaeqgr6oq=ra5xJdV1kZ zyjspzf5GAhBxTqihd4=7o;(6)?*ubu??iqr@n1_SBS9u{_Rgxh z`t6*K+@e3Nl0MqQ!p%T(PNz}Rl5Qub*XlFH-?PfUC)#VJMiB2x3{lkxE6EoQUVsGI zk3q?grzfa;f+`4eK1R$Qg6o#hbr;0d2*Hbf!0Hmo%scXW&xt#fE`Oh@iHV96#g0oQ z+Q$-&Q-!8O8Ckj+^KFsVxl#;KS*Po+15s6Mrc$?m{|tjrRnf+H&zY&XWp};kJyE)P zCDwb5T{Ocq_ELqH(Iza@if1WJOOYo1`oiR}lZEidmg!Hj+-pi!uiFuo2lx7WD(V}< zuOw6Xzv~owQ>kur0?sKT*?mC|%bo6+r`K%XvZ{8&$2rLs@r^z}sYiIp9CDI)hXbg^ zaYujzFY+@KJy2K9z}i#1KdNdaP0*G4V|T4jS62tKDr?Gd`&z-`k2!eUFZHhG%Ow?t z#DRWNr-!Li<>+U-C%*ZP1*-SiIWf%MWldDb?Jh8R#%YMP=iFMg>YpR>gEzNm@H*>> z5a=+($r3Q@F}Ol<4`LRK=LXuAHz$}am4vs)on;VgSOI^o13j_(=O%obh=AsplzZiX zUV%clMLLeK@K zS$bcxo1$$q@9Thj*_k#vN%!5W4gGpPzm$57BZ~>ywHgVUVht;kwLhB)$=Jsz%5$V%9$JGPCeZ`Fbe)q z`=Y&hQ0_qcqNjOqd%S(c#VRUyqFsHn&(POz%<|hb0Komp9TM>e^*(HCmYN{2AM0IB zdoG_^;m$v%#eba6qzMB90K|g*zf6qyi;F3mSeh6)n-~ii8JRdaiMSh?*gIR;+5RWm zu4RWThQWi()nde@tt|+3#CT}QMLb@NZr(?Npoj>HM=u;0rkeCvmu}qMxt48{wEO}6 zitP?ij!-UsX++|SvX^q4tj^{X`}8sxD^8((Sy0J6gZB0HZf{%CP9X$-bQcKky(Xw5Vis;HLvjNZc9249 zFI0r8rr9L-o-&BbdyW`hQynm@%JFSu5$_9yBN7|xBAf}fSXAAI#DphRp_x-g?X)^H zT=z9pNe3S#R7BN%`b#@DA|P59;xx%}BWxc@H$F85f#?MN0|*dGw5}1_?!s!1MYh-K zUPZGwrdRdD7I`XaHD$uHhsQx*7Wz&|(@QS{WW*klrn*^9ZK*?V3m2YGokd8Xx#E(n zm`^<_A{Ky0z4zN^S+e!CiWea>QDI3}A#q%hWJHTOF2g@=xe3VTif}ewd=_?LzdY<0gXl(DCd} z59_X_1YC_9w6&v_Y&84c)P%FOouX`A%Y9_dSs7G#AW5iouMn1N-voTR-D%nr+`c23HdvN-3v$&PKyZ%IP-u=5F$4*aP!@3n{0J!qnM8`c!V2p=HWaa=w{RI2wKA8@bUe67HqVHL}W03`xP&phks^=6+f+ zp3TpQxpQ9aUQ&F`G)p@nFelo|oYM4NAX?KiumH+hS3n+Ipn(~FCaw*FM^UTIvKREQ zb2^3(yhXF-TN|90QWd|b$ac#q!KErczIhj40ymJ}YB=rIt#7uQuJ}aXCM8$}c9R!f zPLcmEs~E9SEd9S^^#%d}fcyVdR{!BAWvl)**qhaafKIWuMR68tF$3M?TnaH0gQDI7 zS}Ly?Sm|xh#7TzM`gbNB^&^fiZxOTJ_2Q+P>7yiezdaKHDe)v3zTNA&i~Bk2ILnF6 z@AG~b8X$Mq7s!H|^oAtW*}Hh(6bK6nIlM4!V40j^l1;LuO4sgGUE)iG2(Ah4He(U> zum*?r;Z=`4aHxdS^y@66AO()mrX?x{X^Ze?v+;>j==EE(h*RyQ8FM$5TQrild41OGU zJoHsHGi9DN*qm@7EQ^qp(j{~7Hm5&e*g^-Fj2I}hia&3ujwB1UOSv4Z%ONg}5^Fxe zC5ZeYxt6TA!(M^lZNF3F3p2-A>2y zY6HSML>Vrbe_+^}Hke&&v<%x@43H~|%||@(%2+WIaoWzK_R`O9C;DWw>o#RiA&|Co z&9r)ml%0AOdYuH0F^5=miMUfJ+lS@fY2JQ1!=Peh_PE)vgqW&xV#by7(0b;Wv7 zR2kBMBA-iY%NPJ>DhDO4TZL)8xmD*0bkS$=Z9*BZL0AEzem(^p*z$K-d-kc^e6?Vw_Z+r(lDLKN)8Z52F`7iSxX;qPnp}wBfi>q;^v2hC-S75YkvR-&eadtItuUPMw~2T%zGWw zEsxrLr80K$mh@9tD^}o$MjmrOtre{Y9z7Q4ETtJ(;HTLEO^$L8DiFT4 z1w=$asEiFNEaWXdt;d0pAd|rSA^5r%#iVQMeFOZGkMm3nD46L##hxwke6jlT@@h3{ zvUZ!#7bs1T1Rk|j8K>`(`zkMMnA!BMDQj}SCi#fIa3?3w18kB>XZD6FR$-sE|8}gS z-r^+HVEGYr{HDvkaAJmo#(H;}5hl8LCHkBG2n{>SlzbUPCZC0^a{JpxnP$;xIeUpg zH@!B&asuOVEo7WQ@m`yYPxktC@MXH7o%$rFglAgD@MHPBRk zZ#${CE#!15cM6yrTd>?pPmA#Qel2E))kYIJd?(%sMx>1?%{CuD$iNTV}~M!jY%q9Ya); zH+-hU*bN{;=SSe~<``XaqVp@*L=tQfZM$dpHExJgE}Gk1aW z$>mObXpt4T*mBv`WOFrWuw;81c9`njm*jykS6;goFvZL;YA54)b74VSRvrh$<-}Ge z1!ip3XbJiF@pL}mR5IQoM@-d>v`+00G41(M=0no|2|4 zhG=9x1X{0w6X4|SaU&*t35@7@T%#g=t_g#<#)x7PVk`+&pstEefknQFT=5hWv+vLi z+!A9-7}1s(M;HzaAgP4@LyVO%6 zCfx;}7py%@eP%{4`2c)KSc5mm4g`Ht=A^1=8`%A>Ec?VM^oS~rDDYpEC(R|>HZA+? zVbC2YAS;OZr%U5vx(zxes1ST{3mU< zbNoxR<$0ul1rU5HZ)tOG3z4g7njCtUD8mT?^uGYUO`5W^)-(=Z>9)PIK>PXgNp6e> zps5BbO)T$EW~RqYx4Hqiu}>--u%nG|S4)Tm+&)e&{7|fk(1QWDF*Z48G+{KitH2On z-WsTeiBywX9{CX~UYWmy(q~jy9yxT|{e7_O$Xxey6{N9=+1OQ~SskG*t7cJ@MbU@t zFv+NM#Rjx!%p?C#S3*iOqf7CB%wSCyvQDYN>W=ca~XvTx-V%5Ut;W z`WDY5^bPaQ6k{Y0HwZ11QQTmLy&sdE-EITgA{v7$qgO<`!G5(56)Nb zL&xRFuoc0)hFf<@5px2Y5aAWAzf-%B|5H|EzZW}hm2Wy8=c~xn3@;H7ua-ym_eCbR z-s4cT)Ao<9%HUh8-lF^v3~CTSwf!FK(5ww`8BST(g}S1)Ra%DJ^KIO#`LcNQYfn7* z{!vt5N@M=CbPkPtmXctO{@$UGFjyAhKI5GqjKr3PNHp55453=AaC`AFKTn!wLu|2m z*!2t1TOqBv%AYCw=p0S<;jT2>%#3i5SxFfNzlYjL8};@1V1sh!A+tuYhQ?mjX6`y9 z%@}Etlv8vwWLv_q3uVF_u`=5lg!WQ`sI*%jys@H@1tu@`&pBMvH0cH_CM6<_GgOZx6=vYB9&BzJQv`7kr-~MWYfrgD_y!XYn4H)2oZxp|2&Ri zg1IVzAv-BtfJ=l;#Kjd|0nnIpHI~>)AX%Izs}A|-J}JEDXU-(?gJ11=|Bj{ZYY$8F zBcFx%u6vY)_+GE>SV`F_cnDNnw!xgGsZ8 z=WaqlhPn@COk(vF2 zmdWgh%gMFp^Ys<>J5*{ZeL!VRyDC54GIU!&Cf*Ux#+`j;J~PC=W2V;lBWo+}qG4L) zbx06>xX>ybvmf^T2$+R!3V|J0U-DPXoW&Gda4&7w5at93PlsP0lMm9|LRvlN%iN4%m?F@MKp%x@;?LOryaaLYC1`)mMiNK48 z$;suBofSr}I=q3H`L?82x7BcDc+1vfL$~7nmYfBPXQKiztC7f$N9B=T+lZ;dedBg8kR<;74@oUZ~_<6;1kMyM4yZp>%^;r0Dbd>FN=*G5s&Th#HqnYPrCi1gubT? zfRkF~#{UT12;+DaKR{L|;pxXnp z@l1HN9qzIU)#DGD7zt4o!1nHw_Fp{duD}PeCs*^|w=%?8$Cu$T9@uE5-7)sh+Z1c{ z;(f&>kRcAV-LOJLO%so*Vc|ZrFn|X_9y1lX1gEK1{`^u_dXd_sE!Jg~;+hs0jPk@F z)W}FVqr~(lY?GAs)_=ja+YR;?Y-7@h=hrEr_BZ9Bhv9=czA^-TBOzI~axjM>ds+=A zq^a!y`UP78MFpzG#$T|-@wk^jaWGnX#x0Xiq=$A{EM#I09yd5T=#{DwR1S8*92|pK z^_;TNV&O^l*$^2N13dPQHtGwDs|d@81Ep3BFC5FvclMUxifIDiA`+B4|ApKahIR%l z`%npz^NpT`it%Qio4SD=o6(*#dqrRI%(lX{lI2}7da<%hNG_bS?+$Gy^Oi7DXR7n4 zKl@8O6$j$0+gP``l{&81`|i{+tWN0JV771|F-qn>s^H-x>KtGBSTpdR5a_EuWy0;nP3VbuDF9C113ehR)OfB8{92|yt=GGz&-WHUI=OOEg6=4=2pdE-~vP{t;E&nw>c_n2E1sBD`YTT zjI=Y~zRGW!j8vDSnUODr9MHX9XJ~(e%Vf|nJ^K^DrSLHvYK;E{;Kv_nEtNdKz&*wzA$mx|Fv!y&IDE1T}>pynd|ZCs3= zXkk*Ar)SY%?>0wfmEG1tT}G@)lLzxRc1yOnRUEFl{=hB-XA_|(6Qg`-=_Y#zd`61j zyUhOSf781bX{FE%@SL>9k|fKU0Fw)6&K9O8_Y5*yjl|UnImPYjTI?W|G)++*9RK0yr@wpYeS`*E zhev%W-Kj1kz=;Y645=IhvIBmid8el@k(iw3B(G#Nzsjhhl{KB64jmFCrxqShM zs(kFh+3xu_MPGiqJU*@kc&(QYnioIMSAxgnaW0k*vjyg%AfBnt-a?^%uy|HQ`nk3x zYXIN|v%z9N{oN<@sNmA|!2L#glv}NLi5p(kBsJg1c9M22)yj)Q>Xwbx4T#Ev<{Un5%EBGX!Un zdRu`I|7C-M9MM{%=h8&&pd>{}5v{eVcqIE4s0JqozGyD3zYn<=qQBF*_!mbXoDIU~ zF+FA!^42!x5ZcVTc@Z=)3aLbYiX3O5nXBJ4(Sp4E8t5Pc&A`6zp&irNa_!`3M&+Cp zD=?Y3=h->n-EnW+Hr+3ngX@BG^_G;0r7RvtuO!vLZzT=s6S`Cg{cLF&9xO5KP%O6~ zOpN8OZwNs|s_C#afXSR@E}Rr$+LUJAPXxpzeWotZrcoo!JyaIJfV`hZ&k#WYwq#|9 zg$$OHmexz1U8mT@uz>Ho2@{1X7-ybRRC2K8T*0^l(>#~CHQyG%0cJErHReL2M~5`l zq5TMb-UXv1nhm%4Q;P-_@A~euoMm866ItSnbNA5HT>5$%Iw)Dyja=BPBoWjrs77r* zAhJ8u@c4eC@M$sW<5?F@n}|{xj1h`KIyd~GwUThB<_0*I{*s@)b_@{7p18qL3U+ST zlK7jO7NQ1LnimsiJUyHIC1#$vK1zy1tHMu$Dh@fLKuiDYHhv?i@cG!NCf2@m(NZ*j zsy59&-w1W}*_0pQ6?EcVTOk7TD{zTiWvc=eyC|?I5lO27KOmd0;c1?Tv?rqVC8GxI zX(rCTyZmnLD5DM4ia>-_pvNNuj!XfwPk~2j>H809E64}DkojdPK0yBmb%_6WX=9-O zPaUGoFP9i$_)|&>{>%WH_sdK0Z(Bidi~Eb7e;=Y8!t(a%uDP6ZYF*t_eNoL2XYTnO zSzK7{M6!gSqwgNSoSL|Lw>5Kh_51m8i~OB)se~coyrw-t436!-Eo2=pm3!mX{%bxx z#C~G^FKXGc;}B(#--8d=_XW;`Yvx^Of(V;f2pfiD9kHZo=%%2?nQP^^fvPA%cqZ!2 zCJ_kPVzx0<%!lT}KwogxvLYz2It2>{EJnFnV%%lGOVwi7o3f~G8bkBYL0~#MuVFZT zQ<+9KU^(QV_6IP*oH1?r;qpX*#^Dq~&kh!Fy7NO4A2go)3%O!{dUw z_zavD696#v9e!W~esCjhU^+k&X8;&AnWt=T7?2u46W&060vD+zGWoLDJF;yxeeO~> zYQJQ!Zm__C&`y0jvtiQKs4$(u!cu2yIi*~AZHc4@s${&Gi>QMtdSI?x*^W^{&vIfS zLwRUP3DPLr#5J>}JzIAI9!)@D4UnW*-*HP>i*r$;Qa6*Fyo?giyghwWwx|>&LrKzT zN&O%p{;TVl98AZYu`;)MRXkb5da8t*ltCt0Lv$>$NkSR3q#7lXXT_%VvJFw#C3~b~ z;I5`<-gjm}Hjkti%&nZ? z*!@Z|4l5Lxq@3Pz#0z%^um~k6Ls7Mr8T~#xmEf?OqWK9BCyzXSLY}??&Uft^N5A>I zR&q@!dnq?@)J%!wIX4Q8Kl#Q+4y$nIe_uAhl8~k0BSHGQ=GaXC#eGG&T-2|O+%Vo#d+E!5GvR$GbdXlKpShbNr`=mVpa z9O)sM^>FMywN-a+LG`WPWn(YcZEO?mKYyu?+@?t5 z9Q(2r(t>%3ewI$_DAE7bK09=KA0Eqf*Amt)d=~$RiJB+ zNihwo-_i|wz_Q*g8>}Dt`nQl))T*DKBr5%UH_CtmvVaXnv7sc;@^}tTv~<0DeT^1t z?#yH|lGUzS4-bls?Ip?Z>udGZZ@? zq$gG+Zn`Ocq#;>T$3{kb`Uv!i>v zR_uDooVr*F4asG)n5k1^4u^RG_KEjbCNb55Z!@f*$AJ*DfH zOUvd7c71J^W7pi%VMeYLl7oODMZrOxXxWl&gdV97H{;w)pFpJEYoI`OkQjg5!Yx9x znw+Sc8XN|Xs9%$GgT{$07rjbdKh=h>%CqoANys%S%i)^&VWYBaYb;H8=!)ojyV8w0 zvD>GcF9GfmD&a0O~D$iX``dnkN_%nqwYKSnhh zJsGVv9I_JFeBxYa*UPNUUmo5QLtdRS+lpA(5_mGG&5Yd*pTlos#CoJ&o<$3oH2tyiaTTsT7T4ErZ^J@23LW~9gsn?>;mm znWR*IRCvRJFAorw@#GviOF?;m+28tYs&VEtgjONHm77AT50|!M-A$aT(YKp^iQNsY;C#nz1Px?lS-o* zx29mfSUN3P*gOBW1_d4W2Rq*JAJG&HfxI$xcJ2w&jU#&JwU3wmd!BaH-%KJjYvBrV zQaHQe8wcG*)lKt5g%{2ij|e7EC=`r3e@c(mAI&CbiJ}K&wvA8XYzwYdJ6prQeV-&= zwqe|1A^{CDC%X-Hoq**z3dI`_)g4lS6A%*!KP0i>?IKEpQ3=T}5E3bkq(v<=EbU!Cd}4sRv!^{hEf&Y$%0wBk9~)gRES!< z1O={GiXxvNo!y-&lkp{}aMg&lAFFEdygaEOcgu zS+f$^gcL5G5`44?ylC8IiqcglA=}Zy$t$NY))-tYR*9>asA8|9=q7mBuk3YyYIBDz{80P#cuydh{)dw>nB>%O9o zg*6{=YJ6vBV&!y>O2E?&(!`&}p}f`P*Eel0buZGVMK@LnMh^2Gyip@D6NAy@0cnU{ z0QYucF79pVY_(%zb^h&8ytEV;N3hhi?CilqBt1tL{y0_7@PrFww2F_uy;JdBx(jay zE`?ikHJFA|WMF&QhEq~|aQ6G8SL1%Woq3*byOW*PSyE{Jx#zsxBCWYY);I!0v3D0d zqVBl{A$;Rb&m5}H_rXV?EnDcT{(6QK-O@9$^pCY8DB2@mw8BT5K~{%TqHa0Tqrp;3 z+fqG1LAYm^q|)pTFk5x|ym{n0ii64dN6|U%+8`aPM0BhORboFJ)%-kKj3z_|z0xKZ zZnhngDXtB2F3>~|i$3*{>|9l-P-ypt+mldr=}KqE^#Mom6TAv+-@F;+VxF(pAwODj%2x&!o&%=XiB;jYv#KOXAejrcPB zC*r&QJInSv;!7SyG z-K-Ua&@tfD*7byo>cx`J-4Uq)OuA7BQ(6{#@@}+75ZUp>5!z0? z_5h~4znU1FKC46F`lWdB?N6ID&JVjq@9JJ2%SF9KF&sgu6+$qAJK0L$`gbR~_ zTqvhAH%Eg7i>xG;s#4U4ff5gm;fU%+nq3anW_N5IH`)*uCv_8{rysF?n)I=gJ5O** z<7dsRbZwab?(L^?g25@bt$JP*1H2_ZV{u}ptG_FjPY3t!EF0k^Xu(qW^qg)L4PM}4 zDqKRjkI<8pp?c9`FMv<7d3Pc^IzvnpzaWRW=rxHp9#9P_H@)s#M+?`KHU*WZZ> zgg+7$XlfSI25|Rj1eQ5}B`WSgH-?3{NB)+m_$_ChB_Cko0DlgM*8JDxjLzJ|+MebQ zbViNX-cH`>~cldtg-j{=`x1p zl^#P~L&N~nv`(L+8nwA9!u%=Gh6?tZch3iqUb!O56~}n4&bX#RyV2NpASHPP2{x}4 zNkuD7c6mujf#i5KVcT+GO@Q*}nLGp-1Z)}g7$krGSYkBKGbn>(@<*x!lx*lPk`Y;m z13`iZm$6G3=KX4nvZ&q*Wy`W}5N}Fiit>FlszL2sy1i8MDdsHV6-~zNz4rc#9Ck98 zvddYQ>eKptof9b`dDVR|(X!yK>g{vhbF|eZoAxD?;83P{Y}KNLj6*fSv7`OSNGOLJ zf*_$Co!Zl9ocKiD44{iX5y|yks)laqkV>knV^|ax=t>aO8pAKbPBzp--6&a6YDt>3 zXMyR_04+e$zY8;GuTrdgA+GH>SX$6AittF%z!^?zZ&WIXQwBnBfdq7wG)9~yM6lg+ zEAFD~%!01opR&k$(yZ`bV$4>9azPME>LmB-8lYiLXk_`rQ}K=Vje72q)oGGdvctyvM%F1{Od=UWrV1ptk3_OU z^y~u8wSQB`pd8mLd>uFmlIZf3?tAk9yLV@RopDFcWBjy5#3`dMAMJzx3Mc)u*bm=X zfuVpn)q$%lD9l4&I&^yYQvz)$LBa@0Oe0VXyivMA=|m_E4DLlDEhBIu(fw4tBnt!4 z?F$AvC^(9eW!nivN7_9ntgJsh>^rETfmcD)Q~Q#>y^%)YI+^#1R~{u9EUxuaP+)4& zp{IATiB#=`sRtIGw>|tWl{ePFTUtZ6yX2bx zoUlw`YTdm=`_>K)Byz9H*zJZYJ0RNCz8*XH6HE97Y`*ug_M6X#p%Knmd0GVnSUp=)K{mc=#6k-pNw|JuR+T2}v_?h-cn^CxzWzgzp! z8QTe&n;2RBod8iuO;E@z{>5ql0RDb19xZXOlwZ51UrX)ZJC~%0tdxYT5)A_jD?Kx< zr4vtdJzO9?iilfJ0Wc&ro_RHW`u9$V2)9l$4lyf*;g=&t ztLY7(N^t%C1n6_rkj@^kGt7rm`RusGVs)FA@#Yf>W>Wd68fjaawG+q?pG&JCxA-0J zqB;cEj`kN~!wqTFOgQ77O!F)0CZmCOmBPCMO-5}D9 zGzf@vBS=bjN{TefcUgB=Wp($ztDioXhxb0b^E)$h=1iV5j|Jc%hk5hQ?hv|xE>CRI z!YT{3562{fIe3U?k-N++BrdS^xRffa^F1SYk-;%0QQRipTTfQCE3g+!gQ$3y$$WDs zSl3e|PBRU>AeMqIE%kZLwmkJ64l78qz!Z#?R<2+Zg(Oij58^tNj7(iae+S07@>mWi z>43AL=EQ26UN$<>bLH~F>HhN}xNnEH$BOd5;o&x#l1^8y?J6?{qE6j7^niQrwS*Jgz@} z-1jl!y>2y<2P;TK#M^b74Z8bu_>ff2oTGG!lK$z$QrALZ;*i-{Lxdnt@zVZy+=i2! zw_Xs^^{TqW!#cm^SzaK9Q&{(ko~QKOgRw*Frqxc@vABJzzE_)=K|bS$ zOwIX3xfIVg&)7X5=z2dBxf%{7D4cvwb-g9wc4;w22Ogyrk8)dxkr`xb8U-LOrCOI~=z z2XZiN9NDP6#o157p)2KSC{aib z;HsrGd_j)JENr~u&b}FwT(`5uyc?Ggxf>vhJkcC^*f^%8aj@wP zeEu?w?;F(6%!cUd286^sS^()lITmMu z+u2d@fs;0VpVzy$tx5hgyE)nnl<&OSW(n5bbImlSsZXcC1|U`YKP=I(U`kH9FM@5W zb|JD{kwdP`L5)9Sh;I=>rktsfbT9Jb1!Fmr&ln;BvoV=999-U7g*9G{9NEKm7b@AF z!ME+aTO}_YR&)D{)#yfzi{e8+C{iJ-y`ZSCQOJAn@PrY&)WpJ)c;1LyDF`lWr2GkH zt`wPMzbFo|NgNhd&x+PNG}DXSWR^#;H5`;XyS5mx%1JA!lyE+K2lG3(kn#|s~SAHDA7(w2-z z8MCRDxyOm&#K4K?fYivRnkAr+5)p)a=<)WV^h6)M~(XFk*!`thN)E$MV?ebuQU zt4;VRxk_G9C9A!e-jH%Jf>W;(_~o0zUNk7g{`kisM^?MKJVD0N%W8smND-2Wm=QXn zxw|JG#A*f(Byc`H?{hX_yQ7;qZ_MR|^0GoDXQo8kb`R-TOxXo}hLL1JD%o<84wp4Y zswf%zjk_SE*QS3&{nA6M7w-H4S>CG@S*f z811r9xR=m@hJH;MIYh67wXEF9G(LVwBHHN*~&Lwnc?k z_C}FU4TzNrtU38Iom-GIg+3!646N=+XtRH=KKoozTiOMb7WFhBQZ7P2-B#sqw79G}z~Gq`+E&a20wqNvtn1KK98z}{;q2Z2V>uplxRH6?JY*5Rn^p>$zgQnIbm43vZX=KV5te#uD8?Vn>`vE zWk(D7EGac)V}m8>tYf9_+Pbz>PxsimRoBxQ`7(euq&?TGb2-@$vY9(ZD(~Z~S_ijD z#Bh&hmA8(*D?z)vL)_jgxm@#~mZJa>1_VK)6FFk0vYGNcyChF&lcHkmT$U_xQw*~^ z%=cUgU&SJEsyf^Ac~nj!#NOfK#?~GXe*79~MUkO9%vr{4kka}{{7Emv_NDc)<9Yq* z%w=zpB7RoTb%*lara5n2(TSZiAKva332gt!ixpyiGo)*GcMVmQmxY_#yAoYS&Ny#Z zG#2>XuMmSC5*I44IQXO4o)k#Yr|W+rKb94afL%DhCNx!n5!6+SFiFItbnMzNK0Cik zc4a)zuIw~2<+!};d)z9&QCr96)$~-Nd8(U!BS6R;ztU@4!pHy90Zre9kN+z6IH;!e z{8PARHHt6t;=<~Z3+F{-ydEKkaSnB!8+Vs^ddy68HO<#($l%X6O0BQ?! z1}!fth?K{5KOYT%ret@h1{J-6+f)UQD%uuT_@K&%wgmDVK(X$OuT$U`nU>|5qCX_Y zZup2*Ne8m7&{o+~{HbVeoFHXoNgmrGm+$g3?nQ5Oa8~(NXQZz8>wJMQHQw_4)kYMh zMQWo^i@R|3Pn~u>z?z@kU!*KEN2m`0$Hxf1|LjuQ0{kfo1C*kgsx|M2?CR%v1b|Nt zrzizx)F+MFGdkW6Ecv^3h^0e!EUC-LSQm-M7cj=;40UEC!)$R{Ag(*Sb@C=M1RjxG zlj>QVXQMW!oqEgQx5O!{B4tx7mci6tFtUnIYWcj#{p7N}(M&+AQajINrQ5Q$Y&OkE zgHVrWfYGDqc*&?M%M)&Xm3Yq`Og#;@UVgDp*=dXK*Z|rH3j!@{5T39+%2%VKg{F&! z#y}&%fHJ{|Ozq&K)J_XknO0=#o1SY&i$}!$h^}7082j*wjSrUHJ1CS~klRla+vYI6)e~W~ zfpzu*A6@T+Z#oHk(X(~m!HIjXJQjjJW_|tTi@XyE69}vvQ^jPwXUS9owUZV>T^*hJ zgIs~gXwjepd&>5yj(sssEh9l}ZwRnm+FIynT5K-z!}+ExHDC24q)V=>R}bRiwRM+} z+Sbz0l1GYN;d^JKT|ZLstFr1#&!L#{qTup_#QtQb6H(~8RA98-kD50&Wx3K?eyEUP zZMEiZBrYc!AM9AgR8b_qCS6n6#L@rWOW~D~LLdWkwjY-M2u6Cs8nv}5Q^NkBMl-w7 z`Mgs#r$bh_wdR{z8ogFC`GHD}5~ms(x(SM$uwI8a{pXy4<~82fIdj?tS>e1k4}ZLnp4`HWxa4v(f4H_c`#j2n0g z6<_ls+q*UB+7Dy}`XL>3Ghju}C0U~CY(b3R}dyckX-KkAMS{or= z>N9L~|KeoiM$8=-s7q)YB4b-X zo{^+==641dDFu2M)9{g~FX|O`nF9FsV%N^HSe8Ji9}_WfaMz0v?se` zrEbvyC3$rq$#Oogc)k1_ksfX~Ji)Fz{^>!fT(R5=b56X*Rx@fY^J~M1Ko3^W+Es8X z6W>ugf}>ZOo7!9kc9*y3m*y^4LvfBkm+=yA?uvLb} z7O=!#t0@)C?s~xE^(hdCV` zwnw!(XYqIX?kECisa+Rcm)dxBH25C9d@VGmr*W?UvtwC6blN3Vypw^aj3hDy)YISB z6`>8qF1-)Q_*hy=nIXP08DVqs-fB&TJhp^L<)ekMwe)I-sP9b|r*xCA5htvp|I$9nx6qjY_hfQ-F>+ z8jOo$^Z>&=Mp z3HdQ5`nctm%wpKn+TKvhD~3?v66D9xYX+<+zvtvJLyJ&1MrxKxiI_B|e%akd6i;L1 zGl&*AR)zT~T(v`mk&$r^*KcLw01d&XICH3o^g+PE@>3^tW+s4%u1oS%9M(7h_O(vq z;(+1@=7Npg#DZZu8lHxWYs&nYS5H}LTGauk_>(9VsxR;?obz@!`_rI!)H>7ConZ?q z)1WPL(h5f28}KamJPdVIDSorrV_s%a=xPAn?2*yGwnB^spQcES``i4Kj5}|6TR$tSihgP9k8RG zkd0qeb{`91$E^BM9U2e+m^$brgZH&po#VpNY#rw*`bP8Ai{}n|)M=MW8}o~^@6tVx z*x#k{G(Wg?s8xY~I2s%Z1cd)C2#EMk9S!__-Qf3KFm+9JTv60d7MNz6@I)~XZAoZZ zi0?>E7?~as8@GZ4!y_hlkjcse%*-0P^)!~7Nw*Kr7901U0;?R1>i+uN9iNzo#d)Sg z@T&|xoWjnkbGw&&r8DC`ACI~-K=eB6U}LB-^U#`#33G|V%o#Hj?W6-9S3jPi!nPCd z^l8G7~)P-z~NsVVvk;|=fQdq4F1@RkO(xCbC-Q_;t>MGUgFP>#c?olUT1+ z*9xrr>UfNz^Wi56wc8T_9uAQpV%-}xWY%P5Xc#N8wSe8MTh=PvY|feS*Da27Ip$$418kIaqF}CfX&OC zXh@IO#~5xzijeWe4qPVgvv1sAS3zEM<>YU{p=imiYqyG5THry(ksiMfi9*3r7Pwby zE4L{-_y(O5YbI_g;1T}oBD}G znZjJYs~TRC99bYTtS6*&TuDxJ4NIg&jDb7h_$$_@nH%~;GsP(&BMHY?IjRrkq)K_m zO?CC8P|YFxRiLtYHtf>njKvf*g><#<7h-dX!Mk2BY*OW3wNz%WIm}XqP->9h1rG$c z38RY3=)-zALZPkn?D5>m+!&G^z6Sv+Ig^j#*cqkkFlv(l9ksMmc4-8Gl4OiIE2(&@ zJEX_5=h%%9CNUnp%p|;z*v$)VKmCU0^yw~*!~Mr-B}y==!czLyA&s>!j5Vez$*Ia` zP3tS~Q~_iqH7=916d*G6W3ZJVjFQ3@l^G97&fqhY^-j|!Moj}U=!rMQhHQlC*{s>U zM=R9TOr>P644va&+m&LA05~a9LKdYxa~I!1H@AZO9ulP^wGzEyYcieQJmd8s-IB2K zs$=}Hvo6|US8inI>SKV5q<*w$zq}boS2*L_IEATt=OnjTE|~bn4}gK!IK& z9kh8C!3Xxp2uFX**}u#+AmbUiZj~>CtYxfaLF(2+5ilYq&>eAvj|2j|P`!(Hr_Irc zz>hh?5YzIeq(7jLKgg>9eNKqZ!6ly3rq~V@=QPQ+EiHC*Plpy8B_w@w+A!<_3+rB& zcOIjR(Ipi2$lN=H`wu6|pTBbz*>oz)Jj5u7HQe#KNUFxMzpEHi7Cj$<0xl47dRYfRy-zma@*zv!VIqR#byO}?^%9MjOaL_E@Z;fEIcnL9O^@MV=^j(M zl_DmZ(aVfH&(3&?Eer~r{6JX4VZMq<)xzd}8c>lzL~xz`sT^~rO}^KZ#0vm}tJr6( za&gx!g$zZLiANmyh}0;-VDp(Vf)qTSDEu3cc7TB2jFwBmIAsvbBY~C^Fj9d?X=&w<+z9j&S3g$6CV}n(8qPK zVpEGT1lIXcou~FyMs&SFV9B@4SbRqDPI#+~6<1HVpGzxt%g)sSPn;a$nz!TH`1zs_ zg7wEGJ;}@LPg>^+)Mxwc?oxQ0>=fOeyrQ|0cRsjZMXfI=&Uwqn@JH=LB5NcL44K_~ zYX-7|TH&4kRERWsNFsdo`pIGjl}Ky-s*fcs)r6|VY{RA|NaQHRh?aD5y0y2fP;WQ!hnEi|FZRY(r3H>E9zLRuyco3?y!EX{rX4w?y@<6~p&FRz`up z-#3C@Y+$MER(F;s8naYs4px;Q)J!tN(t2eN3v{3<*pLQO>Dd7wVWJuNJhi86qzlQDonwZ8B z-V8LjIhJU*rRobZOP;6*S-!Vy?Q0Y$u^bi$!wbR|TrP{835s@FW!1zVi4zkFXl+y5 z!Ur$w>e0kRPFnIiUns{R2x-IAPw6b#h%m=1wX+5~vI}!Z(JIC_lOAH9QmhELh1Tvu zC@N$q2p(8GTHz1+F1YMK@Lh>ZsdQ|ssy zhIz~WPbou`=29!}$J0@$VO9pk^2E&?;193u3cPL3Qtf7tV|t|1wM3$4)Qw=EiOu&$ zvCSDTBdf$fw3~`g*~F5|W|6#o=SWpW91=wZ1Fz0bTO92j_TFZFC;~!8@i`pcgJo)j z2h6jd9+dH~d5SDyc11S#!e=waz1@50+HDtxuiTr*ZIr5#UGh-!C2C=kaLx;KKt-Fi z9tP^6lg;%xE*H^mw4TE@@{WPjjN1Hb&Kx+q#fp2DnAmFT;Z0 zx^c0|!JqDa8vM`=o)_#js%{rp9k+E#Dm%h+2_4*sM>@0V^T;=7IB!~_bRnpjYfdcB z&!`b)foRUxo4^kbUe4LBDIBFSdOa%0cS8|^Ky)lxAD5m*&> z_6-Rxx%mzwGZvb`HCo@JlG{zOs>AlISb$F2&9yfL;o#da#X%Y9u+3_f)z$A7pttLI zvE%v+;`X^bJAaoX&L-iAg!#UfM8-XNNm6&qrMw@YE`bvK4C8dh7?OM>%M>B4Azw_yVMc3hr z-(xqi9`c1HgQ*%;@X|8Ut;aTrq;FcKAV!`rMx`b(<|gYAp6a$%@kF)j>n6?fJS%uu zaIVopY;CXO9D9Z+Z{C=UT+U1WAr2!KiyUe2`0x?44E5CF$qO~!vfbt&GALVRM3qG4 z_qs*PW!p*KiIMtZ1vtSTPQ~rP;;gBa_ADdiDG{4UMn&Di@KYhN=Uu^lUNfH^v_b>a zM4pR*j(8Uhy+L?Wa^S4FKIh?+K!K;8-DlzeXsaSbArTO@J~Moh10TB{~u#f=j>?rS7ab!391 z5-+iJY6k)z;fpB3^Q|NzrG%)t0!^J9z98wOc6d$4xMYC~H`JH$^oxjlOE%uy7~d*x zz`==z98hs#pnk1u;h$#`|G2Y~F0NBeMd3>5Ch~{xSs)`LM0rKXJ-?xu-M5IkrYXth z0Y16GA}fCUEd+q3su?KaHxQ$9JFh?TFrN;jpP#?GL;`uIk=_PT*0>N3_P$=Tg?9F& zd~+!xs1e@LkAV&cN9*3Mo#hMfUTg6hOmyr!ON8*t3bo*a3*9`Gg4;9tznx1|4ctJ; zooNM4)V&Fzpf_t9i<%@6`jnMt+tMHs45d#^84t?3bZn90t@L<9&N=aXl@#~DeIeM( zIA8TH$ct0tqD%M}vg6C}oqy-da1+fsAzbPJ0m= zt6mBs}s_|_^zP>jaZ zWu>mKRcE_DA4+`eQrFTL?fY7AI-=0aiJpb}nnQKPTnh=bedT&7!r+|8TqDPW5owy` z#s_^BBz)}9$@RtuU3ebu#HVbsDc$0B#gEgwXk2hQg6N0}gh@j+jq3g^^m2Q!h>BK+)w{?Zc^=>Qt(uRiDk z7Ya2uF>B6#ZOj_BZ|@7c{!Mk!n+X31TGOmA0}tAv!f1Hl-iSAo`A&wJx%8lvCECpM zK=-3u7jq*S3o<^#ZXWYSTJq5hXJYyTpXO_j@{o9#u%#57GK{G1a4?(Ygdj+yI^J1> zq`e?}O$j4mtV7wG*dIDx{5wHFldJaZ{PM=+CQJZLF0G7vz>>S}mRk7`AgGDtp3{n6 z=uPAVr_v=wc{mTuBn38{Z9+5i+>4-An<5+Oe&>$H8?RX6oN{@rlz7D(uh@p}C~bAw z*9s+Yy$_E0Sn9oT*bFNN##B+eJTlSc2rwP#4556qrOGB(hB4)~L+_O#z<7loMrZU=eB4zpyUO18T0v zjpmMYbCCb8xgE%O_A2&3%?+s@l|U(HCVrR#G`d~#9fNOLu=3(&mrrWcbjx-#g8)#r z+9#?(qggV)NB-zeGTK(lLL_ir`C) zegp!1tT{U(onN`bNgYdy-i=S}MMfGUO?o#1oD;&jV5d-?io;8UVVQ6_bwJ=JzC5#I zFv)j0rsi}L=#ecJ(&9a1&BT|SQyMvM3?5}rym2G5r!&OH@D*{85E~-X!3R-C!NYv+ zCAf~`knxG=qnw0ygQ-9t^3-lhr)r90Qag`{L`r@Y?eOQL2Sjpj8t;k=?20Ai?`^YGOKey746E)GJZx@N#~@#w9lVZrk%$ z1Unu_5&Ks@-TjXAyoacds{GNrrs^}X;SLZ#y@>Xi$I8oa4LG!E3NmF6IMkaRz^U^; zN6LpThR65XvXO-+ZwZCxV@_aqds66}0ldCK``yTP z1Cc|B>m8UCWw)5gry~a}%I<-Q{DW0P2Rl{ReECa9*iX?4bm`{&jZAoEwl@27AU`1X z$H-|<+v#q>s$4XJ9Usbep_3%AmCS3@Z6vAf3xN)@mKaCAR23+}D3CiS4O_~Eh&YTi zbTO)V1-DsCLq>p!TBK&^KhJISiw{m{iGX<23d0){z3($vEo{pdv_Q?#?pbN*8- z%YH_SI-Y!=CFHF6xA8SRRq|zQx>fgSwQ2iL;D@Sdl?)@6YgtWJF^sG!lixPAxo}l& zvCNf8V{TQ;)J{!^Rk8O^M{kG9^nXZzP8(rSJ3Y=mw$!7&Yf%-OGdosq-uv3>L8KEM zs{}gRQ(1U5oQU3qR>L0YWYSs4puz^*Esf_!GujtMb4_$Z>I8G!q~>oF2J-l0jmVIM z{dyZ7A>37EVVj|3Z`AD48S2T&MbWl#>tf=ztZ>&I>LrJ^BHfkj#Bc1d3*+08%G5CcC8g$bFf&ef8{-&}aFLvpvn67dv9zG$aI+sGPgZEziPv)lIa(%h zf2N*ll1^6>zW9>Q_4(aH*RuE*%-wB`qqQ^y zQ>?*4G7E267;Q?E!;EYhM^-uIZOGyXc+1r!)CBYob3Wwb#vK4qG?+N&SUjT#3|&<6CB9%=S;Z>D*P-8f1>8RY0M$AiXt=9u|fg zzZzO;%gZ(!>r&K#zT+;Y8WT6Dqz_F)8%70qQ*TY0Tg8BXR~@HchPwBF9*ae5LXz2& z@>p4>Bq<42H>R-1*piq?x6aE_$ON-C43?d2R3#8QloY0Vm6l66S*;Cr08<<;rmQOeY}qBdf11a@LCcQ%|s+r?6}<% zYP?rnTE0f&AfwwHV*J@+-L?nXd>^>+UUd^VUET8;IW)Zrm{op~5B5aqBgO2SL-nh0 z>>z~BGouur$pa+3>yB4X-f|FSt}L7(Wz0QadByQ;$%jj?O)yF84(CGoVW)}4Qkuk(qKsXfz4&DJuAjr1F+Yw;JXLln0Xn71Vk+_Q!Yfvm z2J0-M$DTO8Fcq1@HY*D~mPm5uzEe6v2J6FI91&%nGjtButeDlZ7Jo7YtI9#G zV7c190q};*0*pb>^kUOKJ8rZPp#hj#RPr`G2JJY)xp zMAOV6$Kj~qrRcfbO7ph15OVmVgz^Q9rYdzB7FWKruU5d6&(+%NmLRDbb6lwPCY3)ZU*{w7 zQ!yq^sg4$xtH2Q+Z8SySygUT5h&T-tGaGDGvTk!Q&FTpfN=(7R9^O-vv*0m$5>d{N zVTTQy>-Lu;D4|cLys#{m(`Fyw!SjElhmJ}mY|05MNsSF%;EWigtnXmWnq$t)`vk2) zF>4Ip9lukKTtICY=M$U8OLV_PQt`1=ag379f%G##)_yh*Guk$Z9jJ))A3`j05JuJ&U~67 z%u>985skg2=+8ux>yD*W5!NLIUH#?^-TU4f4dI0k7FjbJqmwmGbRsL&n}i}x^EPp! zru$;W2@w1N|bOc@@6g+!yEyr3qmIdCuEOY^x^~vj^0a^iQ-3zd!Rr5?})H#<~ zCjDNLkv?!I-0Ew}NoUa;P)eXSY~G|fAahEBY>s++sHf^QZ^if|q#G3!ovB2l)L%?Q zqx^{v9leICUU`u~xvB+kcZ0Ozc2_QosocgeMh2pIkOC)&wXD-5g{MlWdcCBAekM+OV$ZQIZ%B5c%HKV}4;*f>097Mxh~oi0hFBf&+iVef~Eq zsi=l;CZY8BY@oaKP`%Kd-&~ltC+Ue9%FYBwVMceIz7P&+aQ6s+?xBo<2TiO5HybWD zFBFSGhsq!NP}|~>Kj5d z21y0W)I}X@OC$S7U^6h5eb!d`+;I$OrIY`mX*+k^=53+}%^zrR;bN=ALOz88VC9>un5_*Rr>`uIWBXo{E6CHR>429DQ zRbMa>S2jw44%0hCu%@7rkerw9nERdRI;DgSiNnM4HfDs%I9y&=8XQNq@cZza5Qs2Su{0^Rr?Ti2yOjA2+kNzwA!2=d^texyj`J0ot@6r@ zeOU;Y@1zna*PU>B@NY-D5<{%VHJ~6M^5FmB&e!j4@&4)RsH7`DCVHds1gV`?vl+cKSqc^8&H6oPE2^(Kn%d@YXRYNvkeeB>y% ztK87b1)nq@@B>^~4CmP+hHx^Z)5Dfl;XD0^YM4!o@~LcW?-`6Vo*-u*pK-{%kZQ9D zwqcxgBa0uT&dkt^Ti|X)pzxwq5}!} zX1a1{U3T@A$6`lZ}3PJw6Q=SUI(dn~(C;^OY9gG1E-#F#bh6y6J5#AL&O?y0$ zR~)hjPwBJ)^8thwJ}?jrtDvF|qsHgdTW_ABd9vk!L}z}%q5~Irw^_{bVd0|vt7COc zR-7(K;kTUV%z&MZmvx(Yv?}AO)8&^`p;z3rn4vnTR|+X2(A`_8PZmv=kV~Io+2W)P z#4#z&y*R=wd2HCT0h`6zd2f8@R--^ERq$5<0|CMMwM)gUe>wr%pfKL$l^e0bsU<}< z%bbOUkQf;$rE&u!$}FN4^(B7XcVuFO%pQt2C1v_mmTV=#=2WD^QKjK2&nMRs;;Vo)sm~MElP6$3eyTWsC!eZS_`GW};(@6AmB! zXCf>Z2PR$H1u;7^t?s0}3UN2~8*`2PE!?g;CjL#h-NrBBc9s4Nw>yXQTew|}n{c}y zpG6mG$GrX=Zg(G!atHi#xLxIfJ%&HS?dE@+lr4MUKbS=GTew}hf`HMWt~BYd!tGwT z$z&$%b3w5zrER?T;WPHqaHT zU=pfTe#{f+H7n354tn2H#<1^m&{9zv#V0E_K}+4FX9nXQ?J{QOH&F~SxKr4K-AOah zl6=TKRK0C)eacJ2?56GTtRXGUc9iPNqFZu-xGrK?sqB~}J_XS?E2>TF>uJTHbU~g%rgw}L|mrsv?GSv+Ac*~%pn>#VIaL1XY$+BVC z!{+;nI7EdtGjni5=*T+hSDwHm)Ax9tL2h|HUd^`*l+YA%DPFgWSjT;BgwISm7;6f) zHAM36*b|i99;fcTGXJe*H*S;rD8fMf^@RB8nD|NnM_Vfwa~spI?!EZ)?yApG+7swX z0*Do;Flwmu>8Dkjcm>>u-m4I5`N7zZ?iyn??66c-AzYzeLpH~C-0^wD-{FW;CeZ^}1q#hTWZ>Fg0W&>DFx z%A^`$F?kkH?{eNJHtQ#J${jrMJJ`3YMY*EDAJu2lqlrFY#MIp52 z3Rs-;7qa8l32k9O{q!ioVB|inJ!8oNZjk}Y_zVvfQofQB*J6GzM_n1S23M<)flCL& zIK!;<{^4tVjmwY8)Lddkgw+RO)9g7Ib|~f{@z;eP)c~mtZxPVU)pz>p%?{~l#+E>``N68A2Io+cBqe${$ZDdN$)hdN7!qb+=nnHtikfp)hM=z zD@m;^=5QaiSCb`+jO{S0RE${X@s%V?+0rSw)QhT&cypQw)whV|+TCh`21Iik9AMr( zUM|dOcXDujs<5{8K%xQl@_bTPjiORO*Rb@Fhra=1uc^0}wmnL!-G;{K@o*ij`(8yk<;|+`45jc*42G=b>_?7# z5Q%U9XE!T#wPs6@V-z!(kjNEJQ;3>^;1s- zA75LIGbi{h1cN68v(TK}SgbT2`O}^0xbWBn-D2~|iQ;@e%u2M$i5XL>JtJ`qd^jTn z!N&f@xgLQHLhJ4^z6Okxc%QVQLzdE@M@w(6?Yhf76If_Qj+5?KV>9K(SUNls+jeUZ zx(UHRzV9BxCNVUkQ9_I7JDw03Rp=#p;)tw~^U4o2RWx{&`ew7k?%wCk4*!1PmYT0O zJJ!G2>{zA>=NvHj!|JX?AImjA@^E`}d0x2i?P!S!wznJrd~yYT;D8@<8)Jam=TQz` zcq>Ai06fHXp&EJ z5JD4936QV;-8d)9imC|GO3I1RTN}6lZ0LXd$K-$gV_-w1`@JF38ky7m(Gq{02HXFf z#^=^d`)70hQlx{xA~h_C$R7qyOdi35fbjn3H0^Ay>D;WXUaD!^&ak0+4?SOe+>C~t zp4Sc~jKXH+C6PGRsf6l9Z5EP%rJU0eN>6dI;7Xx|>};Q~YXEAPx`F$&-_>ToqVy?d z&87y7x%Z2CEp2+nvYw~Cabx~Anng~*{AKtDGfvTTWqzyg&D`#Lao~=$Ta%ocRMfXV zz~3WSG89Fo3nz$?E6W=^XE!_PeX{t`z-yP0SVj8Ou> zL453RS+$h#5kZ zk-oH2#Z^VcBm5!s;ciDv1m+$zn))TQYjS@s%ny9e{3I0Zd&9>&sH~$@pHP|CAa9nF zq1z`502EE&VX5G=M6y_gr&}jsVDx>V7aUuq)Cf%pAF;Go^e+035}!Kjd*&YEMWU=) zW-jKTulg^%x;)sJ3lhg&emK{4+z{UXPSDh?x(I8rS0=U4svS__k}D}>9*~wtTU4hY zWy^Hcq)@yaM0#F*VN@f6HpOUMWAwy|X0*o!>zPVRkiJL*mn8Z6D8iB<*@AhgQ!`Op z`TX{y$%G+_7#X|1oGtKolDxUkeS;e1H~Gp}I$VWlPe6FzMvHOE3g4-9VJLupdjfUX zV(4n6%TgWUpJ_!3Pl!awkU&;9$lrDF!uefX5o`vt9H;m3E>HPLGeL7*@FN_oh!4kN zu(hUbB*&K}H4i+r`+bZo<#xv*c#a-C#?nvGREoGSgnTjYSjN+XSCnj0!-P8>gVgto zBtuMcU3AYqySa~no=&}e08*!s)fij8$iRvt>w_~+)+fvP<9kQfi2Cza{_}T~8O#zM zOLD53&blzY7r?w z>YWI8K`EPd901?_z&5L11F;?VR@jB*PggQ__j2jGYF4tCeQa{g9XsH%k&f9?wgvPn%!T3Ki$aGX;Si_UzUaIy6;if zd7X7te{SAA_N<+|}m;m zS=YSHPfTqtYE$evh>;qsHKcc6l!|!Lb*@h>pXzykeLKpH`SF@>wr~bMxdT5UKV>TH z9Bl0X4o>C(M}$6U*fxf{Aj4jAjKj`;Fcb2b(v)ZoBXAu~@Yzf-u{pytGjrNaCL&_? zkQi>(42@`-td7R@+)y*V6(0{rT-ED&pXwv6=cvWWMBm9C)PxLXmF>Svwu$sUk)!UZ z)5UnXGAHE+m*yQ3k4%;6ux8kJ*^j_65hxfc`1kE9%%8vA1=$q@`8u0lUV{d~1HPs! z_%=}k6a>T)IG{rPDv2E5`(M8%D<~%^CaSDLCoA?f_@+Pm8jSW0xB~R66bx)^Y@G~l zuC#Te|GOk0zDZ&g^z-=c@7_TC<_##Y+atoidl&ticbWe3?w3C7$7zs#lZMF;)3`Mv z{W{4vx00m&CdtC3RtY%N-})c>5^W>)!jn|9<_p=RWtI=Q-c!obPi!pZA>ieV_O9vUJ+W zvMkqgqv#CgGWxbyCC7YYDn7GZ4Kuqqy;gHD2koGzn#|%?+_pG&$Pui}Lj@GAa2^+o zt?ES`r*Pf!N8SRQxX~rnV;h#M;sCJUo#By@iBEg_3V(>W0-g0-UUYxjQQL~uYLxoEsAoWBCg1LZQSWSREm0}$>}fl0Bor+^@_ zpv2$#3t(Td)&WZY8iU+#-4Xc~yPs_}G&BSmE=iaj;*B$BZi!Ws1gb={#$EP?-Sp$p z)Ac6O4mh^&3(YIA+M&fbF7})-@h+%K!P_n3y9)LCoUW^aPLjgx;j8nIy@IdU!F%Q7 zi&Ed#B5@t5+jOP!Cw*@mjz{MdE1myXeMbTwvNrBHhF3k24*hm#v`({PR|6wyHi=?i zzpyU;I?+_egQf1_DqKAC!ZvLbusn}pF%J116H`BCm`@B;$c}Fz^X_>nzfyp1J(z1A za14Ng!2p{jfIXf@_uvQxCHw5(v_5xK7-5qNP221+kz{?SXnAz-c$X|INeL)zi=BMu z^TvAnlrKWR9tPood4Y)ogCMIMvWC8V&UBvJqmL0$$MSEK-*In`7mM~rft&E`epg~1 zHH})5o5ono(>qe@Q!X0XxA`wroEf~%iNe@^Fr+tsHRYUE8!PT*E@=YPUm;aC?g-Ky zQuL$^*^nhWSh`a%`@!U%X#;-*RX5oRJxXx#(NY0e%fg2#l?$?`qu-Mj9!C{sSTb3? z%sx6xd0J8-QA9xCwD1aATQVZkVHD!he@1~a0KpZ6| zwCpDnaIJ$fP=t{zM?>x{Y{x(C5Km;ixXXtgW?Kobq{+c$DsQN8`YooQoE^TL`emJ0 z{gEeyrJ2dQvfhPnHxP1LjSdK7xI#b8P+x}Tx@M$mm|KhwEW1Bx``NC?FZ0*rsfa=J zo9O8~l%2mW&?zCZR^kmvbIXU{>LT}WZ=LHFijdrRz|DgXH4a9OlJ6ZzY`=o(#a&iK z5CDbXn3>{U)7=i-S=d2075JRAaX)Xdv>?TuT%cB9II;8D*X2qR#F;+WJG-*K3#`3A zFsBWCK$mpe3NFr#v2c~OaU*8wonB*Yuk%rn)^$if&ke~!W!aCMEJNR!g; zJNgt;ey4KGVr!M=cc*^Q4dz@$E#sH6$c`=MpJAfRsAyscEtG?eGW`3WJ!Or10D-*? z7FWkE=C!wCu{s9qtmwXI?y6- zO2cC?v6TiT3kp>pD|LfmL~VRcY+|xwl&=HLL*BA?_rsw6^XYe`IKz*L4MYz&BP%(O zH~$37@?~~zUCnmR-e;MORf@}UBz1U6pP241eL7N_q9v++Yb>T)H<>1{(nRP+U#$o- zVJ@f29&nf5-eRXFa!*S-9se*fORG}!v|U@%%yr5AXoujH;C*XV?Oke&m+u?y;p7(G zM1=tu^B@USj$)s-tFhDuy!pG45^^F{>i#KBBtzj+(_BJy&-Tr)ZEDLUtLIavOz;+$ z_{h>fF7}s($*b)q^`)mwd22<-yb?pv;`T6>E?w_Li+Jl$y~^%}Z=Tv#%RKz-xzWVw zbwAUXoIO)`7K9#6`wU~;6*p%eb~UdWIuF(7)0+`xy@T|KFd#)#G(aMQm`Ul!?@KTY zalL=|Qui3sq7FUC`T4e`bkF0$gTK&pX9YUAw$OI}#IWhtTfZ$QKKKkJ5 zxi;seY(|BRbahFosb75-&V28n%#CFYH_DtTk9wSuUNjR4IzAFPEtYgZ7{=FS@gkTmSA0PT}%0Q%La1&aUF6()?eA1QNxzb@Alrr!^f=W1pI#GMbEnvqT z>wxECeU!rGDx4`v`HtO1fU2a8!3UJT_*ZXEjPJ@OX zG&UWz-Q=X^71S%=Ng%iQ2giv4C5}jOcZNzon4gM1EOTi+E7F*~r$b5KKrHseKl;qF z21NJW|2`fr2Rc{Jtdh~GQ_Z3IFhl0MY;VPNs{p~&)^rYfPX%$FvI5eF^?)q>TbMUTx_H|AVtuC36%LvSG0Q^|5ZzzTbNh!(E7wO@_T+o45g;$y zm3uwRZQxMVGLvSXu0Z>cJ#ihK_ig8y=^Vy-@nXF0Z10P?lKHlzs-(@}2=RW8Z)J2j)3}C93H0N=J>IX+^iGJ5 zTnJld)eD`oHJ#Y~84~p~SH}7lc;6Xy`5@htU7_knVf2^Xmwj~hnc{S-(#{DP`CmHU zgZit<);JyqO1`c>5n8bqF-l``i`t)1OOtpLeU}obNkz6yjydPDs}ho58=cn54W?)G z#11Z0y8|c6cKQXisrZ#>XuDAcLeWUo=edU5ZRirzV+7c>Z~MfXWD@X0pi7+m{Fp0O zd_HZ6iOY@a`OgyJ&$^AM@fT*(58KXS`;JC_#H71@eR?cU2I9T z;lTPwAO19a33ukoar}6%*dcr5Q1jFChJxA>1qf77MP~A`y(TIDpS~SaV1~kLgi+eY zi)8Q6+fqAu?zYkqzi-j++q{x^(Ra=?>GHLdz0X$DmVF5l#g~K4RfbR6{gj)%;m8*8 zZ6m&cUgM)EBujJ;0q2+6PyA6o&y8F5iO}8?-g=R_6?BsdHn99a{?A#>JZh9N%Psjy z#ie8Kd5Xw%lo=We4yg)iFJd{FnaZXV!Z=V>gkOF3V7rmc)!1LA*`H=(bJ>UUft{1k zaq8=mPL`j~g?BR?4V^`X-9-cym1n-jXp+Vq(Zqfd2oJN{JdGU@#BUM%fdoimtPiz| z-J;sAo~MVm=3Vl`T;Mv4?;qTim;*|XF>#ZhryxH1k>1BH9|6@>sX3*INVsr6vpMAt z_@`$!wju$Wdx_gpJog3S5}t6}h2_kA;$ig(!H%hZZmL9apnts!kP;};p&!LCQ4Yl; z$QKHOD?>a641Wjcy+Ng3rqNbAvwxfAHo7!%i5`{&5R5E@VG0*;mkefWY)9#ZOIr}z8)^}(b5fy z5{Vj#z}@AV7ziFvygi0^3+3@M|AOpIaSL%`V~`dTB!mRa1`Va^)4A{ZV;NR3FRiF* z4qtXTv@g`Q6!9T&Ia7Bfmo5&r1qp5hswd~y6NaWA<@ry!v72Xv`ET6lPn8)$&JXWp zXft}oSypE0Hpt%M_YNpt)76mi`JAwukPw^+twf&MQEQjmjI`IRLtnZ08@`f5|A;smnmTcN*`v+3?PT_o%Op~GeX22` z+~YUURMKBn;_I$zg=$i{W5+gxXVEv213>;<>rpt$M4$35y!7Q4iktYUgw%eRAfVl3 zo38wAktR*j3-MH@AzkXrg}2Fr-Qo4KaG7?Bu}3o9l`deA-79lwMUMI-{4u1%sD|#% zNQ)$lbk<63Vu`!6k(6gnTn`&3V7}YyV|1=fNw$humERMa4B)Pn_!%QPC%QB=QFSNI z@iVVDR=9`R+j6-zb<<~i?P>!5ly$P!xHNZ!@1|0Yma4JldZo2zl*mqAgVpx~Bc_Yn zC!Lixo2isx)&>QyVU64TH`|s;fpE@xhoAr%^l)~*CP&`XH0ke z0^p}&+Z=*9$J zsoGb?np)4v=Zoyu{n^f?9VtMxmEAMFCkgB%)d7}U^FLKYX&Hrys~ndSK(!pzs<-fU z<$+6^MX@RHrR<1o)TUQ3yLsqaj2Gw*XPDD@Z&t2?^kHW)AnbtfWQ(G@fpOIG_&9}~ z-a4+OS^R^2U^uFbw981A9o=ZA^6qMl>pRbJ$R=Cdq`mB;v;FdiVfmdeFBgXw|1jG} zsHBDCYpmp-v<2C|_750PofJm$nI;MugHH<6XST|8$C}}%uY}w@*Hc~=dT7OOsC6sG zwGWD=eCO90%juWs=C}!gcgP7j9UF|4@>6X0(M|~eDiaMHGIbkYFl8`%Al*^EjVrUQ z{Dg?8`1o*qP#%|KOb#GRjEqGHE!wpP`QR0~+j5b1ssqRhz2>2l^T}Af?aQDhI=0}K z2yBoxL%jU*{>;$0+WY%z%q1fNTVJA&%hh{L+iYcQE`$arxqr7HifEm%Q*+B9bDM+j z^4(?y%ap{AaL6!Iv)fHS=B5{i9h6)x1XUKm8!C~SW9hAHE(2Z787s-#Vhh)3gf=V7 zFgoX)w2L-%J~HIPjXBut2v+MPeDm~&0r8{rpE8ats^YLa9vp#=hd<={FVmK2zVYK) zob$-Sbl|R+fgDS?vBc}hac4Aph)V|r4WcTNHSD`A&*hNeHKV+WnlasW%)#+J1C$ra z$Gey^IOZz-kA3ljQ(bX|VGsq6;R#SDRd&`K@xv%uQ|J<+c@(_^rn%$~G6?_8m6xN& zlG=-Z0ms8CM$H%J7NV~~=<95&Q{W)_T6P_Kd>K27H*5{BGsRwn`>b3aUDYyOK*-Nr zdoS0nA8#3``&Fzh<82?&MpOmyf&F}dvfAO45AG9_@R8CG{Y~|zsrP+H{EkTp^}i-g z#QC?Ih?rmnk$-F6V5pB56edNm09RjMDWm@>tpmJ#rF>ms0s6sUHyf}&%VE}^h!}}>wLJ;le!hVo1TpD<)0C-{ z{yXh2mx@S|K*NlZel$xVA=-LNLd5g`w7 Date: Tue, 9 Jan 2018 18:56:19 +0100 Subject: [PATCH 017/863] [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 847a2534ce7a63e2b0943d7f5ddbd4a612a69776 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Wed, 17 Jan 2018 14:52:07 -0800 Subject: [PATCH 018/863] [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 019/863] [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 0ca85720d44f71315fdca2723b758ff8711771fc Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 22 Jan 2018 11:22:47 +0100 Subject: [PATCH 020/863] [JENKINS-48766] - Make the RemotingVersionInfo API publicly available --- .../jenkins/MasterToSlaveFileCallable.java | 7 +++ .../security/MasterToSlaveCallable.java | 4 ++ .../jenkins/slaves/RemotingVersionInfo.java | 51 +++++++++++-------- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/jenkins/MasterToSlaveFileCallable.java b/core/src/main/java/jenkins/MasterToSlaveFileCallable.java index 9b5c88dcb6..ce7d8df28e 100644 --- a/core/src/main/java/jenkins/MasterToSlaveFileCallable.java +++ b/core/src/main/java/jenkins/MasterToSlaveFileCallable.java @@ -1,12 +1,19 @@ package jenkins; import hudson.FilePath.FileCallable; +import hudson.remoting.VirtualChannel; import jenkins.security.Roles; +import jenkins.slaves.RemotingVersionInfo; import org.jenkinsci.remoting.RoleChecker; +import java.io.File; + /** * {@link FileCallable}s that are meant to be only used on the master. * + * Note that the logic within {@link #invoke(File, VirtualChannel)} should use API of a minimum supported Remoting version. + * See {@link RemotingVersionInfo#getMinimumSupportedVersion()}. + * * @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 */ diff --git a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java index 39be186fb0..d8ef8b09ee 100644 --- a/core/src/main/java/jenkins/security/MasterToSlaveCallable.java +++ b/core/src/main/java/jenkins/security/MasterToSlaveCallable.java @@ -3,6 +3,7 @@ package jenkins.security; import hudson.remoting.Callable; import hudson.remoting.Channel; import hudson.remoting.ChannelClosedException; +import jenkins.slaves.RemotingVersionInfo; import org.jenkinsci.remoting.RoleChecker; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; @@ -11,6 +12,9 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Convenient {@link Callable} meant to be run on agent. * + * Note that the logic within {@link #call()} should use API of a minimum supported Remoting version. + * See {@link RemotingVersionInfo#getMinimumSupportedVersion()}. + * * @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 diff --git a/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java b/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java index e2c2211591..2c89ec136e 100644 --- a/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java +++ b/core/src/main/java/jenkins/slaves/RemotingVersionInfo.java @@ -24,10 +24,7 @@ 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; @@ -39,17 +36,17 @@ import java.util.logging.Logger; /** * Provides information about Remoting versions used within 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 + @Nonnull private static VersionNumber EMBEDDED_VERSION; - @CheckForNull + @Nonnull private static VersionNumber MINIMUM_SUPPORTED_VERSION; private RemotingVersionInfo() {} @@ -64,39 +61,53 @@ public class RemotingVersionInfo { LOGGER.log(Level.WARNING, "Failed to load Remoting Info from " + RESOURCE_NAME, e); } - EMBEDDED_VERSION = tryExtractVersion(props, "remoting.embedded.version"); - MINIMUM_SUPPORTED_VERSION = tryExtractVersion(props, "remoting.minimum.supported.version"); + EMBEDDED_VERSION = extractVersion(props, "remoting.embedded.version"); + MINIMUM_SUPPORTED_VERSION = extractVersion(props, "remoting.minimum.supported.version"); } - @CheckForNull - private static VersionNumber tryExtractVersion(@Nonnull Properties props, @Nonnull String propertyName) { + @Nonnull + private static VersionNumber extractVersion(@Nonnull Properties props, @Nonnull String propertyName) + throws ExceptionInInitializerError { 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; + throw new ExceptionInInitializerError(String.format( + "Property %s is not defined in %s", propertyName, RESOURCE_NAME)); } 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; + throw new ExceptionInInitializerError(String.format( + "Property %s in %s has unresolved variable(s). Raw value: %s", + propertyName, RESOURCE_NAME, prop)); } 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; + throw new ExceptionInInitializerError(new IOException( + String.format("Failed to parse version for for property %s in %s. Raw Value: %s", + propertyName, RESOURCE_NAME, prop), ex)); } } - @CheckForNull + /** + * Returns a version which is embedded into the Jenkins core. + * Note that this version may differ from one which is being really used in Jenkins. + * @return Remoting version + */ + @Nonnull public static VersionNumber getEmbeddedVersion() { return EMBEDDED_VERSION; } - @CheckForNull + /** + * Gets Remoting version which is supported by the core. + * Jenkins core and plugins make invoke operations on agents (e.g. {@link jenkins.security.MasterToSlaveCallable}) + * and use Remoting-internal API within them. + * In such case this API should be present on the remote side. + * This method defines a minimum expected version, so that all calls should use a compatible API. + * @return Minimal Remoting version for API calls. + */ + @Nonnull public static VersionNumber getMinimumSupportedVersion() { return MINIMUM_SUPPORTED_VERSION; } -- GitLab From 1b2f0e7e64dc32bc6aac8d31b35013f52a668613 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 22 Jan 2018 11:23:39 +0100 Subject: [PATCH 021/863] [JENKINS-48766] - Expose Remoting Minimum version in TcpSlaveAgentListener --- core/src/main/java/hudson/TcpSlaveAgentListener.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/hudson/TcpSlaveAgentListener.java b/core/src/main/java/hudson/TcpSlaveAgentListener.java index 03455a7753..f0a9c03700 100644 --- a/core/src/main/java/hudson/TcpSlaveAgentListener.java +++ b/core/src/main/java/hudson/TcpSlaveAgentListener.java @@ -34,6 +34,7 @@ import javax.annotation.Nullable; import hudson.model.AperiodicWork; import jenkins.model.Jenkins; import jenkins.model.identity.InstanceIdentityProvider; +import jenkins.slaves.RemotingVersionInfo; import jenkins.util.SystemProperties; import hudson.slaves.OfflineCause; import java.io.DataOutputStream; @@ -300,6 +301,7 @@ public final class TcpSlaveAgentListener extends Thread { o.write("Jenkins-Session: " + Jenkins.SESSION_HASH + "\r\n"); o.write("Client: " + s.getInetAddress().getHostAddress() + "\r\n"); o.write("Server: " + s.getLocalAddress().getHostAddress() + "\r\n"); + o.write("Remoting-Minimum-Version: " + RemotingVersionInfo.getMinimumSupportedVersion() + "\r\n"); o.flush(); s.shutdownOutput(); } else { -- GitLab From 95f1f5ad19385408da15eb1b4dcaedb705dee638 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 22 Jan 2018 11:24:32 +0100 Subject: [PATCH 022/863] [JENKINS-48766] - Print warnings in agent connection logs when the Remoting version is not supported --- core/src/main/java/hudson/slaves/SlaveComputer.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java index 9b87bf5dec..1f3f2beb13 100644 --- a/core/src/main/java/hudson/slaves/SlaveComputer.java +++ b/core/src/main/java/hudson/slaves/SlaveComputer.java @@ -47,6 +47,7 @@ import hudson.util.Futures; import hudson.util.NullStream; import hudson.util.RingBufferLogHandler; import hudson.util.StreamTaskListener; +import hudson.util.VersionNumber; import hudson.util.io.RewindableFileOutputStream; import hudson.util.io.RewindableRotatingFileOutputStream; import jenkins.model.Jenkins; @@ -54,6 +55,7 @@ import jenkins.security.ChannelConfigurator; import jenkins.security.MasterToSlaveCallable; import jenkins.slaves.EncryptedSlaveAgentJnlpFile; import jenkins.slaves.JnlpSlaveAgentProtocol; +import jenkins.slaves.RemotingVersionInfo; import jenkins.slaves.systemInfo.SlaveSystemInfo; import jenkins.util.SystemProperties; import org.acegisecurity.context.SecurityContext; @@ -545,6 +547,12 @@ public class SlaveComputer extends Computer { String slaveVersion = channel.call(new SlaveVersion()); log.println("Remoting version: " + slaveVersion); + VersionNumber agentVersion = new VersionNumber(slaveVersion); + if (agentVersion.isOlderThan(RemotingVersionInfo.getMinimumSupportedVersion())) { + log.println(String.format("WARNING: Remoting version is older than a minimum required one (%s). " + + "Connection will not be rejected, but the compatibility is NOT guaranteed", + RemotingVersionInfo.getMinimumSupportedVersion())); + } boolean _isUnix = channel.call(new DetectOS()); log.println(_isUnix? hudson.model.Messages.Slave_UnixSlave():hudson.model.Messages.Slave_WindowsSlave()); -- GitLab From 07d8f4e3e6715a3ea5b060709a92a6b6950aca30 Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Mon, 22 Jan 2018 12:04:54 +0100 Subject: [PATCH 023/863] [JENKINS-48766] - Add info about remoting to the WAR manifest file --- .../slaves/RemotingVersionInfoTest.java | 70 +++++++++++++++++++ war/pom.xml | 2 + 2 files changed, 72 insertions(+) create mode 100644 test/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java diff --git a/test/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java b/test/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java new file mode 100644 index 0000000000..c396eb3a22 --- /dev/null +++ b/test/src/test/java/jenkins/slaves/RemotingVersionInfoTest.java @@ -0,0 +1,70 @@ +/* + * 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.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.For; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +@For(RemotingVersionInfo.class) +public class RemotingVersionInfoTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + @Issue("JENKINS-48766") + public void warShouldIncludeRemotingManifestEntries() throws Exception { + ZipFile jenkinsWar = new ZipFile(new File(j.getWebAppRoot(), "../jenkins.war")); + ZipEntry entry = new JarEntry("META-INF/MANIFEST.MF"); + try (InputStream inputStream = jenkinsWar.getInputStream(entry)) { + assertNotNull("Cannot open input stream for /META-INF/MANIFEST.MF", inputStream); + Manifest manifest = new Manifest(inputStream); + + assertAttributeValue(manifest, "Remoting-Embedded-Version", RemotingVersionInfo.getEmbeddedVersion()); + assertAttributeValue(manifest, "Remoting-Minimum-Supported-Version", RemotingVersionInfo.getMinimumSupportedVersion()); + } + } + + private void assertAttributeValue(Manifest manifest, String attributeName, Object expectedValue) throws AssertionError { + assertThat("Wrong value of manifest attribute " + attributeName, + manifest.getMainAttributes().getValue(attributeName), + equalTo(expectedValue.toString())); + } +} diff --git a/war/pom.xml b/war/pom.xml index b25893b6b5..ecb48903a5 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -199,6 +199,8 @@ THE SOFTWARE. ${project.version} 1.395 ${project.version} + ${remoting.version} + ${remoting.minimum.supported.version} -- GitLab From 9842a2795e81bbdb0aeb5039cd9953bbb0ff2531 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Mon, 22 Jan 2018 17:45:26 -0500 Subject: [PATCH 024/863] [JENKINS-46652] Check Computer.BUILD permission only on heayweight tasks. --- core/src/main/java/hudson/model/Node.java | 2 +- core/src/main/java/hudson/model/queue/MappingWorksheet.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/hudson/model/Node.java b/core/src/main/java/hudson/model/Node.java index bcf81c19ad..70484762e4 100644 --- a/core/src/main/java/hudson/model/Node.java +++ b/core/src/main/java/hudson/model/Node.java @@ -394,7 +394,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable } Authentication identity = item.authenticate(); - if (!hasPermission(identity,Computer.BUILD)) { + if (!(item.task instanceof Queue.FlyweightTask) && !hasPermission(identity, Computer.BUILD)) { // doesn't have a permission return CauseOfBlockage.fromMessage(Messages._Node_LackingBuildPermission(identity.getName(), getDisplayName())); } diff --git a/core/src/main/java/hudson/model/queue/MappingWorksheet.java b/core/src/main/java/hudson/model/queue/MappingWorksheet.java index a8cdad8ef9..708f022284 100644 --- a/core/src/main/java/hudson/model/queue/MappingWorksheet.java +++ b/core/src/main/java/hudson/model/queue/MappingWorksheet.java @@ -134,7 +134,7 @@ public class MappingWorksheet { if (c.assignedLabel!=null && !c.assignedLabel.contains(node)) return false; // label mismatch - if (!nodeAcl.hasPermission(item.authenticate(), Computer.BUILD)) + if (!(item.task instanceof Queue.FlyweightTask) && !nodeAcl.hasPermission(item.authenticate(), Computer.BUILD)) return false; // tasks don't have a permission to run on this node return true; -- GitLab From 0f1c35291d38a7b5700a4cfa47eb04d2cee3a0a2 Mon Sep 17 00:00:00 2001 From: Daniel Trebbien Date: Mon, 22 Jan 2018 18:50:01 -0800 Subject: [PATCH 025/863] [JENKINS-49060] Include the filename of the unreadable file in the exception message --- core/src/main/java/hudson/util/TextFile.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/hudson/util/TextFile.java b/core/src/main/java/hudson/util/TextFile.java index 0ac1326ce7..fcfd45604e 100644 --- a/core/src/main/java/hudson/util/TextFile.java +++ b/core/src/main/java/hudson/util/TextFile.java @@ -75,6 +75,8 @@ public class TextFile { String line; while ((line = in.readLine()) != null) w.println(line); + } catch (Exception e) { + throw new IOException("Failed to fully read " + file, e); } return out.toString(); } -- GitLab From d2b81b8ad1a71410765aad0e92c540168571e1f3 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Tue, 23 Jan 2018 13:47:05 +0100 Subject: [PATCH 026/863] Separate original and downstream dependency errors Nobody understands 'fix this plugin first', so separate those from the actual issues that need fixing. --- core/src/main/java/hudson/PluginWrapper.java | 54 +++++++++++++++---- .../message.jelly | 31 ++++++++--- .../message.properties | 3 +- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/hudson/PluginWrapper.java b/core/src/main/java/hudson/PluginWrapper.java index 9ca85b04aa..64238fbe5c 100644 --- a/core/src/main/java/hudson/PluginWrapper.java +++ b/core/src/main/java/hudson/PluginWrapper.java @@ -37,6 +37,9 @@ import hudson.model.UpdateCenter; import hudson.model.UpdateSite; import hudson.util.VersionNumber; import org.jvnet.localizer.ResourceBundleHolder; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.DoNotUse; +import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.StaplerRequest; @@ -66,10 +69,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Predicate; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; import static java.util.logging.Level.WARNING; import static org.apache.commons.io.FilenameUtils.getBaseName; @@ -159,10 +164,34 @@ public class PluginWrapper implements Comparable, ModelObject { private final List optionalDependencies; public List getDependencyErrors() { - return Collections.unmodifiableList(dependencyErrors); + return Collections.unmodifiableList(new ArrayList<>(dependencyErrors.keySet())); } - private final transient List dependencyErrors = new ArrayList<>(); + @Restricted(NoExternalUse.class) // Jelly use + public List getOriginalDependencyErrors() { + Predicate> p = Map.Entry::getValue; + return dependencyErrors.entrySet().stream().filter(p.negate()).map(Map.Entry::getKey).collect(Collectors.toList()); + } + + @Restricted(NoExternalUse.class) // Jelly use + public boolean hasOriginalDependencyErrors() { + return !getOriginalDependencyErrors().isEmpty(); + } + + @Restricted(NoExternalUse.class) // Jelly use + public List getDerivedDependencyErrors() { + return dependencyErrors.entrySet().stream().filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.toList()); + } + + @Restricted(NoExternalUse.class) // Jelly use + public boolean hasDerivedDependencyErrors() { + return !getDerivedDependencyErrors().isEmpty(); + } + + /** + * A String error message, and a boolean indicating whether it's an original error (false) or downstream from an original one (true) + */ + private final transient Map dependencyErrors = new HashMap<>(); /** * Is this plugin bundled in jenkins.war? @@ -571,7 +600,7 @@ public class PluginWrapper implements Comparable, ModelObject { } else { VersionNumber actualVersion = Jenkins.getVersion(); if (actualVersion.isOlderThan(new VersionNumber(requiredCoreVersion))) { - dependencyErrors.add(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion)); + dependencyErrors.put(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion), false); } } } @@ -581,21 +610,21 @@ public class PluginWrapper implements Comparable, ModelObject { if (dependency == null) { PluginWrapper failedDependency = NOTICE.getPlugin(d.shortName); if (failedDependency != null) { - dependencyErrors.add(Messages.PluginWrapper_failed_to_load_dependency(failedDependency.getLongName(), failedDependency.getVersion())); + dependencyErrors.put(Messages.PluginWrapper_failed_to_load_dependency(failedDependency.getLongName(), failedDependency.getVersion()), true); break; } else { - dependencyErrors.add(Messages.PluginWrapper_missing(d.shortName, d.version)); + dependencyErrors.put(Messages.PluginWrapper_missing(d.shortName, d.version), false); } } else { if (dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { - dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version)); + dependencyErrors.put(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version), false); } } else { if (isDependencyObsolete(d, dependency)) { - dependencyErrors.add(Messages.PluginWrapper_disabledAndObsolete(dependency.getLongName(), dependency.getVersion(), d.version)); + dependencyErrors.put(Messages.PluginWrapper_disabledAndObsolete(dependency.getLongName(), dependency.getVersion(), d.version), false); } else { - dependencyErrors.add(Messages.PluginWrapper_disabled(dependency.getLongName())); + dependencyErrors.put(Messages.PluginWrapper_disabled(dependency.getLongName()), false); } } @@ -606,7 +635,7 @@ public class PluginWrapper implements Comparable, ModelObject { PluginWrapper dependency = parent.getPlugin(d.shortName); if (dependency != null && dependency.isActive()) { if (isDependencyObsolete(d, dependency)) { - dependencyErrors.add(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version)); + dependencyErrors.put(Messages.PluginWrapper_obsolete(dependency.getLongName(), dependency.getVersion(), d.version), false); } else { dependencies.add(d); } @@ -616,7 +645,7 @@ public class PluginWrapper implements Comparable, ModelObject { NOTICE.addPlugin(this); StringBuilder messageBuilder = new StringBuilder(); messageBuilder.append(Messages.PluginWrapper_failed_to_load_plugin(getLongName(), getVersion())).append(System.lineSeparator()); - for (Iterator iterator = dependencyErrors.iterator(); iterator.hasNext(); ) { + for (Iterator iterator = dependencyErrors.keySet().iterator(); iterator.hasNext(); ) { String dependencyError = iterator.next(); messageBuilder.append(" - ").append(dependencyError); if (iterator.hasNext()) { @@ -750,6 +779,11 @@ public class PluginWrapper implements Comparable, ModelObject { return !plugins.isEmpty(); } + @Restricted(DoNotUse.class) // Jelly + public boolean hasAnyDerivedDependencyErrors() { + return plugins.values().stream().anyMatch(PluginWrapper::hasDerivedDependencyErrors); + } + @Override public String getDisplayName() { return Messages.PluginWrapper_PluginWrapperAdministrativeMonitor_DisplayName(); diff --git a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly index a98627489a..067f0c1c97 100644 --- a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.jelly @@ -4,14 +4,31 @@
    - ${%Dependency errors} +

    ${%Dependency errors}:

    +

    ${%blurbOriginal}

    -
    -
    ${plugin.longName} v${plugin.version}
    - -
    ${d}
    -
    -
    + +
    +
    ${plugin.longName} version ${plugin.version}
    + +
    ${d}
    +
    +
    +
    + +

    ${%Downstream dependency errors}:

    +

    ${%blurbDerived}

    +
    + + +
    ${plugin.displayName} version ${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 555e651d66..9313dac39c 100644 --- a/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.properties +++ b/core/src/main/resources/hudson/PluginWrapper/PluginWrapperAdministrativeMonitor/message.properties @@ -19,4 +19,5 @@ # 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. +blurbOriginal=Some plugins could not be loaded due to unsatisfied dependencies. Fix these issues and restart Jenkins to restore the functionality provided by these plugins. +blurbDerived=These plugins failed to load because of one or more of the errors above. Fix those and these plugins will load again. -- GitLab From 7e8d817f78f4bad6c8100643c860cb326dc03a42 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Tue, 23 Jan 2018 14:45:10 +0100 Subject: [PATCH 027/863] Remove support for unbounded number of polling threads The default is 10. Previously unbounded configs will be set to 10. --- .../main/java/hudson/triggers/SCMTrigger.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/hudson/triggers/SCMTrigger.java b/core/src/main/java/hudson/triggers/SCMTrigger.java index 682ce987fb..5a1bea9e09 100644 --- a/core/src/main/java/hudson/triggers/SCMTrigger.java +++ b/core/src/main/java/hudson/triggers/SCMTrigger.java @@ -237,13 +237,19 @@ public class SCMTrigger extends Trigger { * Max number of threads for SCM polling. * 0 for unbounded. */ - private int maximumThreads; + private int maximumThreads = 10; public DescriptorImpl() { load(); resizeThreadPool(); } + private void readResolve() { + if (maximumThreads == 0) { + maximumThreads = 10; + } + } + public boolean isApplicable(Item item) { return SCMTriggerItem.SCMTriggerItems.asSCMTriggerItem(item) != null; } @@ -299,11 +305,11 @@ public class SCMTrigger extends Trigger { /** * Sets the number of concurrent threads used for SCM polling and resizes the thread pool accordingly - * @param n number of concurrent threads, zero or less means unlimited, maximum is 100 + * @param n number of concurrent threads in the range 10..100, outside values will set the to the nearest bound */ public void setPollingThreadCount(int n) { // fool proof - if(n<0) n=0; + if(n<10) n=10; if(n>100) n=100; maximumThreads = n; @@ -313,7 +319,7 @@ public class SCMTrigger extends Trigger { @Restricted(NoExternalUse.class) public boolean isPollingThreadCountOptionVisible() { - if (getPollingThreadCount() != 0) { + if (getPollingThreadCount() != 10) { // this is a user who already configured the option return true; } @@ -337,8 +343,7 @@ public class SCMTrigger extends Trigger { * Update the {@link ExecutorService} instance. */ /*package*/ synchronized void resizeThreadPool() { - queue.setExecutors( - (maximumThreads==0 ? Executors.newCachedThreadPool(threadFactory()) : Executors.newFixedThreadPool(maximumThreads, threadFactory()))); + queue.setExecutors(Executors.newFixedThreadPool(maximumThreads, threadFactory())); } @Override -- GitLab From f0b1bce2f4b5b14bb60f62647cffd68f7d8655c2 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Tue, 23 Jan 2018 17:35:35 +0100 Subject: [PATCH 028/863] Further clean up the polling threads option, better validation --- .../main/java/hudson/triggers/SCMTrigger.java | 32 +++++++++++-------- .../main/java/hudson/util/FormValidation.java | 24 ++++++++++++++ .../hudson/model/Messages.properties | 2 ++ .../SCMTrigger/help-pollingThreadCount.html | 8 ++--- .../help-pollingThreadCount_bg.html | 10 ------ .../help-pollingThreadCount_de.html | 10 ------ .../help-pollingThreadCount_fr.html | 11 ------- .../help-pollingThreadCount_it.html | 10 ------ .../help-pollingThreadCount_ja.html | 8 ----- .../help-pollingThreadCount_nl.html | 10 ------ .../help-pollingThreadCount_pt_BR.html | 9 ------ .../help-pollingThreadCount_ru.html | 9 ------ .../help-pollingThreadCount_tr.html | 9 ------ .../help-pollingThreadCount_zh_TW.html | 6 ---- 14 files changed, 46 insertions(+), 112 deletions(-) delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_bg.html delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_de.html delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_fr.html delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_it.html delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_ja.html delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_nl.html delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_pt_BR.html delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_ru.html delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_tr.html delete mode 100644 core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_zh_TW.html diff --git a/core/src/main/java/hudson/triggers/SCMTrigger.java b/core/src/main/java/hudson/triggers/SCMTrigger.java index 5a1bea9e09..7afeeacdb0 100644 --- a/core/src/main/java/hudson/triggers/SCMTrigger.java +++ b/core/src/main/java/hudson/triggers/SCMTrigger.java @@ -235,10 +235,13 @@ public class SCMTrigger extends Trigger { /** * Max number of threads for SCM polling. - * 0 for unbounded. */ private int maximumThreads = 10; + private static final int THREADS_LOWER_BOUND = 5; + private static final int THREADS_UPPER_BOUND = 100; + private static final int THREADS_DEFAULT= 10; + public DescriptorImpl() { load(); resizeThreadPool(); @@ -246,7 +249,7 @@ public class SCMTrigger extends Trigger { private void readResolve() { if (maximumThreads == 0) { - maximumThreads = 10; + maximumThreads = THREADS_DEFAULT; } } @@ -296,8 +299,6 @@ public class SCMTrigger extends Trigger { /** * Gets the number of concurrent threads used for polling. * - * @return - * 0 if unlimited. */ public int getPollingThreadCount() { return maximumThreads; @@ -305,12 +306,16 @@ public class SCMTrigger extends Trigger { /** * Sets the number of concurrent threads used for SCM polling and resizes the thread pool accordingly - * @param n number of concurrent threads in the range 10..100, outside values will set the to the nearest bound + * @param n number of concurrent threads in the range 5..100, outside values will set the to the nearest bound */ public void setPollingThreadCount(int n) { // fool proof - if(n<10) n=10; - if(n>100) n=100; + if (n < THREADS_LOWER_BOUND) { + n = THREADS_LOWER_BOUND; + } + if (n > THREADS_UPPER_BOUND) { + n = THREADS_UPPER_BOUND; + } maximumThreads = n; @@ -319,7 +324,7 @@ public class SCMTrigger extends Trigger { @Restricted(NoExternalUse.class) public boolean isPollingThreadCountOptionVisible() { - if (getPollingThreadCount() != 10) { + if (getPollingThreadCount() != THREADS_DEFAULT) { // this is a user who already configured the option return true; } @@ -349,10 +354,11 @@ public class SCMTrigger extends Trigger { @Override public boolean configure(StaplerRequest req, JSONObject json) throws FormException { String t = json.optString("pollingThreadCount",null); - if(t==null || t.length()==0) - setPollingThreadCount(0); - else + if (doCheckPollingThreadCount(t).kind != FormValidation.Kind.OK) { + setPollingThreadCount(THREADS_DEFAULT); + } else { setPollingThreadCount(Integer.parseInt(t)); + } // Save configuration save(); @@ -361,9 +367,7 @@ public class SCMTrigger extends Trigger { } public FormValidation doCheckPollingThreadCount(@QueryParameter String value) { - if (value != null && "".equals(value.trim())) - return FormValidation.ok(); - return FormValidation.validateNonNegativeInteger(value); + return FormValidation.validateIntegerInRange(value, THREADS_LOWER_BOUND, THREADS_UPPER_BOUND); } /** diff --git a/core/src/main/java/hudson/util/FormValidation.java b/core/src/main/java/hudson/util/FormValidation.java index c95e5d1da8..b8964811a2 100644 --- a/core/src/main/java/hudson/util/FormValidation.java +++ b/core/src/main/java/hudson/util/FormValidation.java @@ -393,6 +393,30 @@ public abstract class FormValidation extends IOException implements HttpResponse } } + /** + * Make sure that the given string is an integer in the range specified by the lower and upper bounds (both inclusive) + * + * @param value the value to check + * @param lower the lower bound (inclusive) + * @param upper the upper bound (inclusive) + * + * @since TODO + */ + public static FormValidation validateIntegerInRange(String value, int lower, int upper) { + try { + int intValue = Integer.parseInt(value); + if (intValue < lower) { + return error(hudson.model.Messages.Hudson_MustBeAtLeast(lower)); + } + if (intValue > upper) { + return error(hudson.model.Messages.Hudson_MustBeAtMost(upper)); + } + return ok(); + } catch (NumberFormatException e) { + return error(hudson.model.Messages.Hudson_NotANumber()); + } + } + /** * Makes sure that the given string is a positive integer. */ diff --git a/core/src/main/resources/hudson/model/Messages.properties b/core/src/main/resources/hudson/model/Messages.properties index 35d5c33cc8..600d509e98 100644 --- a/core/src/main/resources/hudson/model/Messages.properties +++ b/core/src/main/resources/hudson/model/Messages.properties @@ -149,6 +149,8 @@ Hudson.NotANumber=Not a number Hudson.NotAPositiveNumber=Not a positive number Hudson.NotANonNegativeNumber=Number may not be negative Hudson.NotANegativeNumber=Not a negative number +Hudson.MustBeAtLeast=Must be {0} or greater +Hudson.MustBeAtMost=Must be {0} or less Hudson.NotUsesUTF8ToDecodeURL=\ Your container doesn\u2019t use UTF-8 to decode URLs. If you use non-ASCII characters as a job name etc, \ this will cause problems. \ diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount.html index b4e1e8c45e..5d45a865d2 100644 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount.html +++ b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount.html @@ -1,9 +1,5 @@
    - If you use SCM polling to trigger a new build for a large number of projects, - you may want to limit the number of concurrent polling activities to avoid - overloading the server. - + This option configures the maximum number of concurrent threads that are used by Jenkins to poll for SCM changes.

    - Setting a positive number sets the upper bound to the number of concurrent polling. - Leaving the field empty will make it unbounded. + Depending on your network connection, SCM service, and Jenkins configuration, you may need to increase or decrease the number of threads to achieve optimal results.

    \ No newline at end of file diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_bg.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_bg.html deleted file mode 100644 index d3a0eb9407..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_bg.html +++ /dev/null @@ -1,10 +0,0 @@ -
    - Ако много проекти се изграждат след слушане за промени в системата за контрол - на версиите, може да искате да ограничите общия брой едновременни заявки към - сървъра за версиите, за да не бъде претоварен. - -

    - Попълването на положително цяло число ограничава броя на едновременните заявки - за проверка. Ако оставите полето празно, няма да има никакви ограничения в - броя на едновременните заявки. -

    diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_de.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_de.html deleted file mode 100644 index 846b1801b5..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_de.html +++ /dev/null @@ -1,10 +0,0 @@ -
    - Um eine Überlastung des Servers zu vermeiden, kann es bei einer großen Anzahl - von zu bauenden Projekten sinnvoll sein, die Anzahl gleichzeitiger SCM-Anfragen - zu begrenzen. - -

    - Geben Sie eine positive Zahl an, um die Anzahl der gleichzeitigen SCM-Anfragen - auf diesen Wert zu begrenzen. Lassen Sie das Feld frei, wenn Sie keine - Begrenzung wünschen. -

    \ No newline at end of file diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_fr.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_fr.html deleted file mode 100644 index 832dfde213..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_fr.html +++ /dev/null @@ -1,11 +0,0 @@ -
    - Si vous utilisez la scrutation de l'outil de gestion de version pour - déclencher un nouveau build pour un grand nombre de projets, vous - souhaiterez peut-être limiter le nombre de scrutations parallèles, afin - d'éviter de surcharger le serveur. - -

    - Un nombre positif indique la limite supérieure du nombre de scrutations - parallèles. - Laissez le champ vide pour supprimer toute limite supérieure. -

    \ No newline at end of file diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_it.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_it.html deleted file mode 100644 index 8b6c74881f..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_it.html +++ /dev/null @@ -1,10 +0,0 @@ -
    - Se si utilizza il polling del sistema di controllo del codice sorgente per - attivare una nuova compilazione per un numero elevato di progetti, si - potrebbe desiderare di limitare il numero di polling concorrenti per evitare - di sovraccaricare il server. - -

    - Impostando un numero positivo si imposta il limite superiore al numero dei - polling concorrenti. Lasciando il campo vuoto non si imposta alcun limite. -

    diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_ja.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_ja.html deleted file mode 100644 index 5bcac2d21c..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_ja.html +++ /dev/null @@ -1,8 +0,0 @@ -
    - SCMのポーリングを使用してたくさんのプロジェクトの新規ビルドを起動するのなら、 - サーバーの過負荷を避けるために、並行して動くポーリング数を制限してください。 - -

    - 正の数を設定すると、並行してポーリングする数の上限を設定します。 - 項目を空欄のままにすると、無制限になります。 -

    \ No newline at end of file diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_nl.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_nl.html deleted file mode 100644 index c4e33a4f95..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_nl.html +++ /dev/null @@ -1,10 +0,0 @@ -
    - Indien U voor een groot aantal projecten, bouwpogingen start omwille van het - bemonsteren van u versiecontrolesysteem, kan het nuttig zijn om het aantal - parallelle bemonsteringsopdrachten te limiteren, teneinde het versiecontrolesysteem - niet te overbelasten. - -

    - Een positieve waarde bepaalt het maximaal aantal parallelle bemonsteringsopdrachten. - Standaard is dit maximum onbegrensd. -

    \ No newline at end of file diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_pt_BR.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_pt_BR.html deleted file mode 100644 index e92297111f..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_pt_BR.html +++ /dev/null @@ -1,9 +0,0 @@ -
    - Se você usa a votação de SCM para disparar uma nova construção para um grande número de projetos, - você pode querer limitar o número de atividades de votação concorrentes para evitar - sobrecarregar o servidor. - -

    - Informando um número positivo indica o limite superior do número de votações concorrentes. - Deixar o campo vazio o tornará ilimitado. -

    diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_ru.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_ru.html deleted file mode 100644 index 00cfe9e521..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_ru.html +++ /dev/null @@ -1,9 +0,0 @@ -
    - Если вы используете опрос сервера с системой контроля версий для инициации - новой сборки для большого количества проектов, для предотвращения перегрузки - сервера рекомендуется ограничить количество одновременных опросов. -

    - Установив положительное значение в это поле, вы зададите верхнюю границу - количества конкурирующих опросов, оставив поле пустым - ограничение - установлено не будет. -

    \ No newline at end of file diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_tr.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_tr.html deleted file mode 100644 index 2cf7791765..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_tr.html +++ /dev/null @@ -1,9 +0,0 @@ -
    - Çok sayıda proje üzerinde, yapılandırmaları tetiklemek için SCM kontrolleri kullanıyorsanız, - SCM sunucusunu çok fazla yorabilirsiniz. Bunu engellemek için eşzamanlı SCM kontrol sayısını - sınırlamak isteyebilirsiniz. - -

    - Bu alana pozitif bir sayı yazarsanız, eşzamanlı SCM kontrolü sayısının üst limiti olacaktır. - Eğer boş bırakırsanız, herhangi bir sınır kullanılmayacaktır. -

    \ No newline at end of file diff --git a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_zh_TW.html b/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_zh_TW.html deleted file mode 100644 index 63617d80d3..0000000000 --- a/core/src/main/resources/hudson/triggers/SCMTrigger/help-pollingThreadCount_zh_TW.html +++ /dev/null @@ -1,6 +0,0 @@ -
    - 如果很多專案的建置是由 SCM 輪詢機制觸發,您或許會想限制同時輪詢活動的上限數,免得讓伺服器過忙。 - -

    - 正數代表同時輪詢的上限數。不填則代表不限制。 -

    \ No newline at end of file -- GitLab From 104f4888bbf587103dc1df6f870a8c688de66895 Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Wed, 27 Aug 2014 03:50:37 +0200 Subject: [PATCH 029/863] [FIXED JENKINS-24452] Don't export arbitrary unexportable exceptions --- core/src/main/java/hudson/slaves/OfflineCause.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/main/java/hudson/slaves/OfflineCause.java b/core/src/main/java/hudson/slaves/OfflineCause.java index 3d711dd5f0..7c5c77861f 100644 --- a/core/src/main/java/hudson/slaves/OfflineCause.java +++ b/core/src/main/java/hudson/slaves/OfflineCause.java @@ -103,7 +103,6 @@ public abstract class OfflineCause { * Caused by unexpected channel termination. */ public static class ChannelTermination extends OfflineCause { - @Exported public final Exception cause; public ChannelTermination(Exception cause) { -- GitLab From 240a127315437f700a068afaf6b9dd2bd4cde8f8 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Wed, 24 Jan 2018 10:56:38 +0100 Subject: [PATCH 030/863] OldDataMonitor: Prevent escaping b-old version --- .../hudson/diagnosis/OldDataMonitor/manage.jelly | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.jelly b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.jelly index 0f73ec6d0f..56587c3092 100644 --- a/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.jelly +++ b/core/src/main/resources/hudson/diagnosis/OldDataMonitor/manage.jelly @@ -34,15 +34,22 @@ THE SOFTWARE. ${%Type}${%Name}${%Version} - - - ${range} + ${obj.class.name} ${obj.fullName?:obj.fullDisplayName?:obj.displayName?:obj.name} - ${range} + + + + ${item.value} + + + ${item.value} + + + ${item.value.extra} -- GitLab From 8e189efbdf0ffe228d15d85dbaa764fea02eb07d Mon Sep 17 00:00:00 2001 From: MarcoAurelioWM <30000615+MarcoAurelioWM@users.noreply.github.com> Date: Wed, 24 Jan 2018 13:09:57 +0100 Subject: [PATCH 031/863] Fix Spanish (es) link translation Typo/gramar fix. --- .../hudson/security/SecurityRealm/loginLink_es.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/resources/hudson/security/SecurityRealm/loginLink_es.properties b/core/src/main/resources/hudson/security/SecurityRealm/loginLink_es.properties index caf5fd8393..5387de8441 100644 --- a/core/src/main/resources/hudson/security/SecurityRealm/loginLink_es.properties +++ b/core/src/main/resources/hudson/security/SecurityRealm/loginLink_es.properties @@ -20,4 +20,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -login=Iniciar Sesion +login=Iniciar sesión -- GitLab From 9a4a913e387c49d0c4cec602280e25c37196f83c Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Fri, 8 Dec 2017 14:45:22 -0500 Subject: [PATCH 032/863] [JENKINS-48463] - update jenkins core to use xml v1.1 - switched to KxmlDriver (needed for xml 1.1 support) - updated to create xml v1.1 headers - updated test to use KXml2Driver explicitly - updated unit tests to support xml v1.1 --- core/src/main/java/hudson/Main.java | 2 +- core/src/main/java/hudson/XmlFile.java | 7 ++-- .../main/java/hudson/model/Fingerprint.java | 2 +- core/src/main/java/hudson/util/XStream2.java | 2 +- .../test/java/hudson/PluginManagerTest.java | 2 +- .../hudson/util/XStream2EncodingTest.java | 2 +- .../java/jenkins/model/RunIdMigratorTest.java | 36 +++++++++---------- .../jenkins/util/xstream/XStreamDOMTest.java | 3 +- .../test/java/jenkins/xml/XMLUtilsTest.java | 6 ++-- .../java/hudson/cli/GetNodeCommandTest.java | 2 +- .../java/hudson/cli/GetViewCommandTest.java | 2 +- .../model/AbstractItemSecurityTest.java | 4 +-- .../model/ComputerConfigDotXmlTest.java | 2 +- .../java/hudson/model/ItemGroupMixInTest.java | 2 +- .../util/RobustReflectionConverterTest.java | 2 +- 15 files changed, 38 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/hudson/Main.java b/core/src/main/java/hudson/Main.java index 896ae4dd53..11cb516cee 100644 --- a/core/src/main/java/hudson/Main.java +++ b/core/src/main/java/hudson/Main.java @@ -144,7 +144,7 @@ public class Main { int ret; try (OutputStream os = Files.newOutputStream(tmpFile.toPath()); Writer w = new OutputStreamWriter(os,"UTF-8")) { - w.write(""); + w.write(""); w.write(""); w.flush(); diff --git a/core/src/main/java/hudson/XmlFile.java b/core/src/main/java/hudson/XmlFile.java index 16644262b9..ae65b265d5 100644 --- a/core/src/main/java/hudson/XmlFile.java +++ b/core/src/main/java/hudson/XmlFile.java @@ -26,7 +26,7 @@ package hudson; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.io.xml.Xpp3Driver; +import com.thoughtworks.xstream.io.xml.KXml2Driver; import hudson.diagnosis.OldDataMonitor; import hudson.model.Descriptor; import hudson.util.AtomicFileWriter; @@ -39,7 +39,6 @@ import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.ext.Locator2; import org.xml.sax.helpers.DefaultHandler; - import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import java.io.BufferedInputStream; @@ -172,7 +171,7 @@ public final class XmlFile { mkdirs(); AtomicFileWriter w = new AtomicFileWriter(file); try { - w.write("\n"); + w.write("\n"); beingWritten.put(o, null); writing.set(file); try { @@ -343,7 +342,7 @@ public final class XmlFile { private static final SAXParserFactory JAXP = SAXParserFactory.newInstance(); - private static final Xpp3Driver DEFAULT_DRIVER = new Xpp3Driver(); + private static final KXml2Driver DEFAULT_DRIVER = new KXml2Driver(); static { JAXP.setNamespaceAware(true); diff --git a/core/src/main/java/hudson/model/Fingerprint.java b/core/src/main/java/hudson/model/Fingerprint.java index b2fcc6c5d6..2e0a7ce166 100644 --- a/core/src/main/java/hudson/model/Fingerprint.java +++ b/core/src/main/java/hudson/model/Fingerprint.java @@ -1256,7 +1256,7 @@ public class Fingerprint implements ModelObject, Saveable { AtomicFileWriter afw = new AtomicFileWriter(file); try { PrintWriter w = new PrintWriter(afw); - w.println(""); + w.println(""); w.println(""); w.print(" "); w.print(DATE_CONVERTER.toString(timestamp)); diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 6b17761cb6..285dd6b2db 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -215,7 +215,7 @@ public class XStream2 extends XStream { */ public void toXMLUTF8(Object obj, OutputStream out) throws IOException { Writer w = new OutputStreamWriter(out, Charset.forName("UTF-8")); - w.write("\n"); + w.write("\n"); toXML(obj, w); } diff --git a/core/src/test/java/hudson/PluginManagerTest.java b/core/src/test/java/hudson/PluginManagerTest.java index 843e15ba1e..1e89e5c5c8 100644 --- a/core/src/test/java/hudson/PluginManagerTest.java +++ b/core/src/test/java/hudson/PluginManagerTest.java @@ -64,7 +64,7 @@ public class PluginManagerTest { @Issue("SECURITY-167") @Test public void parseInvalidRequestedPlugins() throws Exception { - String evilXML = "\n" + + String evilXML = "\n" + "]>\n" + "\n" + " \n" + diff --git a/core/src/test/java/hudson/util/XStream2EncodingTest.java b/core/src/test/java/hudson/util/XStream2EncodingTest.java index ea23797dea..ea10414ebc 100644 --- a/core/src/test/java/hudson/util/XStream2EncodingTest.java +++ b/core/src/test/java/hudson/util/XStream2EncodingTest.java @@ -70,7 +70,7 @@ public class XStream2EncodingTest { Thing t = (Thing) xs.fromXML(new ByteArrayInputStream(ambiguousXml)); assertThat(t.field, not(msg)); ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); - baos2.write("\n".getBytes("UTF-8")); + baos2.write("\n".getBytes("UTF-8")); baos2.write(ambiguousXml); t = (Thing) xs.fromXML(new ByteArrayInputStream(ambiguousXml)); assertThat(t.field, not(msg)); diff --git a/core/src/test/java/jenkins/model/RunIdMigratorTest.java b/core/src/test/java/jenkins/model/RunIdMigratorTest.java index 71a3e47832..fda491eda6 100644 --- a/core/src/test/java/jenkins/model/RunIdMigratorTest.java +++ b/core/src/test/java/jenkins/model/RunIdMigratorTest.java @@ -83,13 +83,13 @@ public class RunIdMigratorTest { @Test public void legacy() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); - write("2014-01-02_03-04-05/build.xml", "\n\n ok\n 99\n ok\n"); + write("2014-01-02_03-04-05/build.xml", "\n\n ok\n 99\n ok\n"); link("99", "2014-01-02_03-04-05"); link("lastFailedBuild", "-1"); link("lastSuccessfulBuild", "99"); - assertEquals("{2014-01-02_03-04-05={build.xml='\n\n ok\n 99\n ok\n'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); + assertEquals("{2014-01-02_03-04-05={build.xml='\n\n ok\n 99\n ok\n'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); assertTrue(migrator.migrate(dir, null)); - assertEquals("{99={build.xml='\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize()); + assertEquals("{99={build.xml='\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize()); assertEquals(99, migrator.findNumber("2014-01-02_03-04-05")); migrator = new RunIdMigrator(); assertFalse(migrator.migrate(dir, null)); @@ -104,58 +104,58 @@ public class RunIdMigratorTest { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); write("2014-01-02_03-04-04/build.xml", "\n 98\n"); link("98", "2014-01-02_03-04-04"); - write("99/build.xml", "\n\n ok\n 1388649845000\n ok\n"); + write("99/build.xml", "\n\n ok\n 1388649845000\n ok\n"); link("lastFailedBuild", "-1"); link("lastSuccessfulBuild", "99"); - assertEquals("{2014-01-02_03-04-04={build.xml='\n 98\n'}, 98=→2014-01-02_03-04-04, 99={build.xml='\n\n ok\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); + assertEquals("{2014-01-02_03-04-04={build.xml='\n 98\n'}, 98=→2014-01-02_03-04-04, 99={build.xml='\n\n ok\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); assertTrue(migrator.migrate(dir, null)); - assertEquals("{98={build.xml='\n 2014-01-02_03-04-04\n 1388649844000\n'}, 99={build.xml='\n\n ok\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-04 98\n'}", summarize()); + assertEquals("{98={build.xml='\n 2014-01-02_03-04-04\n 1388649844000\n'}, 99={build.xml='\n\n ok\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-04 98\n'}", summarize()); } @Test public void reverseImmediately() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); File root = dir; dir = new File(dir, "jobs/somefolder/jobs/someproject/promotions/OK/builds"); - write("99/build.xml", "\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n"); + write("99/build.xml", "\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n"); link("lastFailedBuild", "-1"); link("lastSuccessfulBuild", "99"); write("legacyIds", "2014-01-02_03-04-05 99\n"); - assertEquals("{99={build.xml='\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize()); + assertEquals("{99={build.xml='\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize()); RunIdMigrator.main(root.getAbsolutePath()); - assertEquals("{2014-01-02_03-04-05={build.xml='\n\n ok\n 99\n ok\n'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); + assertEquals("{2014-01-02_03-04-05={build.xml='\n\n ok\n 99\n ok\n'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); } @Test public void reverseAfterNewBuilds() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); File root = dir; dir = new File(dir, "jobs/someproject/modules/test$test/builds"); - write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); + write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); write("legacyIds", ""); - assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); + assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); RunIdMigrator.main(root.getAbsolutePath()); - assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); + assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); } @Test public void reverseMatrixAfterNewBuilds() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); File root = dir; dir = new File(dir, "jobs/someproject/Environment=prod/builds"); - write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); + write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); write("legacyIds", ""); - assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); + assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); RunIdMigrator.main(root.getAbsolutePath()); - assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); + assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); } @Test public void reverseMavenAfterNewBuilds() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); File root = dir; dir = new File(dir, "jobs/someproject/test$test/builds"); - write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); + write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); write("legacyIds", ""); - assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); + assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); RunIdMigrator.main(root.getAbsolutePath()); - assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); + assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); } // TODO test sane recovery from various error conditions diff --git a/core/src/test/java/jenkins/util/xstream/XStreamDOMTest.java b/core/src/test/java/jenkins/util/xstream/XStreamDOMTest.java index b32ed5a40f..69a1fbe7ef 100644 --- a/core/src/test/java/jenkins/util/xstream/XStreamDOMTest.java +++ b/core/src/test/java/jenkins/util/xstream/XStreamDOMTest.java @@ -23,6 +23,7 @@ */ package jenkins.util.xstream; +import com.thoughtworks.xstream.io.xml.KXml2Driver; import hudson.util.XStream2; import org.apache.commons.io.IOUtils; import org.junit.Before; @@ -53,7 +54,7 @@ public class XStreamDOMTest { @Before public void setUp() throws Exception { - xs = new XStream2(); + xs = new XStream2(new KXml2Driver()); xs.alias("foo", Foo.class); } diff --git a/core/src/test/java/jenkins/xml/XMLUtilsTest.java b/core/src/test/java/jenkins/xml/XMLUtilsTest.java index 55b0d806bb..5b998b1ca1 100644 --- a/core/src/test/java/jenkins/xml/XMLUtilsTest.java +++ b/core/src/test/java/jenkins/xml/XMLUtilsTest.java @@ -49,7 +49,7 @@ public class XMLUtilsTest { @Issue("SECURITY-167") @Test() public void testSafeTransformDoesNotProcessForeignResources() throws Exception { - final String xml = "\n" + + final String xml = "\n" + "\n" + "]>\n" + @@ -82,7 +82,7 @@ public class XMLUtilsTest { @Issue("SECURITY-167") @Test() public void testUpdateByXmlIDoesNotFail() throws Exception { - final String xml = "\n" + + final String xml = "\n" + "\n" + " \n" + " &\n" + @@ -119,7 +119,7 @@ public class XMLUtilsTest { @Test public void testParse_with_XXE() throws IOException, XPathExpressionException { try { - final String xml = "\n" + + final String xml = "\n" + "\n" + " ]> " + diff --git a/test/src/test/java/hudson/cli/GetNodeCommandTest.java b/test/src/test/java/hudson/cli/GetNodeCommandTest.java index b2792924aa..71cc405915 100644 --- a/test/src/test/java/hudson/cli/GetNodeCommandTest.java +++ b/test/src/test/java/hudson/cli/GetNodeCommandTest.java @@ -74,7 +74,7 @@ public class GetNodeCommandTest { .invokeWithArgs("MySlave") ; - assertThat(result.stdout(), startsWith("")); + assertThat(result.stdout(), startsWith("")); assertThat(result.stdout(), containsString("MySlave")); assertThat(result, hasNoErrorOutput()); assertThat(result, succeeded()); diff --git a/test/src/test/java/hudson/cli/GetViewCommandTest.java b/test/src/test/java/hudson/cli/GetViewCommandTest.java index d3a6fd9ef7..c08af750d2 100644 --- a/test/src/test/java/hudson/cli/GetViewCommandTest.java +++ b/test/src/test/java/hudson/cli/GetViewCommandTest.java @@ -79,7 +79,7 @@ public class GetViewCommandTest { assertThat(result, succeeded()); assertThat(result, hasNoErrorOutput()); - assertThat(result.stdout(), startsWith("")); + assertThat(result.stdout(), startsWith("")); assertThat(result.stdout(), containsString("aView")); } diff --git a/test/src/test/java/hudson/model/AbstractItemSecurityTest.java b/test/src/test/java/hudson/model/AbstractItemSecurityTest.java index 653097d295..7ed8b79557 100644 --- a/test/src/test/java/hudson/model/AbstractItemSecurityTest.java +++ b/test/src/test/java/hudson/model/AbstractItemSecurityTest.java @@ -47,7 +47,7 @@ public class AbstractItemSecurityTest { @Issue("SECURITY-167") @Test() public void testUpdateByXmlDoesNotProcessForeignResources() throws Exception { - final String xml = "\n" + + final String xml = "\n" + "\n" + "]>\n" + @@ -73,7 +73,7 @@ public class AbstractItemSecurityTest { @Issue("SECURITY-167") @Test() public void testUpdateByXmlDoesNotFail() throws Exception { - final String xml = "\n" + + final String xml = "\n" + "\n" + " &\n" + " \n" + diff --git a/test/src/test/java/hudson/model/ComputerConfigDotXmlTest.java b/test/src/test/java/hudson/model/ComputerConfigDotXmlTest.java index 737e7b3228..6661d55780 100644 --- a/test/src/test/java/hudson/model/ComputerConfigDotXmlTest.java +++ b/test/src/test/java/hudson/model/ComputerConfigDotXmlTest.java @@ -121,7 +121,7 @@ public class ComputerConfigDotXmlTest { computer.doConfigDotXml(req, rsp); final String out = outputStream.toString(); - assertThat(out, startsWith("")); + assertThat(out, startsWith("")); assertThat(out, containsString("slave0")); assertThat(out, containsString("dummy")); } diff --git a/test/src/test/java/hudson/model/ItemGroupMixInTest.java b/test/src/test/java/hudson/model/ItemGroupMixInTest.java index 79ea318b8b..0bf83e5970 100644 --- a/test/src/test/java/hudson/model/ItemGroupMixInTest.java +++ b/test/src/test/java/hudson/model/ItemGroupMixInTest.java @@ -185,7 +185,7 @@ public class ItemGroupMixInTest { @Test public void createProjectFromXMLShouldNoCreateEntities() throws IOException { - final String xml = "\n" + + final String xml = "\n" + "\n" + "]>\n" + diff --git a/test/src/test/java/hudson/util/RobustReflectionConverterTest.java b/test/src/test/java/hudson/util/RobustReflectionConverterTest.java index 0bff3490cf..bdbc887b88 100644 --- a/test/src/test/java/hudson/util/RobustReflectionConverterTest.java +++ b/test/src/test/java/hudson/util/RobustReflectionConverterTest.java @@ -168,7 +168,7 @@ public class RobustReflectionConverterTest { } private static final String CONFIGURATION_TEMPLATE = - "" + "" + "" + "" + "" -- GitLab From 37365eeca7e8683920dd0d13f039e36380f19e24 Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Fri, 8 Dec 2017 17:30:55 -0500 Subject: [PATCH 033/863] [JENKINS-48463] update hard coded references of XPP3Driver to KXml2Driver in order to support XMLv1.1 --- core/src/main/java/hudson/model/View.java | 4 ++-- .../src/main/java/jenkins/util/xstream/XStreamDOM.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index fa3914af44..fe181c327d 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -26,7 +26,7 @@ package hudson.model; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.io.StreamException; -import com.thoughtworks.xstream.io.xml.Xpp3Driver; +import com.thoughtworks.xstream.io.xml.KXml2Driver; import hudson.DescriptorExtensionList; import hudson.Extension; import hudson.ExtensionPoint; @@ -1213,7 +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; - Object o = Jenkins.XSTREAM.unmarshal(new Xpp3Driver().createReader(in), this); + Object o = Jenkins.XSTREAM.unmarshal(new KXml2Driver().createReader(in), this); 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/jenkins/util/xstream/XStreamDOM.java b/core/src/main/java/jenkins/util/xstream/XStreamDOM.java index d98f346540..f0def484da 100644 --- a/core/src/main/java/jenkins/util/xstream/XStreamDOM.java +++ b/core/src/main/java/jenkins/util/xstream/XStreamDOM.java @@ -34,8 +34,8 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.AbstractXmlReader; import com.thoughtworks.xstream.io.xml.AbstractXmlWriter; import com.thoughtworks.xstream.io.xml.DocumentReader; +import com.thoughtworks.xstream.io.xml.KXml2Driver; import com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer; -import com.thoughtworks.xstream.io.xml.Xpp3Driver; import hudson.Util; import hudson.util.VariableResolver; @@ -241,11 +241,11 @@ public class XStreamDOM { * Writes this {@link XStreamDOM} into {@link OutputStream}. */ public void writeTo(OutputStream os) { - writeTo(new Xpp3Driver().createWriter(os)); + writeTo(new KXml2Driver().createWriter(os)); } public void writeTo(Writer w) { - writeTo(new Xpp3Driver().createWriter(w)); + writeTo(new KXml2Driver().createWriter(w)); } public void writeTo(HierarchicalStreamWriter w) { @@ -262,11 +262,11 @@ public class XStreamDOM { } public static XStreamDOM from(InputStream in) { - return from(new Xpp3Driver().createReader(in)); + return from(new KXml2Driver().createReader(in)); } public static XStreamDOM from(Reader in) { - return from(new Xpp3Driver().createReader(in)); + return from(new KXml2Driver().createReader(in)); } public static XStreamDOM from(HierarchicalStreamReader in) { -- GitLab From 570b52dbcc4e6b2555bb8080994d30cc7228f9ca Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Wed, 10 Jan 2018 08:31:34 -0500 Subject: [PATCH 034/863] [JENKINS-48463] refactored XStream2 to default to KXml2Driver by default. Changed all explicit references of the hierarchical driver to use a static convinience method XStream2.getDefaultDriver() to ensure all XML operations are using the same driver. --- core/src/main/java/hudson/XmlFile.java | 7 ++-- core/src/main/java/hudson/model/View.java | 3 +- core/src/main/java/hudson/util/XStream2.java | 13 +++++++ .../java/jenkins/util/xstream/XStreamDOM.java | 10 +++--- .../test/java/hudson/PluginManagerTest.java | 2 +- .../hudson/util/XStream2EncodingTest.java | 2 +- .../java/jenkins/model/RunIdMigratorTest.java | 36 +++++++++---------- .../jenkins/util/xstream/XStreamDOMTest.java | 3 +- .../test/java/jenkins/xml/XMLUtilsTest.java | 6 ++-- .../model/AbstractItemSecurityTest.java | 4 +-- .../java/hudson/model/ItemGroupMixInTest.java | 2 +- 11 files changed, 50 insertions(+), 38 deletions(-) diff --git a/core/src/main/java/hudson/XmlFile.java b/core/src/main/java/hudson/XmlFile.java index ae65b265d5..f63ed33881 100644 --- a/core/src/main/java/hudson/XmlFile.java +++ b/core/src/main/java/hudson/XmlFile.java @@ -26,7 +26,7 @@ package hudson; import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.io.xml.KXml2Driver; +import com.thoughtworks.xstream.io.HierarchicalStreamDriver; import hudson.diagnosis.OldDataMonitor; import hudson.model.Descriptor; import hudson.util.AtomicFileWriter; @@ -336,13 +336,14 @@ public final class XmlFile { /** * {@link XStream} instance is supposed to be thread-safe. */ - private static final XStream DEFAULT_XSTREAM = new XStream2(); private static final Logger LOGGER = Logger.getLogger(XmlFile.class.getName()); private static final SAXParserFactory JAXP = SAXParserFactory.newInstance(); - private static final KXml2Driver DEFAULT_DRIVER = new KXml2Driver(); + private static final HierarchicalStreamDriver DEFAULT_DRIVER = XStream2.getDefaultDriver(); + + private static final XStream DEFAULT_XSTREAM = new XStream2(DEFAULT_DRIVER); static { JAXP.setNamespaceAware(true); diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index fe181c327d..159f0befc3 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -26,7 +26,6 @@ package hudson.model; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.io.StreamException; -import com.thoughtworks.xstream.io.xml.KXml2Driver; import hudson.DescriptorExtensionList; import hudson.Extension; import hudson.ExtensionPoint; @@ -1213,7 +1212,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; - Object o = Jenkins.XSTREAM.unmarshal(new KXml2Driver().createReader(in), this); + Object o = Jenkins.XSTREAM.unmarshal(XStream2.getDefaultDriver().createReader(in), this); 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 285dd6b2db..b4a9520d28 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -26,6 +26,7 @@ package hudson.util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.io.xml.KXml2Driver; import com.thoughtworks.xstream.mapper.AnnotationMapper; import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.mapper.MapperWrapper; @@ -86,7 +87,18 @@ public class XStream2 extends XStream { */ private MapperInjectionPoint mapperInjectionPoint; + /** + * Convinience method so we only have to change the driver in one place + * if we switch to something new in the future + * + * @return a new instance of the HierarchicalStreamDriver we want to use + */ + public static HierarchicalStreamDriver getDefaultDriver() { + return new KXml2Driver(); + } + public XStream2() { + super(getDefaultDriver()); init(); classOwnership = null; } @@ -98,6 +110,7 @@ public class XStream2 extends XStream { } XStream2(ClassOwnership classOwnership) { + super(getDefaultDriver()); init(); this.classOwnership = classOwnership; } diff --git a/core/src/main/java/jenkins/util/xstream/XStreamDOM.java b/core/src/main/java/jenkins/util/xstream/XStreamDOM.java index f0def484da..2b2ed1621b 100644 --- a/core/src/main/java/jenkins/util/xstream/XStreamDOM.java +++ b/core/src/main/java/jenkins/util/xstream/XStreamDOM.java @@ -34,11 +34,11 @@ import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.AbstractXmlReader; import com.thoughtworks.xstream.io.xml.AbstractXmlWriter; import com.thoughtworks.xstream.io.xml.DocumentReader; -import com.thoughtworks.xstream.io.xml.KXml2Driver; import com.thoughtworks.xstream.io.xml.XmlFriendlyReplacer; import hudson.Util; import hudson.util.VariableResolver; +import hudson.util.XStream2; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; @@ -241,11 +241,11 @@ public class XStreamDOM { * Writes this {@link XStreamDOM} into {@link OutputStream}. */ public void writeTo(OutputStream os) { - writeTo(new KXml2Driver().createWriter(os)); + writeTo(XStream2.getDefaultDriver().createWriter(os)); } public void writeTo(Writer w) { - writeTo(new KXml2Driver().createWriter(w)); + writeTo(XStream2.getDefaultDriver().createWriter(w)); } public void writeTo(HierarchicalStreamWriter w) { @@ -262,11 +262,11 @@ public class XStreamDOM { } public static XStreamDOM from(InputStream in) { - return from(new KXml2Driver().createReader(in)); + return from(XStream2.getDefaultDriver().createReader(in)); } public static XStreamDOM from(Reader in) { - return from(new KXml2Driver().createReader(in)); + return from(XStream2.getDefaultDriver().createReader(in)); } public static XStreamDOM from(HierarchicalStreamReader in) { diff --git a/core/src/test/java/hudson/PluginManagerTest.java b/core/src/test/java/hudson/PluginManagerTest.java index 1e89e5c5c8..843e15ba1e 100644 --- a/core/src/test/java/hudson/PluginManagerTest.java +++ b/core/src/test/java/hudson/PluginManagerTest.java @@ -64,7 +64,7 @@ public class PluginManagerTest { @Issue("SECURITY-167") @Test public void parseInvalidRequestedPlugins() throws Exception { - String evilXML = "\n" + + String evilXML = "\n" + "]>\n" + "\n" + " \n" + diff --git a/core/src/test/java/hudson/util/XStream2EncodingTest.java b/core/src/test/java/hudson/util/XStream2EncodingTest.java index ea10414ebc..ea23797dea 100644 --- a/core/src/test/java/hudson/util/XStream2EncodingTest.java +++ b/core/src/test/java/hudson/util/XStream2EncodingTest.java @@ -70,7 +70,7 @@ public class XStream2EncodingTest { Thing t = (Thing) xs.fromXML(new ByteArrayInputStream(ambiguousXml)); assertThat(t.field, not(msg)); ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); - baos2.write("\n".getBytes("UTF-8")); + baos2.write("\n".getBytes("UTF-8")); baos2.write(ambiguousXml); t = (Thing) xs.fromXML(new ByteArrayInputStream(ambiguousXml)); assertThat(t.field, not(msg)); diff --git a/core/src/test/java/jenkins/model/RunIdMigratorTest.java b/core/src/test/java/jenkins/model/RunIdMigratorTest.java index fda491eda6..71a3e47832 100644 --- a/core/src/test/java/jenkins/model/RunIdMigratorTest.java +++ b/core/src/test/java/jenkins/model/RunIdMigratorTest.java @@ -83,13 +83,13 @@ public class RunIdMigratorTest { @Test public void legacy() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); - write("2014-01-02_03-04-05/build.xml", "\n\n ok\n 99\n ok\n"); + write("2014-01-02_03-04-05/build.xml", "\n\n ok\n 99\n ok\n"); link("99", "2014-01-02_03-04-05"); link("lastFailedBuild", "-1"); link("lastSuccessfulBuild", "99"); - assertEquals("{2014-01-02_03-04-05={build.xml='\n\n ok\n 99\n ok\n'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); + assertEquals("{2014-01-02_03-04-05={build.xml='\n\n ok\n 99\n ok\n'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); assertTrue(migrator.migrate(dir, null)); - assertEquals("{99={build.xml='\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize()); + assertEquals("{99={build.xml='\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize()); assertEquals(99, migrator.findNumber("2014-01-02_03-04-05")); migrator = new RunIdMigrator(); assertFalse(migrator.migrate(dir, null)); @@ -104,58 +104,58 @@ public class RunIdMigratorTest { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); write("2014-01-02_03-04-04/build.xml", "\n 98\n"); link("98", "2014-01-02_03-04-04"); - write("99/build.xml", "\n\n ok\n 1388649845000\n ok\n"); + write("99/build.xml", "\n\n ok\n 1388649845000\n ok\n"); link("lastFailedBuild", "-1"); link("lastSuccessfulBuild", "99"); - assertEquals("{2014-01-02_03-04-04={build.xml='\n 98\n'}, 98=→2014-01-02_03-04-04, 99={build.xml='\n\n ok\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); + assertEquals("{2014-01-02_03-04-04={build.xml='\n 98\n'}, 98=→2014-01-02_03-04-04, 99={build.xml='\n\n ok\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); assertTrue(migrator.migrate(dir, null)); - assertEquals("{98={build.xml='\n 2014-01-02_03-04-04\n 1388649844000\n'}, 99={build.xml='\n\n ok\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-04 98\n'}", summarize()); + assertEquals("{98={build.xml='\n 2014-01-02_03-04-04\n 1388649844000\n'}, 99={build.xml='\n\n ok\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-04 98\n'}", summarize()); } @Test public void reverseImmediately() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); File root = dir; dir = new File(dir, "jobs/somefolder/jobs/someproject/promotions/OK/builds"); - write("99/build.xml", "\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n"); + write("99/build.xml", "\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n"); link("lastFailedBuild", "-1"); link("lastSuccessfulBuild", "99"); write("legacyIds", "2014-01-02_03-04-05 99\n"); - assertEquals("{99={build.xml='\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize()); + assertEquals("{99={build.xml='\n\n ok\n 2014-01-02_03-04-05\n 1388649845000\n ok\n'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n'}", summarize()); RunIdMigrator.main(root.getAbsolutePath()); - assertEquals("{2014-01-02_03-04-05={build.xml='\n\n ok\n 99\n ok\n'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); + assertEquals("{2014-01-02_03-04-05={build.xml='\n\n ok\n 99\n ok\n'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", summarize()); } @Test public void reverseAfterNewBuilds() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); File root = dir; dir = new File(dir, "jobs/someproject/modules/test$test/builds"); - write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); + write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); write("legacyIds", ""); - assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); + assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); RunIdMigrator.main(root.getAbsolutePath()); - assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); + assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); } @Test public void reverseMatrixAfterNewBuilds() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); File root = dir; dir = new File(dir, "jobs/someproject/Environment=prod/builds"); - write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); + write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); write("legacyIds", ""); - assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); + assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); RunIdMigrator.main(root.getAbsolutePath()); - assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); + assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); } @Test public void reverseMavenAfterNewBuilds() throws Exception { assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); File root = dir; dir = new File(dir, "jobs/someproject/test$test/builds"); - write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); + write("1/build.xml", "\n\n ok\n 1388649845000\n ok\n"); write("legacyIds", ""); - assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); + assertEquals("{1={build.xml='\n\n ok\n 1388649845000\n ok\n'}, legacyIds=''}", summarize()); RunIdMigrator.main(root.getAbsolutePath()); - assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); + assertEquals("{1=→2014-01-02_03-04-05, 2014-01-02_03-04-05={build.xml='\n\n ok\n 1\n ok\n'}}", summarize()); } // TODO test sane recovery from various error conditions diff --git a/core/src/test/java/jenkins/util/xstream/XStreamDOMTest.java b/core/src/test/java/jenkins/util/xstream/XStreamDOMTest.java index 69a1fbe7ef..b32ed5a40f 100644 --- a/core/src/test/java/jenkins/util/xstream/XStreamDOMTest.java +++ b/core/src/test/java/jenkins/util/xstream/XStreamDOMTest.java @@ -23,7 +23,6 @@ */ package jenkins.util.xstream; -import com.thoughtworks.xstream.io.xml.KXml2Driver; import hudson.util.XStream2; import org.apache.commons.io.IOUtils; import org.junit.Before; @@ -54,7 +53,7 @@ public class XStreamDOMTest { @Before public void setUp() throws Exception { - xs = new XStream2(new KXml2Driver()); + xs = new XStream2(); xs.alias("foo", Foo.class); } diff --git a/core/src/test/java/jenkins/xml/XMLUtilsTest.java b/core/src/test/java/jenkins/xml/XMLUtilsTest.java index 5b998b1ca1..55b0d806bb 100644 --- a/core/src/test/java/jenkins/xml/XMLUtilsTest.java +++ b/core/src/test/java/jenkins/xml/XMLUtilsTest.java @@ -49,7 +49,7 @@ public class XMLUtilsTest { @Issue("SECURITY-167") @Test() public void testSafeTransformDoesNotProcessForeignResources() throws Exception { - final String xml = "\n" + + final String xml = "\n" + "\n" + "]>\n" + @@ -82,7 +82,7 @@ public class XMLUtilsTest { @Issue("SECURITY-167") @Test() public void testUpdateByXmlIDoesNotFail() throws Exception { - final String xml = "\n" + + final String xml = "\n" + "\n" + " \n" + " &\n" + @@ -119,7 +119,7 @@ public class XMLUtilsTest { @Test public void testParse_with_XXE() throws IOException, XPathExpressionException { try { - final String xml = "\n" + + final String xml = "\n" + "\n" + " ]> " + diff --git a/test/src/test/java/hudson/model/AbstractItemSecurityTest.java b/test/src/test/java/hudson/model/AbstractItemSecurityTest.java index 7ed8b79557..653097d295 100644 --- a/test/src/test/java/hudson/model/AbstractItemSecurityTest.java +++ b/test/src/test/java/hudson/model/AbstractItemSecurityTest.java @@ -47,7 +47,7 @@ public class AbstractItemSecurityTest { @Issue("SECURITY-167") @Test() public void testUpdateByXmlDoesNotProcessForeignResources() throws Exception { - final String xml = "\n" + + final String xml = "\n" + "\n" + "]>\n" + @@ -73,7 +73,7 @@ public class AbstractItemSecurityTest { @Issue("SECURITY-167") @Test() public void testUpdateByXmlDoesNotFail() throws Exception { - final String xml = "\n" + + final String xml = "\n" + "\n" + " &\n" + " \n" + diff --git a/test/src/test/java/hudson/model/ItemGroupMixInTest.java b/test/src/test/java/hudson/model/ItemGroupMixInTest.java index 0bf83e5970..79ea318b8b 100644 --- a/test/src/test/java/hudson/model/ItemGroupMixInTest.java +++ b/test/src/test/java/hudson/model/ItemGroupMixInTest.java @@ -185,7 +185,7 @@ public class ItemGroupMixInTest { @Test public void createProjectFromXMLShouldNoCreateEntities() throws IOException { - final String xml = "\n" + + final String xml = "\n" + "\n" + "]>\n" + -- GitLab From 24a1852fc2c9a74c59a25c3e0ed022e315894344 Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Thu, 11 Jan 2018 11:04:46 -0500 Subject: [PATCH 035/863] [JENKINS-48463] Added additional unit tests for verifying xml v1.1 compatibility - verify can still read xml 1.0 - verify can read xml 1.1 - verify can read xml 1.1 with 'special' characters - verify that jenkins can start with a config.xml having and special characters --- core/src/test/java/hudson/XmlFileTest.java | 84 +++++++++++++++++++ .../hudson/confg_1_1_with_special_chars.xml | 9 ++ core/src/test/resources/hudson/config_1_0.xml | 8 ++ core/src/test/resources/hudson/config_1_1.xml | 8 ++ pom.xml | 2 +- test/src/test/java/hudson/XMLFileTest.java | 23 +++++ .../config.xml | 9 ++ 7 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 core/src/test/java/hudson/XmlFileTest.java create mode 100644 core/src/test/resources/hudson/confg_1_1_with_special_chars.xml create mode 100644 core/src/test/resources/hudson/config_1_0.xml create mode 100644 core/src/test/resources/hudson/config_1_1.xml create mode 100644 test/src/test/java/hudson/XMLFileTest.java create mode 100644 test/src/test/resources/hudson/XMLFileTest/canStartWithXml_1_1_ConfigsTest/config.xml diff --git a/core/src/test/java/hudson/XmlFileTest.java b/core/src/test/java/hudson/XmlFileTest.java new file mode 100644 index 0000000000..0583e46e99 --- /dev/null +++ b/core/src/test/java/hudson/XmlFileTest.java @@ -0,0 +1,84 @@ +package hudson; + +import hudson.model.Node; +import hudson.util.XStream2; +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import jenkins.model.Jenkins; +import org.junit.Ignore; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class XmlFileTest { + + @Test + public void canReadXml1_0Test() throws IOException { + URL configUrl = getClass().getResource("/hudson/config_1_0.xml"); + File configFile = new File(configUrl.getFile()); + XStream2 xs = new XStream2(); + xs.alias("hudson", Jenkins.class); + + XmlFile xmlFile = new XmlFile(xs, configFile); + if (xmlFile.exists()) { + Node n = (Node) xmlFile.read(); + assertThat(n.getNumExecutors(), is(2)); + assertThat(n.getMode().toString(), is("NORMAL")); + } + } + + @Test + public void canReadXml1_1Test() throws IOException { + URL configUrl = getClass().getResource("/hudson/config_1_1.xml"); + File configFile = new File(configUrl.getFile()); + XStream2 xs = new XStream2(); + xs.alias("hudson", Jenkins.class); + + XmlFile xmlFile = new XmlFile(xs, configFile); + if (xmlFile.exists()) { + Node n = (Node) xmlFile.read(); + assertThat(n.getNumExecutors(), is(2)); + assertThat(n.getMode().toString(), is("NORMAL")); + } + } + + @Test + @Ignore + //TODO: find a way to complete this test + public void xml1_0ConfigMigrateTo1_1Test() throws IOException { + URL configUrl = getClass().getResource("/hudson/config_1_0.xml"); + File configFile = new File(configUrl.getFile()); + XStream2 xs = new XStream2(); + xs.alias("hudson", Jenkins.class); + + XmlFile xmlFile = new XmlFile(xs, configFile); + if (xmlFile.exists()) { + Node n = (Node) xmlFile.read(); + assertThat(n.getNumExecutors(), is(2)); + assertThat(n.getMode().toString(), is("NORMAL")); + // this fails, because configFile.getParent() returns null....how do i fix? + n.save(); + // Now verify that the node is showing tag + } + + } + + @Test + public void canReadXmlWithControlCharsTest() throws IOException { + URL configUrl = getClass().getResource("/hudson/confg_1_1_with_special_chars.xml"); + File configFile = new File(configUrl.getFile()); + XStream2 xs = new XStream2(); + xs.alias("hudson", Jenkins.class); + + XmlFile xmlFile = new XmlFile(xs, configFile); + if (xmlFile.exists()) { + Node n = (Node) xmlFile.read(); + assertThat(n.getNumExecutors(), is(2)); + assertThat(n.getMode().toString(), is("NORMAL")); + assertThat(n.getLabelString(), is("LESS_TERMCAP_mb=\u001B[01;31m")); + } + } +} diff --git a/core/src/test/resources/hudson/confg_1_1_with_special_chars.xml b/core/src/test/resources/hudson/confg_1_1_with_special_chars.xml new file mode 100644 index 0000000000..cb072b5ebb --- /dev/null +++ b/core/src/test/resources/hudson/confg_1_1_with_special_chars.xml @@ -0,0 +1,9 @@ + + + + 1.480.1 + 2 + NORMAL + + + \ No newline at end of file diff --git a/core/src/test/resources/hudson/config_1_0.xml b/core/src/test/resources/hudson/config_1_0.xml new file mode 100644 index 0000000000..c3a76a6eef --- /dev/null +++ b/core/src/test/resources/hudson/config_1_0.xml @@ -0,0 +1,8 @@ + + + + 1.480.1 + 2 + NORMAL + + \ No newline at end of file diff --git a/core/src/test/resources/hudson/config_1_1.xml b/core/src/test/resources/hudson/config_1_1.xml new file mode 100644 index 0000000000..c3fd8c0603 --- /dev/null +++ b/core/src/test/resources/hudson/config_1_1.xml @@ -0,0 +1,8 @@ + + + + 1.480.1 + 2 + NORMAL + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index c798586c2b..7b64d217d7 100644 --- a/pom.xml +++ b/pom.xml @@ -129,7 +129,7 @@ THE SOFTWARE. org.apache.ant ant - 1.8.4 + 1.9.2 diff --git a/test/src/test/java/hudson/XMLFileTest.java b/test/src/test/java/hudson/XMLFileTest.java new file mode 100644 index 0000000000..dde05c3423 --- /dev/null +++ b/test/src/test/java/hudson/XMLFileTest.java @@ -0,0 +1,23 @@ +package hudson; + +import org.junit.Rule; +import org.junit.Test; +import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.recipes.LocalData; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class XMLFileTest { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + @LocalData + public void canStartWithXml_1_1_ConfigsTest() { + + assertThat(j.jenkins.getLabelString(),is("LESS_TERMCAP_mb=\u001B[01;31m")); + + } +} diff --git a/test/src/test/resources/hudson/XMLFileTest/canStartWithXml_1_1_ConfigsTest/config.xml b/test/src/test/resources/hudson/XMLFileTest/canStartWithXml_1_1_ConfigsTest/config.xml new file mode 100644 index 0000000000..cb072b5ebb --- /dev/null +++ b/test/src/test/resources/hudson/XMLFileTest/canStartWithXml_1_1_ConfigsTest/config.xml @@ -0,0 +1,9 @@ + + + + 1.480.1 + 2 + NORMAL + + + \ No newline at end of file -- GitLab From b9c2148d6e53ef4c7f0f01fab591b0333b4c69f1 Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Fri, 12 Jan 2018 08:55:34 -0500 Subject: [PATCH 036/863] [JENKINS-48463] Added a unit test that validates jenkins xml v1.0 configs are silently migrated to xml v1.1 as they are persisted --- core/src/test/java/hudson/XmlFileTest.java | 24 +------------------ test/src/test/java/hudson/XMLFileTest.java | 22 +++++++++++++++++ .../silentlyMigrateConfigsTest/config.xml | 9 +++++++ 3 files changed, 32 insertions(+), 23 deletions(-) create mode 100644 test/src/test/resources/hudson/XMLFileTest/silentlyMigrateConfigsTest/config.xml diff --git a/core/src/test/java/hudson/XmlFileTest.java b/core/src/test/java/hudson/XmlFileTest.java index 0583e46e99..3493b4335f 100644 --- a/core/src/test/java/hudson/XmlFileTest.java +++ b/core/src/test/java/hudson/XmlFileTest.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.net.URL; import jenkins.model.Jenkins; -import org.junit.Ignore; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; @@ -44,28 +43,7 @@ public class XmlFileTest { assertThat(n.getMode().toString(), is("NORMAL")); } } - - @Test - @Ignore - //TODO: find a way to complete this test - public void xml1_0ConfigMigrateTo1_1Test() throws IOException { - URL configUrl = getClass().getResource("/hudson/config_1_0.xml"); - File configFile = new File(configUrl.getFile()); - XStream2 xs = new XStream2(); - xs.alias("hudson", Jenkins.class); - - XmlFile xmlFile = new XmlFile(xs, configFile); - if (xmlFile.exists()) { - Node n = (Node) xmlFile.read(); - assertThat(n.getNumExecutors(), is(2)); - assertThat(n.getMode().toString(), is("NORMAL")); - // this fails, because configFile.getParent() returns null....how do i fix? - n.save(); - // Now verify that the node is showing tag - } - - } - + @Test public void canReadXmlWithControlCharsTest() throws IOException { URL configUrl = getClass().getResource("/hudson/confg_1_1_with_special_chars.xml"); diff --git a/test/src/test/java/hudson/XMLFileTest.java b/test/src/test/java/hudson/XMLFileTest.java index dde05c3423..0fa2c8af08 100644 --- a/test/src/test/java/hudson/XMLFileTest.java +++ b/test/src/test/java/hudson/XMLFileTest.java @@ -1,5 +1,8 @@ package hudson; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -20,4 +23,23 @@ public class XMLFileTest { assertThat(j.jenkins.getLabelString(),is("LESS_TERMCAP_mb=\u001B[01;31m")); } + + /** + * + * This test validates that xml v1.0 configs silently get migrated to xml v1.1 when they are persisted + * + */ + @Test + @LocalData + public void silentlyMigrateConfigsTest() throws Exception { + j.jenkins.save(); + // verify that we did indeed load our test config.xml + assertThat(j.jenkins.getLabelString(), is("LESS_TERMCAP_mb=\u001B[01;31m")); + //verify that the persisted top level config.xml is v1.1 + File configFile = new File(j.jenkins.getRootPath().getRemote() + File.separator + "config.xml"); + assertThat(configFile.exists(), is(true)); + BufferedReader config = new BufferedReader(new FileReader(configFile)); + assertThat(config.readLine(), is("")); + config.close(); + } } diff --git a/test/src/test/resources/hudson/XMLFileTest/silentlyMigrateConfigsTest/config.xml b/test/src/test/resources/hudson/XMLFileTest/silentlyMigrateConfigsTest/config.xml new file mode 100644 index 0000000000..f26e502947 --- /dev/null +++ b/test/src/test/resources/hudson/XMLFileTest/silentlyMigrateConfigsTest/config.xml @@ -0,0 +1,9 @@ + + + + 1.480.1 + 2 + NORMAL + + + \ No newline at end of file -- GitLab From a0f419120b72b3e645f5beb7b5213ae01cc58364 Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Fri, 12 Jan 2018 10:16:57 -0500 Subject: [PATCH 037/863] [JENKINS-48463] It appears that the KXml2Driver is more tolerant of technically illegal xml (ie. it doesn't complain if an Xml v1.0 file contains special characters). Added a unit test that proves this, as well as fixing silentlyMigrateConfigsTest so that it starts with technically valid Xml 1.0 content --- core/src/test/java/hudson/XmlFileTest.java | 19 ++++++++++++++++++- .../hudson/config_1_0_with_special_chars.xml | 9 +++++++++ ....xml => config_1_1_with_special_chars.xml} | 0 test/src/test/java/hudson/XMLFileTest.java | 2 +- .../silentlyMigrateConfigsTest/config.xml | 2 +- 5 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 core/src/test/resources/hudson/config_1_0_with_special_chars.xml rename core/src/test/resources/hudson/{confg_1_1_with_special_chars.xml => config_1_1_with_special_chars.xml} (100%) diff --git a/core/src/test/java/hudson/XmlFileTest.java b/core/src/test/java/hudson/XmlFileTest.java index 3493b4335f..b34d7340b4 100644 --- a/core/src/test/java/hudson/XmlFileTest.java +++ b/core/src/test/java/hudson/XmlFileTest.java @@ -8,6 +8,8 @@ import java.net.URL; import jenkins.model.Jenkins; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.xml.sax.SAXParseException; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; @@ -29,6 +31,21 @@ public class XmlFileTest { } } + @Test(expected = SAXParseException.class) + public void xml1_0_withSpecialCharsShouldFail() throws IOException { + URL configUrl = getClass().getResource("/hudson/config_1_0_with_special_chars.xml"); + File configFile = new File(configUrl.getFile()); + XStream2 xs = new XStream2(); + xs.alias("hudson", Jenkins.class); + + XmlFile xmlFile = new XmlFile(xs, configFile); + if (xmlFile.exists()) { + Node n = (Node) xmlFile.read(); + assertThat(n.getNumExecutors(), is(2)); + assertThat(n.getMode().toString(), is("NORMAL")); + } + } + @Test public void canReadXml1_1Test() throws IOException { URL configUrl = getClass().getResource("/hudson/config_1_1.xml"); @@ -46,7 +63,7 @@ public class XmlFileTest { @Test public void canReadXmlWithControlCharsTest() throws IOException { - URL configUrl = getClass().getResource("/hudson/confg_1_1_with_special_chars.xml"); + URL configUrl = getClass().getResource("/hudson/config_1_1_with_special_chars.xml"); File configFile = new File(configUrl.getFile()); XStream2 xs = new XStream2(); xs.alias("hudson", Jenkins.class); diff --git a/core/src/test/resources/hudson/config_1_0_with_special_chars.xml b/core/src/test/resources/hudson/config_1_0_with_special_chars.xml new file mode 100644 index 0000000000..f26e502947 --- /dev/null +++ b/core/src/test/resources/hudson/config_1_0_with_special_chars.xml @@ -0,0 +1,9 @@ + + + + 1.480.1 + 2 + NORMAL + + + \ No newline at end of file diff --git a/core/src/test/resources/hudson/confg_1_1_with_special_chars.xml b/core/src/test/resources/hudson/config_1_1_with_special_chars.xml similarity index 100% rename from core/src/test/resources/hudson/confg_1_1_with_special_chars.xml rename to core/src/test/resources/hudson/config_1_1_with_special_chars.xml diff --git a/test/src/test/java/hudson/XMLFileTest.java b/test/src/test/java/hudson/XMLFileTest.java index 0fa2c8af08..b384ee7dfb 100644 --- a/test/src/test/java/hudson/XMLFileTest.java +++ b/test/src/test/java/hudson/XMLFileTest.java @@ -34,7 +34,7 @@ public class XMLFileTest { public void silentlyMigrateConfigsTest() throws Exception { j.jenkins.save(); // verify that we did indeed load our test config.xml - assertThat(j.jenkins.getLabelString(), is("LESS_TERMCAP_mb=\u001B[01;31m")); + assertThat(j.jenkins.getLabelString(), is("I am a label")); //verify that the persisted top level config.xml is v1.1 File configFile = new File(j.jenkins.getRootPath().getRemote() + File.separator + "config.xml"); assertThat(configFile.exists(), is(true)); diff --git a/test/src/test/resources/hudson/XMLFileTest/silentlyMigrateConfigsTest/config.xml b/test/src/test/resources/hudson/XMLFileTest/silentlyMigrateConfigsTest/config.xml index f26e502947..1cc5aa6799 100644 --- a/test/src/test/resources/hudson/XMLFileTest/silentlyMigrateConfigsTest/config.xml +++ b/test/src/test/resources/hudson/XMLFileTest/silentlyMigrateConfigsTest/config.xml @@ -4,6 +4,6 @@ 1.480.1 2 NORMAL - + \ No newline at end of file -- GitLab From 0431cc75fd2745335a8e06c75697890ef049df79 Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Fri, 12 Jan 2018 11:10:34 -0500 Subject: [PATCH 038/863] [JENKINS-48463] - Ignoring test xml1_0_withSpecialCharsShouldFail due to KXml2Driver being tolerant of control characters which should be illegal in an XML v1.0 content. This test should be revisted if we switch to another XML driver --- core/src/test/java/hudson/XmlFileTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/test/java/hudson/XmlFileTest.java b/core/src/test/java/hudson/XmlFileTest.java index b34d7340b4..f4c3c42e02 100644 --- a/core/src/test/java/hudson/XmlFileTest.java +++ b/core/src/test/java/hudson/XmlFileTest.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.net.URL; import jenkins.model.Jenkins; +import org.junit.Ignore; import org.junit.Test; import org.junit.rules.ExpectedException; import org.xml.sax.SAXParseException; @@ -31,6 +32,9 @@ public class XmlFileTest { } } + // KXml2Driver is able to parse XML 1.0 even if it has control characters which + // should be illegal. Ignoring this test until we switch to a more compliant driver + @Ignore @Test(expected = SAXParseException.class) public void xml1_0_withSpecialCharsShouldFail() throws IOException { URL configUrl = getClass().getResource("/hudson/config_1_0_with_special_chars.xml"); -- GitLab From 992100ac4223b3d5eab11e8dccae5e55c2a18484 Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Tue, 23 Jan 2018 13:13:13 -0500 Subject: [PATCH 039/863] [JENKINS-48463] - fixed reference to deprecated Xpp3Driver and replaced with new default driver --- core/src/main/java/hudson/model/View.java | 2 +- core/src/test/java/hudson/util/XStream2Test.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/hudson/model/View.java b/core/src/main/java/hudson/model/View.java index 3cb1a61d1f..721c87476a 100644 --- a/core/src/main/java/hudson/model/View.java +++ b/core/src/main/java/hudson/model/View.java @@ -1213,7 +1213,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(XStream2.getDefaultDriver().createReader(in), this, null, true); + Object o = Jenkins.XSTREAM2.unmarshal(XStream2.getDefaultDriver().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/test/java/hudson/util/XStream2Test.java b/core/src/test/java/hudson/util/XStream2Test.java index f5a038ae79..59192f02a4 100644 --- a/core/src/test/java/hudson/util/XStream2Test.java +++ b/core/src/test/java/hudson/util/XStream2Test.java @@ -29,7 +29,7 @@ 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.io.xml.KXml2Driver; import com.thoughtworks.xstream.security.ForbiddenClassException; import hudson.model.Result; import hudson.model.Run; @@ -426,7 +426,7 @@ public class XStream2Test { 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); + return Jenkins.XSTREAM2.unmarshal(XStream2.getDefaultDriver().createReader(new StringReader(xml)), root, null, true); } public static class WithDefaults { -- GitLab From eb41a7bb0a58474a0fc627ca1cb8f2002ad26b47 Mon Sep 17 00:00:00 2001 From: Alexander Akbashev Date: Wed, 22 Nov 2017 09:34:57 +0100 Subject: [PATCH 040/863] [JENKINS-48348] 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`. (cherry picked from commit 00ccd23f6441a55bfd625660911d4bc79c7578ba) --- 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 e0ea7a648ed6970897ea2c49bc662439ec4e4110 Mon Sep 17 00:00:00 2001 From: Alexander Akbashev Date: Thu, 16 Nov 2017 09:43:59 +0100 Subject: [PATCH 041/863] [JENKINS-48350] Cache estimated duration for execution In case of having 1000s of ongoing builds opening main pages can take some time if list of executors are opened. It happens because for every queury that comes from jelly we re-calculate the value from scratch. And calculation needs to load some builds from disk. The worst thing is that it happens for every user separately. (cherry picked from commit d7b120fea37b46b863c89acb638d04cdddc868b4) --- core/src/main/java/hudson/model/Executor.java | 74 ++++++------------- 1 file changed, 23 insertions(+), 51 deletions(-) diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java index 8cbf964a1a..e20f9a1eee 100644 --- a/core/src/main/java/hudson/model/Executor.java +++ b/core/src/main/java/hudson/model/Executor.java @@ -87,6 +87,7 @@ public class Executor extends Thread implements ModelObject { protected final @Nonnull Computer owner; private final Queue queue; private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private static final int DEFAULT_ESTIMATED_DURATION = -1; @GuardedBy("lock") private long startTime; @@ -105,6 +106,11 @@ public class Executor extends Thread implements ModelObject { @GuardedBy("lock") private Queue.Executable executable; + /** + * Calculation of estimated duration needs some time, so, it's better to cache it once executable is known + */ + private long executableEstimatedDuration = DEFAULT_ESTIMATED_DURATION; + /** * Used to mark that the execution is continuing asynchronously even though {@link Executor} as {@link Thread} * has finished. @@ -396,6 +402,8 @@ public class Executor extends Thread implements ModelObject { return; } + executableEstimatedDuration = executable.getEstimatedDuration(); + if (executable instanceof Actionable) { if (LOGGER.isLoggable(Level.FINER)) { LOGGER.log(FINER, "when running {0} from {1} we are copying {2} actions whereas the item currently has {3}", new Object[] {executable, workUnit.context.item, workUnit.context.actions, workUnit.context.item.getAllActions()}); @@ -451,6 +459,7 @@ public class Executor extends Thread implements ModelObject { if (asynchronousExecution == null) { finish2(); } + executableEstimatedDuration = DEFAULT_ESTIMATED_DURATION; } } @@ -686,18 +695,9 @@ public class Executor extends Thread implements ModelObject { */ @Exported public int getProgress() { - long d; - lock.readLock().lock(); - try { - if (executable == null) { - return -1; - } - d = executable.getEstimatedDuration(); - } finally { - lock.readLock().unlock(); - } + long d = executableEstimatedDuration; if (d <= 0) { - return -1; + return DEFAULT_ESTIMATED_DURATION; } int num = (int) (getElapsedTime() * 100 / d); @@ -716,19 +716,17 @@ public class Executor extends Thread implements ModelObject { */ @Exported public boolean isLikelyStuck() { - long d; - long elapsed; lock.readLock().lock(); try { if (executable == null) { return false; } - - elapsed = getElapsedTime(); - d = executable.getEstimatedDuration(); } finally { lock.readLock().unlock(); } + + long elapsed = getElapsedTime(); + long d = executableEstimatedDuration; if (d >= 0) { // if it's taking 10 times longer than ETA, consider it stuck return d * 10 < elapsed; @@ -776,17 +774,7 @@ public class Executor extends Thread implements ModelObject { * until the build completes. */ public String getEstimatedRemainingTime() { - long d; - lock.readLock().lock(); - try { - if (executable == null) { - return Messages.Executor_NotAvailable(); - } - - d = executable.getEstimatedDuration(); - } finally { - lock.readLock().unlock(); - } + long d = executableEstimatedDuration; if (d < 0) { return Messages.Executor_NotAvailable(); } @@ -804,24 +792,14 @@ public class Executor extends Thread implements ModelObject { * it as a number of milli-seconds. */ public long getEstimatedRemainingTimeMillis() { - long d; - lock.readLock().lock(); - try { - if (executable == null) { - return -1; - } - - d = executable.getEstimatedDuration(); - } finally { - lock.readLock().unlock(); - } + long d = executableEstimatedDuration; if (d < 0) { - return -1; + return DEFAULT_ESTIMATED_DURATION; } long eta = d - getElapsedTime(); if (eta <= 0) { - return -1; + return DEFAULT_ESTIMATED_DURATION; } return eta; @@ -906,16 +884,10 @@ public class Executor extends Thread implements ModelObject { * Returns when this executor started or should start being idle. */ public long getIdleStartMilliseconds() { - lock.readLock().lock(); - try { - if (isIdle()) - return Math.max(creationTime, owner.getConnectTime()); - else { - return Math.max(startTime + Math.max(0, executable == null ? -1 : executable.getEstimatedDuration()), - System.currentTimeMillis() + 15000); - } - } finally { - lock.readLock().unlock(); + if (isIdle()) + return Math.max(creationTime, owner.getConnectTime()); + else { + return Math.max(startTime + Math.max(0, executableEstimatedDuration), System.currentTimeMillis() + 15000); } } @@ -981,7 +953,7 @@ public class Executor extends Thread implements ModelObject { */ @Deprecated public static long getEstimatedDurationFor(Executable e) { - return e == null ? -1 : e.getEstimatedDuration(); + return e == null ? DEFAULT_ESTIMATED_DURATION : e.getEstimatedDuration(); } /** -- GitLab From bed6ccd71921c9321919f7a042864dbbbf63243a Mon Sep 17 00:00:00 2001 From: Daniel Beck Date: Sat, 16 Dec 2017 22:16:37 +0100 Subject: [PATCH 042/863] Merge pull request #3187 from daniel-beck/JENKINS-34254-v2 [JENKINS-34254] Fix RequirePOST form (cherry picked from commit 76c9f8beacc681663571c925b5ee090222407e34) --- core/pom.xml | 2 +- .../hudson/security/csrf/CrumbFilter.java | 14 +++++++ .../security/csrf/CrumbFilter/retry.jelly | 42 +++++++++++++++++++ .../csrf/CrumbFilter/retry.properties | 4 ++ .../security/csrf/DefaultCrumbIssuerTest.java | 17 ++++++++ 5 files changed, 78 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 b9345cd9b8..59eafcba6c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -39,7 +39,7 @@ THE SOFTWARE. true - 1.253 + 1.254 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..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,11 @@ 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; import java.io.IOException; import java.util.Enumeration; @@ -41,6 +46,15 @@ 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 { } 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..70e615b78e --- /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 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 655be64a1753a3077a81fc0c34573bca74dcf5a0 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 25 Jan 2018 12:21:08 -0500 Subject: [PATCH 043/863] [JENKINS-49147] Tolerate unusual CodeSource.location format from old versions of Tomcat. --- .../jenkins/security/ClassFilterImpl.java | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/jenkins/security/ClassFilterImpl.java b/core/src/main/java/jenkins/security/ClassFilterImpl.java index 2de71b01e3..0c554232b4 100644 --- a/core/src/main/java/jenkins/security/ClassFilterImpl.java +++ b/core/src/main/java/jenkins/security/ClassFilterImpl.java @@ -28,6 +28,7 @@ import com.google.common.collect.ImmutableSet; import hudson.ExtensionList; import hudson.Main; import hudson.remoting.ClassFilter; +import hudson.remoting.Which; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -49,6 +50,8 @@ import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; import jenkins.model.Jenkins; import jenkins.util.SystemProperties; import org.apache.commons.io.IOUtils; @@ -71,11 +74,14 @@ public class ClassFilterImpl extends ClassFilter { 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"); + private static final String JENKINS_LOC = codeSource(Jenkins.class); + private static final String REMOTING_LOC = codeSource(ClassFilter.class); + /** * Register this implementation as the default in the system. */ public static void register() { - if (Main.isUnitTest && Jenkins.class.getProtectionDomain().getCodeSource().getLocation() == null) { + if (Main.isUnitTest && JENKINS_LOC == null) { mockOff(); return; } @@ -146,10 +152,9 @@ public class ClassFilterImpl extends ClassFilter { 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; + String location = codeSource(c); if (location != null) { - if (isLocationWhitelisted(location.toString())) { + if (isLocationWhitelisted(location)) { LOGGER.log(Level.FINE, "permitting {0} due to its location in {1}", new Object[] {name, location}); return false; } @@ -176,11 +181,11 @@ public class ClassFilterImpl extends ClassFilter { 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())) { + if (loc.equals(JENKINS_LOC)) { 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())) { + if (loc.equals(REMOTING_LOC)) { LOGGER.log(Level.FINE, "{0} seems to be the location of Remoting, OK", loc); return true; } @@ -241,6 +246,34 @@ public class ClassFilterImpl extends ClassFilter { }); } + /** + * Tries to determine what JAR file a given class was loaded from. + * The location is an opaque string suitable only for comparison to others. + * Similar to {@link Which#jarFile(Class)} but potentially faster, and more tolerant of unknown URL formats. + * @param c some class + * @return something typically like {@code file:/…/plugins/structs/WEB-INF/lib/structs-1.10.jar}; + * or null for classes in the Java Platform, some generated classes, etc. + */ + private static @CheckForNull String codeSource(@Nonnull Class c) { + CodeSource cs = c.getProtectionDomain().getCodeSource(); + if (cs == null) { + return null; + } + URL loc = cs.getLocation(); + if (loc == null) { + return null; + } + String r = loc.toString(); + if (r.endsWith(".class")) { + // JENKINS-49147: Tomcat bug. Now do the more expensive check… + String suffix = c.getName().replace('.', '/') + ".class"; + if (r.endsWith(suffix)) { + r = r.substring(0, r.length() - suffix.length()); + } + } + return r; + } + 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) || -- GitLab From 193855d1fb82e980e93f0c9ce3c15c459d448fab Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 25 Jan 2018 17:05:37 -0500 Subject: [PATCH 044/863] [JENKINS-47142] Simplifying the logic of ArtifactArchiver.perform, making it friendlier to Pipeline. --- core/src/main/java/hudson/FilePath.java | 2 +- .../java/hudson/tasks/ArtifactArchiver.java | 44 +++++++------------ .../hudson/tasks/Messages.properties | 1 - .../hudson/tasks/Messages_bg.properties | 2 - .../hudson/tasks/Messages_da.properties | 1 - .../hudson/tasks/Messages_de.properties | 1 - .../hudson/tasks/Messages_es.properties | 1 - .../hudson/tasks/Messages_fr.properties | 1 - .../hudson/tasks/Messages_it.properties | 1 - .../hudson/tasks/Messages_ja.properties | 1 - .../hudson/tasks/Messages_nl.properties | 1 - .../hudson/tasks/Messages_pt_BR.properties | 1 - .../hudson/tasks/Messages_ru.properties | 1 - .../hudson/tasks/Messages_sr.properties | 1 - .../hudson/tasks/Messages_tr.properties | 1 - .../hudson/tasks/Messages_zh_TW.properties | 1 - 16 files changed, 18 insertions(+), 43 deletions(-) diff --git a/core/src/main/java/hudson/FilePath.java b/core/src/main/java/hudson/FilePath.java index b7b5b2d79d..cf05ba9a58 100644 --- a/core/src/main/java/hudson/FilePath.java +++ b/core/src/main/java/hudson/FilePath.java @@ -2438,7 +2438,7 @@ public final class FilePath implements Serializable { * @throws InterruptedException not only in case of a channel failure, but also if too many operations were performed without finding any matches * @since 1.484 */ - public String validateAntFileMask(final String fileMasks, final int bound, final boolean caseSensitive) throws IOException, InterruptedException { + public @CheckForNull String validateAntFileMask(final String fileMasks, final int bound, final boolean caseSensitive) throws IOException, InterruptedException { return act(new MasterToSlaveFileCallable() { private static final long serialVersionUID = 1; public String invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException { diff --git a/core/src/main/java/hudson/tasks/ArtifactArchiver.java b/core/src/main/java/hudson/tasks/ArtifactArchiver.java index d42a4a3298..32be646aa6 100644 --- a/core/src/main/java/hudson/tasks/ArtifactArchiver.java +++ b/core/src/main/java/hudson/tasks/ArtifactArchiver.java @@ -214,20 +214,10 @@ public class ArtifactArchiver extends Recorder implements SimpleBuildStep { this.caseSensitive = caseSensitive; } - private void listenerWarnOrError(TaskListener listener, String message) { - if (allowEmptyArchive) { - listener.getLogger().println(String.format("WARN: %s", message)); - } else { - listener.error(message); - } - } - @Override - public void perform(Run build, FilePath ws, Launcher launcher, TaskListener listener) throws InterruptedException, AbortException { + public void perform(Run build, FilePath ws, Launcher launcher, TaskListener listener) throws IOException, InterruptedException { if(artifacts.length()==0) { - listener.error(Messages.ArtifactArchiver_NoIncludes()); - build.setResult(Result.FAILURE); - return; + throw new AbortException(Messages.ArtifactArchiver_NoIncludes()); } Result result = build.getResult(); @@ -249,29 +239,29 @@ public class ArtifactArchiver extends Recorder implements SimpleBuildStep { } else { result = build.getResult(); if (result == null || result.isBetterOrEqualTo(Result.UNSTABLE)) { - // If the build failed, don't complain that there was no matching artifact. - // The build probably didn't even get to the point where it produces artifacts. - listenerWarnOrError(listener, Messages.ArtifactArchiver_NoMatchFound(artifacts)); - String msg = null; try { - msg = ws.validateAntFileMask(artifacts, FilePath.VALIDATE_ANT_FILE_MASK_BOUND, caseSensitive); + String msg = ws.validateAntFileMask(artifacts, FilePath.VALIDATE_ANT_FILE_MASK_BOUND, caseSensitive); + if (msg != null) { + listener.getLogger().println(msg); + } } catch (Exception e) { - listenerWarnOrError(listener, e.getMessage()); + Functions.printStackTrace(e, listener.getLogger()); } - if(msg!=null) - listenerWarnOrError(listener, msg); - } - if (!allowEmptyArchive) { - build.setResult(Result.FAILURE); + if (allowEmptyArchive) { + listener.getLogger().println(Messages.ArtifactArchiver_NoMatchFound(artifacts)); + } else { + throw new AbortException(Messages.ArtifactArchiver_NoMatchFound(artifacts)); + } + } else { + // If a freestyle build failed, do not complain that there was no matching artifact: + // the build probably did not even get to the point where it produces artifacts. + // For Pipeline, the program ought not be *trying* to archive anything after a failure, + // but anyway most likely result == null above so we would not be here. } } } catch (java.nio.file.AccessDeniedException e) { LOG.log(Level.FINE, "Diagnosing anticipated Exception", e); throw new AbortException(e.toString()); // Message is not enough as that is the filename only - } catch (IOException e) { - Util.displayIOException(e,listener); - Functions.printStackTrace(e, listener.error(Messages.ArtifactArchiver_FailedToArchive(artifacts))); - build.setResult(Result.FAILURE); } } diff --git a/core/src/main/resources/hudson/tasks/Messages.properties b/core/src/main/resources/hudson/tasks/Messages.properties index fa673df13c..6db0726a91 100644 --- a/core/src/main/resources/hudson/tasks/Messages.properties +++ b/core/src/main/resources/hudson/tasks/Messages.properties @@ -31,7 +31,6 @@ Ant.ProjectConfigNeeded= Maybe you need to configure the job to choose one of yo ArtifactArchiver.ARCHIVING_ARTIFACTS=Archiving artifacts ArtifactArchiver.DisplayName=Archive the artifacts ArtifactArchiver.SkipBecauseOnlyIfSuccessful=Skipped archiving because build is not successful -ArtifactArchiver.FailedToArchive=Failed to archive artifacts: {0} ArtifactArchiver.NoIncludes=\ No artifacts are configured for archiving.\n\ You probably forgot to set the file pattern, so please go back to the configuration and specify it.\n\ diff --git a/core/src/main/resources/hudson/tasks/Messages_bg.properties b/core/src/main/resources/hudson/tasks/Messages_bg.properties index 2cd51a9c5f..fb0c09d7cb 100644 --- a/core/src/main/resources/hudson/tasks/Messages_bg.properties +++ b/core/src/main/resources/hudson/tasks/Messages_bg.properties @@ -41,8 +41,6 @@ ArtifactArchiver.DisplayName=\ \u0410\u0440\u0445\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u0438\u0442\u0435 ArtifactArchiver.SkipBecauseOnlyIfSuccessful=\ \u041f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u043d\u0435 \u043d\u0430 \u0430\u0440\u0445\u0438\u0432\u0438\u0440\u0430\u043d\u0435\u0442\u043e, \u0437\u0430\u0449\u043e\u0442\u043e \u0438\u0437\u0433\u0440\u0430\u0436\u0434\u0430\u043d\u0435\u0442\u043e \u0435 \u043d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e. -ArtifactArchiver.FailedToArchive=\ - \u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0430\u0440\u0445\u0438\u0432\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u0438: {0} ArtifactArchiver.NoIncludes=\ \u041d\u0435 \u0441\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0438 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u0438 \u0437\u0430 \u0430\u0440\u0445\u0438\u0432\u0438\u0440\u0430\u043d\u0435.\n\ \u041f\u0440\u043e\u0431\u0432\u0430\u0439\u0442\u0435 \u0434\u0430 \u0443\u043a\u0430\u0436\u0435\u0442\u0435 \u0448\u0430\u0431\u043b\u043e\u043d \u0437\u0430 \u0438\u043c\u0435 \u043d\u0430 \u0444\u0430\u0439\u043b \u0432 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435.\n\ diff --git a/core/src/main/resources/hudson/tasks/Messages_da.properties b/core/src/main/resources/hudson/tasks/Messages_da.properties index 7e80357600..a0b4e57eb2 100644 --- a/core/src/main/resources/hudson/tasks/Messages_da.properties +++ b/core/src/main/resources/hudson/tasks/Messages_da.properties @@ -57,7 +57,6 @@ JavadocArchiver.NoMatchFound=Ingen Javadoc fundet i {0}: {1} Maven.NoExecutable=Kunne ikke finde en eksekverbar i {0} BuildTrigger.NoSuchProject=Intet s\u00e5kaldt projekt ''{0}''. Mente du ''{1}''? Fingerprinter.Action.DisplayName=Se Filfingeraftryk -ArtifactArchiver.FailedToArchive=Fejlede under arkivering af artifakter: {0} Maven.NotADirectory={0} er ikke et direktorie BuildTrigger.Disabled={0} er sl\u00e5et fra, starter ikke Fingerprinter.Recording=Opsamler filfingeraftryk diff --git a/core/src/main/resources/hudson/tasks/Messages_de.properties b/core/src/main/resources/hudson/tasks/Messages_de.properties index 9c2c5d095b..0b6522d91a 100644 --- a/core/src/main/resources/hudson/tasks/Messages_de.properties +++ b/core/src/main/resources/hudson/tasks/Messages_de.properties @@ -30,7 +30,6 @@ Ant.ProjectConfigNeeded=Eventuell m\u00FCssen Sie f\u00FCr das Projekt noch eine ArtifactArchiver.ARCHIVING_ARTIFACTS=Archiviere Artefakte ArtifactArchiver.DisplayName=Artefakte archivieren -ArtifactArchiver.FailedToArchive=Artefakte konnten nicht archiviert werden: {0} ArtifactArchiver.NoIncludes=Es sind keine Artefakte zur Archivierung konfiguriert.\n\u00DCberpr\u00FCfen Sie, ob in den Einstellungen ein Dateisuchmuster angegeben ist.\nWenn Sie alle Dateien archivieren m\u00F6chten, geben Sie ** an. ArtifactArchiver.NoMatchFound=Keine Artefakte gefunden, die mit dem Dateisuchmuster \u201E{0}\u201C \u00FCbereinstimmen. Ein Konfigurationsfehler? ArtifactArchiver.SkipBecauseOnlyIfSuccessful=Archivierung wird \u00FCbersprungen, da der Build nicht erfolgreich ist. diff --git a/core/src/main/resources/hudson/tasks/Messages_es.properties b/core/src/main/resources/hudson/tasks/Messages_es.properties index 1338b1660b..b5870ec434 100644 --- a/core/src/main/resources/hudson/tasks/Messages_es.properties +++ b/core/src/main/resources/hudson/tasks/Messages_es.properties @@ -30,7 +30,6 @@ Ant.ProjectConfigNeeded= Es posible que tengas que configurar la tarea para que ArtifactArchiver.ARCHIVING_ARTIFACTS=Guardando archivos ArtifactArchiver.DisplayName=Guardar los archivos generados -ArtifactArchiver.FailedToArchive=Error al guardar los archivos generados: {0} ArtifactArchiver.NoIncludes=No hay archivos configurados para guardar.\nEs probable que olvidaras configurar el patr\u00f3n.\nSi lo que quieres es guardar todos los ficheros del espacio de trabajo, utiliza "**" ArtifactArchiver.NoMatchFound=No se encontraron archivos que cumplan el patr\u00f3n "{0}". Comprueba la configuraci\u00f3n diff --git a/core/src/main/resources/hudson/tasks/Messages_fr.properties b/core/src/main/resources/hudson/tasks/Messages_fr.properties index beb41a5301..c06fb41131 100644 --- a/core/src/main/resources/hudson/tasks/Messages_fr.properties +++ b/core/src/main/resources/hudson/tasks/Messages_fr.properties @@ -29,7 +29,6 @@ Ant.NotAntDirectory={0} ne semble pas \u00eatre un r\u00e9pertoire Ant Ant.ProjectConfigNeeded=Avez-vous configur\u00e9 le job de fa\u00e7on \u00e0 choisir une de vos installations de Ant? ArtifactArchiver.DisplayName=Archiver des artefacts -ArtifactArchiver.FailedToArchive=Echec lors de l''archivage des artefacts: {0} ArtifactArchiver.NoIncludes=\ Aucun artefact n''est configur\u00e9 pour l''archivage.\n\ Vous avez probablement oubli\u00e9 de positionner le pattern pour les noms des fichiers; merci de retourner \u00e0 la configuration et de le sp\u00e9cifier.\n\ diff --git a/core/src/main/resources/hudson/tasks/Messages_it.properties b/core/src/main/resources/hudson/tasks/Messages_it.properties index df27728217..586c301611 100644 --- a/core/src/main/resources/hudson/tasks/Messages_it.properties +++ b/core/src/main/resources/hudson/tasks/Messages_it.properties @@ -35,7 +35,6 @@ Ant.ExecFailed=esecuzione del comando non riuscita. BuildTrigger.Triggering=Attivazione di una nuova compilazione di {0} InstallFromApache=Installa da Apache Ant.DisplayName=Invoca Ant -ArtifactArchiver.FailedToArchive=Archiviazione degli artefatti non riuscita: {0} CommandInterpreter.UnableToDelete=Impossibile eliminare il file script {0} BatchFile.DisplayName=Esegui comando batch Windows Fingerprinter.Recording=Registrazione impronte in corso diff --git a/core/src/main/resources/hudson/tasks/Messages_ja.properties b/core/src/main/resources/hudson/tasks/Messages_ja.properties index 8e98e612b6..8f44fddc91 100644 --- a/core/src/main/resources/hudson/tasks/Messages_ja.properties +++ b/core/src/main/resources/hudson/tasks/Messages_ja.properties @@ -30,7 +30,6 @@ Ant.ProjectConfigNeeded=\u3069\u306eAnt\u3092\u4f7f\u3046\u304b\u30d7\u30ed\u30b ArtifactArchiver.ARCHIVING_ARTIFACTS=\u6210\u679c\u7269\u3092\u4fdd\u5b58\u4e2d ArtifactArchiver.DisplayName=\u6210\u679c\u7269\u3092\u4fdd\u5b58 -ArtifactArchiver.FailedToArchive=\u6210\u679c\u7269\u306e\u4fdd\u5b58\u306e\u5931\u6557\u3057\u307e\u3057\u305f ArtifactArchiver.NoIncludes=\u4fdd\u5b58\u3059\u308b\u6210\u679c\u7269\u304c\u4f55\u3082\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\n\u6050\u3089\u304f\u30d5\u30a1\u30a4\u30eb\u30d1\u30bf\u30fc\u30f3\u306e\u6307\u5b9a\u3092\u5fd8\u308c\u305f\u306e\u3067\u3057\u3087\u3046\u3002\u8a2d\u5b9a\u30da\u30fc\u30b8\u306b\u623b\u3063\u3066\u30d1\u30bf\u30fc\u30f3\u3092\u6307\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044\n\u3082\u3057\u672c\u5f53\u306b\u30ef\u30fc\u30af\u30b9\u30da\u30fc\u30b9\u306e\u5168\u3066\u306e\u30d5\u30a1\u30a4\u30eb\u3092\u4fdd\u5b58\u3059\u308b\u3064\u3082\u308a\u306a\u3089\u3001"**"\u3068\u6307\u5b9a\u3057\u3066\u304f\u3060\u3055\u3044\u3002 ArtifactArchiver.NoMatchFound=\u6307\u5b9a\u3055\u308c\u305f\u30d5\u30a1\u30a4\u30eb\u30d1\u30bf\u30fc\u30f3\u300c{0}\u300d\u306b\u5408\u81f4\u3059\u308b\u30d5\u30a1\u30a4\u30eb\u304c\u3042\u308a\u307e\u305b\u3093\u3002\u8a2d\u5b9a\u30df\u30b9\uff1f diff --git a/core/src/main/resources/hudson/tasks/Messages_nl.properties b/core/src/main/resources/hudson/tasks/Messages_nl.properties index 5ff3634cb8..7f05296625 100644 --- a/core/src/main/resources/hudson/tasks/Messages_nl.properties +++ b/core/src/main/resources/hudson/tasks/Messages_nl.properties @@ -28,7 +28,6 @@ Ant.NotAntDirectory={0} is geen Ant folder Ant.ProjectConfigNeeded= Misschien dient u uw job te configureren om \u00e9\u00e9n van je Ant installaties te gebruiken? ArtifactArchiver.DisplayName=Archiveer de artefacten -ArtifactArchiver.FailedToArchive=Fout bij het archiveren van de artefacten: {0} ArtifactArchiver.NoIncludes=Er werden geen artefacten voor archivering geconfigureerd.\nWaarschijnlijk werd geen bestands-selectiepatroon geconfigureerd. Gelieve dit te configureren.\n Indien je alle bestanden in de werkplaats wenst te archiveren, gelieve dan "**" als patroon te configureren. ArtifactArchiver.NoMatchFound=Er werden geen artefacten gevonden die voldoen aan het bestands-selectiepatroon "{0}". Misschien dient U uw configuratie aan te passen? diff --git a/core/src/main/resources/hudson/tasks/Messages_pt_BR.properties b/core/src/main/resources/hudson/tasks/Messages_pt_BR.properties index 654a516574..bd20ca67af 100644 --- a/core/src/main/resources/hudson/tasks/Messages_pt_BR.properties +++ b/core/src/main/resources/hudson/tasks/Messages_pt_BR.properties @@ -28,7 +28,6 @@ Ant.NotAntDirectory={0} n\u00e3o parece ser um diret\u00f3rio Ant Ant.ProjectConfigNeeded= \u00c9 necess\u00e1rio configurar a job para escolher uma de suas instala\u00e7\u00f5es do Ant. ArtifactArchiver.DisplayName=Arquivar os artefatos -ArtifactArchiver.FailedToArchive=Falha ao arquivar os artefatos: {0} ArtifactArchiver.NoIncludes=Nenhum artefato est\u00e1 configurado para arquivamento.\n \u00c9 necess\u00e1rio informar o padr\u00e3o de arquivo, volte para a configura\u00e7\u00e3o e especifique-o.\nSe necessitar arquivar todos os arquivos do workspace, por favor especifique "**" ArtifactArchiver.NoMatchFound=Nenhum artefato encontrado casa com o padr\u00e3o de arquivo "{0}". Erro de configura\u00e7\u00e3o? diff --git a/core/src/main/resources/hudson/tasks/Messages_ru.properties b/core/src/main/resources/hudson/tasks/Messages_ru.properties index 81eb29449f..54d6a3de8a 100644 --- a/core/src/main/resources/hudson/tasks/Messages_ru.properties +++ b/core/src/main/resources/hudson/tasks/Messages_ru.properties @@ -28,7 +28,6 @@ Ant.NotAntDirectory={0} \u043d\u0435 \u043f\u043e\u0445\u043e\u0436\u0430 \u043d Ant.ProjectConfigNeeded= \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u0432\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435, \u0433\u0434\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432\u0430\u0448\u0430 \u0438\u043d\u0441\u0442\u0430\u043b\u043b\u044f\u0446\u0438\u044f Ant? ArtifactArchiver.DisplayName=\u0417\u0430\u0430\u0440\u0445\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u044b -ArtifactArchiver.FailedToArchive=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0430\u0440\u0445\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u044b\: {0} ArtifactArchiver.NoIncludes=\ \u041d\u0435\u0442 \u0430\u0440\u0442\u0435\u0444\u0430\u043a\u0442\u043e\u0432 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u0434\u043b\u044f \u0430\u0440\u0445\u0438\u0432\u0430\u0446\u0438\u0438.\n\ \u041f\u043e\u0445\u043e\u0436\u0435, \u0432\u044b \u0437\u0430\u0431\u044b\u043b\u0438 \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0448\u0430\u0431\u043b\u043e\u043d \u0438\u043c\u0435\u043d\u0438 \u0444\u0430\u0439\u043b\u0430, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0435\u0440\u043d\u0438\u0442\u0435\u0441\u044c \u043d\u0430 \u044d\u043a\u0440\u0430\u043d \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0438 \u0443\u043a\u0430\u0436\u0438\u0442\u0435 \u0435\u0433\u043e.\n\ diff --git a/core/src/main/resources/hudson/tasks/Messages_sr.properties b/core/src/main/resources/hudson/tasks/Messages_sr.properties index e2ff5d5fca..ad91898542 100644 --- a/core/src/main/resources/hudson/tasks/Messages_sr.properties +++ b/core/src/main/resources/hudson/tasks/Messages_sr.properties @@ -10,7 +10,6 @@ Ant.ProjectConfigNeeded=\u041F\u043E\u043A\u0443\u0448\u0430\u0458\u0442\u0435 \ ArtifactArchiver.ARCHIVING_ARTIFACTS=\u0410\u0440\u0445\u0438\u0432\u0438\u0440\u0430\u045A\u0435 \u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u0438 ArtifactArchiver.DisplayName=\u0410\u0440\u0445\u0438\u0432\u0438\u0440\u0430\u0458 \u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u0438 ArtifactArchiver.SkipBecauseOnlyIfSuccessful=\u041F\u0440\u0435\u0441\u043A\u043E\u045B\u0430\u0432\u0430 \u0430\u0440\u0445\u0438\u0432\u0438\u0440\u0430\u045A\u0435 \u0437\u0430\u0448\u0442\u043E \u0458\u0435 \u0438\u0437\u0433\u0440\u0430\u0434\u045A\u0430 \u043D\u0435\u0443\u0441\u043F\u0435\u0448\u043D\u0430 -ArtifactArchiver.FailedToArchive=\u041D\u0435\u0443\u0441\u043F\u0440\u0435\u0448\u043D\u043E \u0430\u0440\u0445\u0438\u0432\u0438\u0440\u0430\u045A\u0435 \u0430\u0440\u0442\u0435\u0444\u0430\u043A\u0442\u0438: {0} ArtifactArchiver.NoIncludes=\u041D\u0435\u043C\u0430 \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0445 \u043F\u0440\u0435\u0434\u043C\u0435\u0442\u0430 \u0437\u0430 \u0430\u0440\u0445\u0438\u0432\u0438\u0440\u0430\u045A\u0435.\n\ \u0418\u0437\u0433\u043B\u0435\u0434\u0430 \u0434\u0430 \u043D\u0438\u0441\u0442\u0435 \u043D\u0430\u0432\u0435\u043B\u0438 \u0448\u0430\u0431\u043B\u043E\u043D \u0438\u043C\u0435\u043D\u0430 \u0434\u0430\u0442\u043E\u0442\u0435\u043A\u0435. \u041C\u043E\u043B\u0438\u043C\u043E \u0432\u0430\u0441, \u0432\u0440\u0430\u0442\u0438\u0442\u0435 \u0441\u0435 \u043D\u0430 \u0442\u0430\u0458 \u0435\u043A\u0440\u0430\u043D \u0438 \u0443\u043D\u0435\u0441\u0438\u0442\u0435 \u0433\u0430.\n\ \u0410\u043A\u043E \u0437\u0430\u0438\u0441\u0442\u0430 \u0436\u0435\u043B\u0438\u0442\u0435 \u0434\u0430 \u0430\u0440\u0445\u0438\u0432\u0438\u0440\u0430 \u0441\u0432\u0435 \u0434\u0430\u0442\u043E\u0442\u0435\u043A\u0435 \u0443 \u0440\u0430\u0434\u043D\u043E\u043C \u043F\u0440\u043E\u0441\u0442\u043E\u0440\u0443, \u0443\u043D\u0435\u0441\u0438\u0442\u0435 "**" diff --git a/core/src/main/resources/hudson/tasks/Messages_tr.properties b/core/src/main/resources/hudson/tasks/Messages_tr.properties index c7d5f9437d..60abb6fde8 100644 --- a/core/src/main/resources/hudson/tasks/Messages_tr.properties +++ b/core/src/main/resources/hudson/tasks/Messages_tr.properties @@ -28,7 +28,6 @@ Ant.NotAntDirectory={0}, bir Ant dizinine benzemiyor Ant.ProjectConfigNeeded= \u00c7al\u0131\u015ft\u0131rd\u0131\u011f\u0131n\u0131z i\u015f i\u00e7in bir Ant kurulumu se\u00e7meniz gerekiyor olabilir? ArtifactArchiver.DisplayName=Artefaktlar\u0131 Ar\u015fivle -ArtifactArchiver.FailedToArchive=Artefakt ar\u015fivleme ba\u015far\u0131s\u0131z oldu\: {0} ArtifactArchiver.NoIncludes=\ Herhangi bir artefakt, ar\u015fivleme i\u00e7in ayarlanmad\u0131.\n\ Konfig\u00fcrasyon k\u0131sm\u0131nda File Pattern ayarlar\u0131n\u0131 kontrol edin.\n\ diff --git a/core/src/main/resources/hudson/tasks/Messages_zh_TW.properties b/core/src/main/resources/hudson/tasks/Messages_zh_TW.properties index aeef7ecace..74e72413e3 100644 --- a/core/src/main/resources/hudson/tasks/Messages_zh_TW.properties +++ b/core/src/main/resources/hudson/tasks/Messages_zh_TW.properties @@ -30,7 +30,6 @@ Ant.ProjectConfigNeeded=\u4e5f\u8a31\u60a8\u61c9\u8a72\u8a2d\u5b9a\u5c08\u6848\u ArtifactArchiver.ARCHIVING_ARTIFACTS=\u5c01\u5b58\u6210\u54c1 ArtifactArchiver.DisplayName=\u5c01\u5b58\u6210\u54c1 -ArtifactArchiver.FailedToArchive=\u7121\u6cd5\u5c01\u5b58\u6210\u54c1: {0} ArtifactArchiver.NoIncludes=\ \u6c92\u8981\u6210\u54c1\u88ab\u8a2d\u70ba\u9700\u8981\u5c01\u5b58\u3002\n\ \u53ef\u80fd\u662f\u60a8\u5fd8\u4e86\u8a2d\u5b9a\u6a94\u6848\u6a23\u5f0f\uff0c\u8acb\u56de\u5230\u8a2d\u5b9a\u9801\u6aa2\u67e5\u770b\u770b\u3002\n\ -- GitLab From 18efa58abf2ef977c1ad168c5f245606d5385193 Mon Sep 17 00:00:00 2001 From: Manuel Recena Date: Thu, 25 Jan 2018 23:41:25 +0100 Subject: [PATCH 045/863] [JENKINS-49129] Manage Jenkins dropdown menu no longer works --- core/src/main/resources/jenkins/model/Jenkins/manage.jelly | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/resources/jenkins/model/Jenkins/manage.jelly b/core/src/main/resources/jenkins/model/Jenkins/manage.jelly index 08393a484c..5fa406c953 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/manage.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/manage.jelly @@ -53,10 +53,11 @@ THE SOFTWARE.
    + + ${taskTags!=null and attrs.contextMenu!='false' ? taskTags.add(m.urlName, iconUrl, m.displayName, m.requiresPOST, m.requiresConfirmation) : null} -
    ${m.displayName}
    @@ -67,7 +68,6 @@ THE SOFTWARE. - manage-option
    ${m.displayName}
    @@ -78,7 +78,6 @@ THE SOFTWARE. - manage-option
    ${m.displayName}
    -- GitLab From 6a3e60ef71900704e552a295a802c2caf29a871c Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Thu, 25 Jan 2018 17:48:02 -0500 Subject: [PATCH 046/863] [JENKINS-46041] If guessBrowser fails, return null and move on. --- core/src/main/java/hudson/scm/SCM.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/scm/SCM.java b/core/src/main/java/hudson/scm/SCM.java index c33810a7f8..2b1e94bb11 100644 --- a/core/src/main/java/hudson/scm/SCM.java +++ b/core/src/main/java/hudson/scm/SCM.java @@ -54,6 +54,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -86,6 +88,8 @@ import org.kohsuke.stapler.export.ExportedBean; @ExportedBean public abstract class SCM implements Describable, ExtensionPoint { + private static final Logger LOGGER = Logger.getLogger(SCM.class.getName()); + /** JENKINS-35098: discouraged */ @SuppressWarnings("FieldMayBeFinal") private static boolean useAutoBrowserHolder = SystemProperties.getBoolean(SCM.class.getName() + ".useAutoBrowserHolder"); @@ -143,7 +147,12 @@ public abstract class SCM implements Describable, ExtensionPoint { } return autoBrowserHolder.get(); } else { - return guessBrowser(); + try { + return guessBrowser(); + } catch (RuntimeException x) { + LOGGER.log(Level.WARNING, null, x); + return null; + } } } -- GitLab From 197cf74f802a04d4ee7f2afaa80e189e7824ae66 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Fri, 26 Jan 2018 14:55:01 +0100 Subject: [PATCH 047/863] [JENKINS-32442] Disable API Tokens when user is created - admin monitor + configuration - preparation for rewrite API Token --- .../jenkins/security/ApiTokenProperty.java | 61 ++++++++++++ .../ApiTokenPropertyConfiguration.java | 93 +++++++++++++++++++ ...yDisabledDefaultAdministrativeMonitor.java | 66 +++++++++++++ .../config.jelly | 33 +++++++ .../help-enabled.html | 4 + .../message.jelly | 33 +++++++ .../message.properties | 2 + .../jenkins/security/Messages.properties | 1 + 8 files changed, 293 insertions(+) create mode 100644 core/src/main/java/jenkins/security/ApiTokenPropertyConfiguration.java create mode 100644 core/src/main/java/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor.java create mode 100644 core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/config.jelly create mode 100644 core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-enabled.html create mode 100644 core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly create mode 100644 core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.properties diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java index dd64d0e9d0..10b24c0c94 100644 --- a/core/src/main/java/jenkins/security/ApiTokenProperty.java +++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java @@ -46,11 +46,13 @@ import java.io.IOException; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.SecureRandom; +import java.util.Date; import javax.annotation.Nonnull; import org.apache.commons.lang.StringUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.interceptor.RequirePOST; +import org.mindrot.jbcrypt.BCrypt; /** * Remembers the API token for this user, that can be used like a password to login. @@ -72,6 +74,12 @@ public class ApiTokenProperty extends UserProperty { private static final boolean SHOW_TOKEN_TO_ADMINS = SystemProperties.getBoolean(ApiTokenProperty.class.getName() + ".showTokenToAdmins"); + /** + * Determine the (log of) number of rounds we need to apply when hashing the token + * default value corresponds to BCrypt#GENSALT_DEFAULT_LOG2_ROUNDS + */ + private static final int BCRYPT_LOG_ROUND = + SystemProperties.getInteger(ApiTokenProperty.class.getName() + ".bcryptLogRound", 10); @DataBoundConstructor public ApiTokenProperty() { @@ -168,6 +176,12 @@ public class ApiTokenProperty extends UserProperty { } /** + * New approach: + * API Token are generated only when a user request a new one. The value is randomly generated + * without any link to the user and only displayed to him the first time. + * We only store the hash for future comparisons. + * + * Legacy approach: * When we are creating a default {@link ApiTokenProperty} for User, * we need to make sure it yields the same value for the same user, * because there's no guarantee that the property is saved. @@ -176,6 +190,11 @@ public class ApiTokenProperty extends UserProperty { * the initial API token value. So we take the seed by hashing the secret + user ID. */ public ApiTokenProperty newInstance(User user) { + if(ApiTokenPropertyConfiguration.get().isTokenGenerationOnCreationDisabled()){ + // recommended way + return null; + } + return new ApiTokenProperty(API_KEY_SEED.mac(user.getId())); } @@ -201,4 +220,46 @@ public class ApiTokenProperty extends UserProperty { * We don't want an API key that's too long, so cut the length to 16 (which produces 32-letter MAC code in hexdump) */ private static final HMACConfidentialKey API_KEY_SEED = new HMACConfidentialKey(ApiTokenProperty.class,"seed",16); + + private class HashedToken { + private String name; + // serve as an optimizer to avoid hashing all the token + private String prefix; + private String hash; + private Date creationDate; + + private Date lastUseDate; + private Integer useCounter; + + private boolean revoked; + private Date revokationDate; + private String revokedBy; + + public HashedToken(String tokenValue){ + this.creationDate = new Date(); + this.hash = BCrypt.hashpw(tokenValue, BCrypt.gensalt(BCRYPT_LOG_ROUND)); + } + + public synchronized void incrementUse(){ + if(revoked){ + throw new RuntimeException("A revoked token cannot be used"); + } + this.useCounter = useCounter == null ? 1 : useCounter + 1; + this.lastUseDate = new Date(); + } + + public void setName(String name){ + this.name = name; + } + + public synchronized void revoke(){ + if(revoked){ + throw new RuntimeException("The token is already revoked"); + } + + this.revoked = true; + this.revokationDate = new Date(); + this.revokedBy = Jenkins.getAuthentication().getName(); + } + } } diff --git a/core/src/main/java/jenkins/security/ApiTokenPropertyConfiguration.java b/core/src/main/java/jenkins/security/ApiTokenPropertyConfiguration.java new file mode 100644 index 0000000000..b1a1437c05 --- /dev/null +++ b/core/src/main/java/jenkins/security/ApiTokenPropertyConfiguration.java @@ -0,0 +1,93 @@ +/* + * 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.security; + +import hudson.Extension; +import hudson.Util; +import hudson.model.AdministrativeMonitor; +import hudson.model.Descriptor.FormException; +import hudson.model.User; +import hudson.model.UserProperty; +import hudson.model.UserPropertyDescriptor; +import hudson.security.ACL; +import hudson.util.HttpResponses; +import hudson.util.Secret; +import jenkins.model.GlobalConfiguration; +import jenkins.model.GlobalConfigurationCategory; +import jenkins.model.Jenkins; +import jenkins.util.SystemProperties; +import net.sf.json.JSONObject; +import org.apache.commons.lang.StringUtils; +import org.jenkinsci.Symbol; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.AncestorInPath; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.kohsuke.stapler.interceptor.RequirePOST; +import org.mindrot.jbcrypt.BCrypt; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.Date; + +/** + * Configuration for the new token generation when a user is created + * + * @since TODO + */ +@Extension +@Symbol("apiToken") +@Restricted(NoExternalUse.class) +public class ApiTokenPropertyConfiguration extends GlobalConfiguration { + private boolean tokenGenerationOnCreationDisabled = false; + + public static ApiTokenPropertyConfiguration get() { + return Jenkins.get().getInjector().getInstance(ApiTokenPropertyConfiguration.class); + } + + public ApiTokenPropertyConfiguration() { + load(); + } + + public boolean isTokenGenerationOnCreationDisabled() { + return tokenGenerationOnCreationDisabled; + } + + public void setTokenGenerationOnCreationDisabled(boolean tokenGenerationOnCreationDisabled) { + this.tokenGenerationOnCreationDisabled = tokenGenerationOnCreationDisabled; + save(); + } + + @Override + public GlobalConfigurationCategory getCategory() { + return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class); + } +} diff --git a/core/src/main/java/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor.java b/core/src/main/java/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor.java new file mode 100644 index 0000000000..9e8b2f42b5 --- /dev/null +++ b/core/src/main/java/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor.java @@ -0,0 +1,66 @@ +/* + * 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.security; + +import hudson.Extension; +import hudson.model.AdministrativeMonitor; +import hudson.util.HttpResponses; +import org.jenkinsci.Symbol; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; + +import java.io.IOException; + +/** + * Monitor that the API Token are not generated by default without the user interaction. + * + * @since TODO + */ +@Extension +@Symbol("apiToken") +@Restricted(NoExternalUse.class) +public class ApiTokenPropertyDisabledDefaultAdministrativeMonitor extends AdministrativeMonitor { + @Override + public String getDisplayName() { + return Messages.ApiTokenPropertyDisabledDefaultAdministrativeMonitor_displayName(); + } + + @Override + public boolean isActivated() { + return !ApiTokenPropertyConfiguration.get().isTokenGenerationOnCreationDisabled(); + } + + @RequirePOST + public HttpResponse doAct(@QueryParameter String no) throws IOException { + if (no == null) { + ApiTokenPropertyConfiguration.get().setTokenGenerationOnCreationDisabled(true); + } else { + disable(true); + } + return HttpResponses.redirectViaContextPath("manage"); + } +} diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/config.jelly b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/config.jelly new file mode 100644 index 0000000000..985a5e77b8 --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/config.jelly @@ -0,0 +1,33 @@ + + + + + + + + + + + diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-enabled.html b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-enabled.html new file mode 100644 index 0000000000..c0cc87bbed --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-enabled.html @@ -0,0 +1,4 @@ +
    + Even if this is disabled (recommended for security reasons), the users are still able to generate new tokens + themselves on their configure page. +
    diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly new file mode 100644 index 0000000000..4a310bedb3 --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly @@ -0,0 +1,33 @@ + + + +
    +
    + + + ${%warningMessage(rootURL)} + +
    +
    diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.properties b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.properties new file mode 100644 index 0000000000..5638ccb00e --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.properties @@ -0,0 +1,2 @@ +warningMessage=The API Token are currently generated directly when a new user is created. The recommended way is to \ + disable this behavior and let users request new tokens themselves. diff --git a/core/src/main/resources/jenkins/security/Messages.properties b/core/src/main/resources/jenkins/security/Messages.properties index f0a98fb4a2..fffe3bfe6c 100644 --- a/core/src/main/resources/jenkins/security/Messages.properties +++ b/core/src/main/resources/jenkins/security/Messages.properties @@ -24,5 +24,6 @@ ApiTokenProperty.DisplayName=API Token ApiTokenProperty.ChangeToken.TokenIsHidden=Token is hidden ApiTokenProperty.ChangeToken.Success=
    Updated. See the new token in the field above
    ApiTokenProperty.ChangeToken.SuccessHidden=
    Updated. You need to login as the user to see the token
    +ApiTokenPropertyDisabledDefaultAdministrativeMonitor.displayName=API Token not generated by default RekeySecretAdminMonitor.DisplayName=Re-keying UpdateSiteWarningsMonitor.DisplayName=Update Site Warnings -- GitLab From ef52f07eeac7153856d540cb244a34f96cf28bcb Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Fri, 26 Jan 2018 14:56:24 +0100 Subject: [PATCH 048/863] [JENKINS-32776] API Tokens not more stored in a recoverable format - rewrite the groovy view into jelly one - remove old config_xx.properties - add capabilities on token: list/generate/revoke - stored using BCrypt hashes - migration from Legacy API Token --- .../jenkins/security/ApiTokenProperty.java | 347 +++++++++----- .../java/jenkins/security/ApiTokenStore.java | 448 ++++++++++++++++++ .../security/ApiTokenProperty/config.groovy | 36 -- .../security/ApiTokenProperty/config.jelly | 91 ++++ .../ApiTokenProperty/config.properties | 9 + .../ApiTokenProperty/config_it.properties | 5 - .../ApiTokenProperty/config_ja.properties | 25 - .../ApiTokenProperty/config_sr.properties | 5 - .../ApiTokenProperty/config_zh_TW.properties | 25 - .../ApiTokenProperty/help-apiToken.html | 15 +- .../security/ApiTokenProperty/resources.css | 83 ++++ .../security/ApiTokenProperty/resources.js | 83 ++++ .../jenkins/security/Messages.properties | 2 + .../test/java/hudson/cli/CLIActionTest.java | 2 + .../security/ApiTokenPropertyTest.java | 23 +- .../security/BasicHeaderProcessorTest.java | 3 + 16 files changed, 979 insertions(+), 223 deletions(-) create mode 100644 core/src/main/java/jenkins/security/ApiTokenStore.java delete mode 100644 core/src/main/resources/jenkins/security/ApiTokenProperty/config.groovy create mode 100644 core/src/main/resources/jenkins/security/ApiTokenProperty/config.jelly create mode 100644 core/src/main/resources/jenkins/security/ApiTokenProperty/config.properties delete mode 100644 core/src/main/resources/jenkins/security/ApiTokenProperty/config_it.properties delete mode 100644 core/src/main/resources/jenkins/security/ApiTokenProperty/config_ja.properties delete mode 100644 core/src/main/resources/jenkins/security/ApiTokenProperty/config_sr.properties delete mode 100644 core/src/main/resources/jenkins/security/ApiTokenProperty/config_zh_TW.properties create mode 100644 core/src/main/resources/jenkins/security/ApiTokenProperty/resources.css create mode 100644 core/src/main/resources/jenkins/security/ApiTokenProperty/resources.js diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java index 10b24c0c94..c11069c638 100644 --- a/core/src/main/java/jenkins/security/ApiTokenProperty.java +++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java @@ -23,9 +23,11 @@ */ package jenkins.security; +import com.thoughtworks.xstream.converters.UnmarshallingContext; import hudson.Extension; +import hudson.diagnosis.OldDataMonitor; +import hudson.util.XStream2; import jenkins.util.SystemProperties; -import hudson.Util; import hudson.model.Descriptor.FormException; import hudson.model.User; import hudson.model.UserProperty; @@ -34,25 +36,30 @@ import hudson.security.ACL; import hudson.util.HttpResponses; import hudson.util.Secret; import jenkins.model.Jenkins; +import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.jenkinsci.Symbol; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import java.io.IOException; -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.Date; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; + import org.apache.commons.lang.StringUtils; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.stapler.interceptor.RequirePOST; -import org.mindrot.jbcrypt.BCrypt; /** * Remembers the API token for this user, that can be used like a password to login. @@ -63,113 +70,182 @@ import org.mindrot.jbcrypt.BCrypt; * @since 1.426 */ public class ApiTokenProperty extends UserProperty { - private volatile Secret apiToken; - + private static final Logger LOGGER = Logger.getLogger(ApiTokenProperty.class.getName()); + + private transient volatile Secret apiToken; + private ApiTokenStore tokenStore; + /** - * If enabled, shows API tokens to users with {@link Jenkins#ADMINISTER) permissions. + * If enabled, the users with {@link Jenkins#ADMINISTER) permissions can generate new tokens for + * other users. Normally only a user can generate tokens for himself. * Disabled by default due to the security reasons. * If enabled, it restores the original Jenkins behavior (SECURITY-200). + * * @since 1.638 */ - private static final boolean SHOW_TOKEN_TO_ADMINS = + private static final boolean ADMIN_CAN_GENERATE_NEW_TOKENS = SystemProperties.getBoolean(ApiTokenProperty.class.getName() + ".showTokenToAdmins"); - /** - * Determine the (log of) number of rounds we need to apply when hashing the token - * default value corresponds to BCrypt#GENSALT_DEFAULT_LOG2_ROUNDS - */ - private static final int BCRYPT_LOG_ROUND = - SystemProperties.getInteger(ApiTokenProperty.class.getName() + ".bcryptLogRound", 10); - @DataBoundConstructor public ApiTokenProperty() { - _changeApiToken(); + this.init(); } - + + public ApiTokenProperty readResolve() { + this.init(); + return this; + } + + private void init() { + if (this.tokenStore == null) { + this.tokenStore = new ApiTokenStore(); + } + } + /** * We don't let the external code set the API token, * but for the initial value of the token we need to compute the seed by ourselves. */ /*package*/ ApiTokenProperty(String seed) { apiToken = Secret.fromString(seed); + this.init(); } - + /** * Gets the API token. * The method performs security checks since 1.638. Only the current user and SYSTEM may see it. - * Users with {@link Jenkins#ADMINISTER} may be allowed to do it using {@link #SHOW_TOKEN_TO_ADMINS}. - * + * Users with {@link Jenkins#ADMINISTER} may be allowed to do it using {@link #ADMIN_CAN_GENERATE_NEW_TOKENS}. + * * @return API Token. Never null, but may be {@link Messages#ApiTokenProperty_ChangeToken_TokenIsHidden()} * if the user has no appropriate permissions. * @since 1.426, and since 1.638 the method performs security checks */ @Nonnull + @Deprecated public String getApiToken() { - return hasPermissionToSeeToken() ? getApiTokenInsecure() - : Messages.ApiTokenProperty_ChangeToken_TokenIsHidden(); + LOGGER.log(Level.WARNING, "Deprecated usage of getApiToken"); + return "deprecated"; } - @Nonnull - @Restricted(NoExternalUse.class) - /*package*/ String getApiTokenInsecure() { - String p = apiToken.getPlainText(); - if (p.equals(Util.getDigestOf(Jenkins.getInstance().getSecretKey()+":"+user.getId()))) { - // if the current token is the initial value created by pre SECURITY-49 Jenkins, we can't use that. - // force using the newer value - apiToken = Secret.fromString(p=API_KEY_SEED.mac(user.getId())); + public boolean matchesPassword(String token) { + if(StringUtils.isBlank(token)){ + return false; } - return Util.getDigestOf(p); + + boolean matchFound = tokenStore.doesContainToken(token); + if (matchFound) { + try { + user.save(); + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Error saving the user after token match", e); + } + } + + return matchFound; } + + /** + * @deprecated Each token can be revoked now and new tokens can be requested without altering existing ones. + */ + @Deprecated + public void changeApiToken() throws IOException { + // just to keep the same level of security + user.checkPermission(Jenkins.ADMINISTER); + + LOGGER.log(Level.WARNING, "Deprecated usage of changeApiToken"); + } + + // only for Jelly + @Restricted(NoExternalUse.class) + public List getTokenList() { + return tokenStore.getTokenListSortedByName(); + } + + @Override + public UserProperty reconfigure(StaplerRequest req, @CheckForNull JSONObject form) throws FormException { + if(form == null){ + return this; + } - public boolean matchesPassword(String password) { - String token = getApiTokenInsecure(); - // String.equals isn't constant time, but this is - return MessageDigest.isEqual(password.getBytes(Charset.forName("US-ASCII")), - token.getBytes(Charset.forName("US-ASCII"))); + Object tokenStoreData = form.get("tokenStore"); + Map tokenStoreTypedData = convertToTokenMap(tokenStoreData); + this.tokenStore.reconfigure(tokenStoreTypedData); + return this; + } + + private Map convertToTokenMap(Object tokenStoreData) { + if (tokenStoreData == null) { + // in case there are no token + return Collections.emptyMap(); + } else if (tokenStoreData instanceof JSONObject) { + // in case there is only one token + JSONObject singleTokenData = (JSONObject) tokenStoreData; + Map result = new HashMap<>(); + addJSONTokenIntoMap(result, singleTokenData); + return result; + } else if (tokenStoreData instanceof JSONArray) { + // in case there are multiple tokens + JSONArray tokenArray = ((JSONArray) tokenStoreData); + Map result = new HashMap<>(); + for (int i = 0; i < tokenArray.size(); i++) { + JSONObject tokenData = tokenArray.getJSONObject(i); + addJSONTokenIntoMap(result, tokenData); + } + return result; + } + + throw HttpResponses.error(400, "Unexpected class received for the token store information"); + } + + private void addJSONTokenIntoMap(Map tokenMap, JSONObject tokenData) { + String uuid = tokenData.getString("tokenUuid"); + tokenMap.put(uuid, tokenData); } - private boolean hasPermissionToSeeToken() { - final Jenkins jenkins = Jenkins.getInstance(); + // for Jelly view + @Restricted(NoExternalUse.class) + public boolean hasCurrentUserPermissionToGenerateTokenForThisUser() { + return hasPermissionToGenerateTokenForUser(user); + } - // Administrators can do whatever they want - if (SHOW_TOKEN_TO_ADMINS && jenkins.hasPermission(Jenkins.ADMINISTER)) { + private static boolean hasPermissionToGenerateTokenForUser(User currentOwner) { + if (ADMIN_CAN_GENERATE_NEW_TOKENS && Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { return true; } - - final User current = User.current(); - if (current == null) { // Anonymous + User currentUser = User.current(); + if (currentUser == null) { + // Anonymous return false; } - // SYSTEM user is always eligible to see tokens if (Jenkins.getAuthentication() == ACL.SYSTEM) { + // SYSTEM user is always eligible to see tokens return true; } - - //TODO: replace by IdStrategy in newer Jenkins versions - //return User.idStrategy().equals(user.getId(), current.getId()); - return StringUtils.equals(user.getId(), current.getId()); - } - public void changeApiToken() throws IOException { - user.checkPermission(Jenkins.ADMINISTER); - _changeApiToken(); - user.save(); + return User.idStrategy().equals(currentOwner.getId(), currentUser.getId()); } - private void _changeApiToken() { - byte[] random = new byte[16]; // 16x8=128bit worth of randomness, since we use md5 digest as the API token - RANDOM.nextBytes(random); - apiToken = Secret.fromString(Util.toHexString(random)); - } - - @Override - public UserProperty reconfigure(StaplerRequest req, JSONObject form) throws FormException { - return this; + public static class ConverterImpl extends XStream2.PassthruConverter { + public ConverterImpl(XStream2 xstream) { + super(xstream); + } + + @Override + protected void callback(ApiTokenProperty apiTokenProperty, UnmarshallingContext context) { + // support legacy configuration + if (apiTokenProperty.apiToken != null) { + apiTokenProperty.tokenStore.generateTokenFromLegacy(apiTokenProperty.apiToken); + OldDataMonitor.report(context, "@since TODO"); + } + + apiTokenProperty.tokenStore.optimize(); + } } - @Extension @Symbol("apiToken") + @Extension + @Symbol("apiToken") public static final class DescriptorImpl extends UserPropertyDescriptor { public String getDisplayName() { return Messages.ApiTokenProperty_DisplayName(); @@ -190,76 +266,117 @@ public class ApiTokenProperty extends UserProperty { * the initial API token value. So we take the seed by hashing the secret + user ID. */ public ApiTokenProperty newInstance(User user) { - if(ApiTokenPropertyConfiguration.get().isTokenGenerationOnCreationDisabled()){ + if (ApiTokenPropertyConfiguration.get().isTokenGenerationOnCreationDisabled()) { // recommended way return null; } + return forceNewInstance(user); + } + + private ApiTokenProperty forceNewInstance(User user) { return new ApiTokenProperty(API_KEY_SEED.mac(user.getId())); } + /** + * @deprecated use {@link #doGenerateNewToken(User, StaplerResponse, String)} instead + */ + @Deprecated @RequirePOST public HttpResponse doChangeToken(@AncestorInPath User u, StaplerResponse rsp) throws IOException { + LOGGER.log(Level.WARNING, "Deprecated action /changeToken used, consider using /generateNewToken instead"); + ApiTokenProperty p = u.getProperty(ApiTokenProperty.class); - if (p==null) { - p = newInstance(u); + if (p == null) { + p = forceNewInstance(u); u.addProperty(p); - } else { - p.changeApiToken(); } - rsp.setHeader("script","document.getElementById('apiToken').value='"+p.getApiToken()+"'"); - return HttpResponses.html(p.hasPermissionToSeeToken() - ? Messages.ApiTokenProperty_ChangeToken_Success() + + String newValue = p.tokenStore.generateNewTokenAndReturnHiddenValue("Created using deprecated method"); + + rsp.setHeader("script","document.getElementById('apiToken').value='"+newValue+"'"); + return HttpResponses.html(p.hasCurrentUserPermissionToGenerateTokenForThisUser() + ? Messages.ApiTokenProperty_ChangeToken_Success() : Messages.ApiTokenProperty_ChangeToken_SuccessHidden()); - } - } - - private static final SecureRandom RANDOM = new SecureRandom(); - - /** - * We don't want an API key that's too long, so cut the length to 16 (which produces 32-letter MAC code in hexdump) - */ - private static final HMACConfidentialKey API_KEY_SEED = new HMACConfidentialKey(ApiTokenProperty.class,"seed",16); - - private class HashedToken { - private String name; - // serve as an optimizer to avoid hashing all the token - private String prefix; - private String hash; - private Date creationDate; - - private Date lastUseDate; - private Integer useCounter; - - private boolean revoked; - private Date revokationDate; - private String revokedBy; - - public HashedToken(String tokenValue){ - this.creationDate = new Date(); - this.hash = BCrypt.hashpw(tokenValue, BCrypt.gensalt(BCRYPT_LOG_ROUND)); + } - public synchronized void incrementUse(){ - if(revoked){ - throw new RuntimeException("A revoked token cannot be used"); + @RequirePOST + public HttpResponse doGenerateNewToken(@AncestorInPath User u, StaplerResponse rsp, @QueryParameter String newTokenName) throws IOException { + if(!hasPermissionToGenerateTokenForUser(u)){ + return HttpResponses.forbidden(); + } + + if (StringUtils.isBlank(newTokenName)) { + return HttpResponses.errorJSON("The name cannot be empty"); + } + + ApiTokenProperty p = u.getProperty(ApiTokenProperty.class); + if (p == null) { + p = forceNewInstance(u); + u.addProperty(p); } - this.useCounter = useCounter == null ? 1 : useCounter + 1; - this.lastUseDate = new Date(); + + String valueToDisplayOnce = p.tokenStore.generateNewTokenAndReturnHiddenValue(newTokenName); + u.save(); + + return HttpResponses.okJSON(new HashMap() {{ + put("tokenValue", valueToDisplayOnce); + }}); } - - public void setName(String name){ - this.name = name; + + @RequirePOST + public HttpResponse doRename(@AncestorInPath User u, + @QueryParameter String tokenId, @QueryParameter String newName) throws IOException { + // only current user + administrator can rename token + u.checkPermission(Jenkins.ADMINISTER); + + if (StringUtils.isBlank(newName)) { + return HttpResponses.errorJSON("The name cannot be empty"); + } + if(StringUtils.isBlank(tokenId)){ + // using the web UI this should not occur + return HttpResponses.errorWithoutStack(400, "The tokenId cannot be empty"); + } + + ApiTokenProperty p = u.getProperty(ApiTokenProperty.class); + if (p == null) { + return HttpResponses.errorWithoutStack(400, "The user does not have any ApiToken yet, try generating one before."); + } + + p.tokenStore.renameToken(tokenId, newName); + u.save(); + + return HttpResponses.ok(); } - - public synchronized void revoke(){ - if(revoked){ - throw new RuntimeException("The token is already revoked"); + + @RequirePOST + public HttpResponse doRevoke(@AncestorInPath User u, + @QueryParameter String tokenId) throws IOException { + // only current user + administrator can revoke token + u.checkPermission(Jenkins.ADMINISTER); + + if(StringUtils.isBlank(tokenId)){ + // using the web UI this should not occur + return HttpResponses.errorWithoutStack(400, "The tokenId cannot be empty"); + } + + ApiTokenProperty p = u.getProperty(ApiTokenProperty.class); + if (p == null) { + return HttpResponses.errorWithoutStack(400, "The user does not have any ApiToken yet, try generating one before."); } - this.revoked = true; - this.revokationDate = new Date(); - this.revokedBy = Jenkins.getAuthentication().getName(); + p.tokenStore.revokeToken(tokenId); + u.save(); + + return HttpResponses.ok(); } } + + /** + * We don't want an API key that's too long, so cut the length to 16 (which produces 32-letter MAC code in hexdump) + * @deprecated only used for the migration of data from previous save + */ + @Deprecated + private static final HMACConfidentialKey API_KEY_SEED = new HMACConfidentialKey(ApiTokenProperty.class, "seed", 16); } diff --git a/core/src/main/java/jenkins/security/ApiTokenStore.java b/core/src/main/java/jenkins/security/ApiTokenStore.java new file mode 100644 index 0000000000..78fadab1fa --- /dev/null +++ b/core/src/main/java/jenkins/security/ApiTokenStore.java @@ -0,0 +1,448 @@ +/* + * 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.security; + +import hudson.Util; +import hudson.diagnosis.OldDataMonitor; +import hudson.util.Secret; +import jenkins.util.SystemProperties; +import net.sf.json.JSONObject; +import org.apache.commons.lang.StringUtils; +import org.mindrot.jbcrypt.BCrypt; + +import javax.annotation.Nonnull; +import javax.annotation.concurrent.Immutable; +import java.security.SecureRandom; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class ApiTokenStore { + private static final Logger LOGGER = Logger.getLogger(OldDataMonitor.class.getName()); + private static final SecureRandom RANDOM = new SecureRandom(); + + private static final Comparator SORT_BY_LOWERCASED_NAME = + Comparator.comparing(hashedToken -> hashedToken.getName().toLowerCase()); + + /** + * Determine the (log of) number of rounds we need to apply when hashing the token + * default value corresponds to + * BCrypt#GENSALT_DEFAULT_LOG2_ROUNDS is 10 which is way too small in 2018 + */ + private static final int BCRYPT_LOG_ROUND = + SystemProperties.getInteger(ApiTokenStore.class.getName() + ".bcryptLogRound", 13); + /** + * Determine the number of attempt to generate an unique prefix (over 4096 possibilities) that is not currently used + */ + private static final int MAX_ATTEMPTS = + SystemProperties.getInteger(ApiTokenStore.class.getName() + ".maxAttempt", 100); + + private static final int TOKEN_LENGTH_V2 = 36; + /** single hex-character */ + private static final String LEGACY_VERSION = "1"; + private static final String HASH_VERSION = "2"; + + private static final String LEGACY_PREFIX = ""; + + private List tokenList; + private transient Map> prefixToTokenList; + + public ApiTokenStore() { + this.init(); + } + + public ApiTokenStore readResolve() { + this.init(); + return this; + } + + private void init() { + if (this.tokenList == null) { + this.tokenList = new ArrayList<>(); + } + this.prefixToTokenList = new HashMap<>(); + } + + public synchronized @Nonnull List getTokenListSortedByName() { + List sortedTokenList = tokenList.stream() + .sorted(SORT_BY_LOWERCASED_NAME) + .collect(Collectors.toList()); + + return sortedTokenList; + } + + /** + * After a load from the disk, we need to re-populate the prefix map + */ + public synchronized void optimize() { + this.prefixToTokenList.clear(); + tokenList.forEach(this::addTokenInPrefixMap); + } + + private void addToken(HashedToken token) { + this.tokenList.add(token); + this.addTokenInPrefixMap(token); + } + + private void addTokenInPrefixMap(HashedToken token) { + String prefix = token.value.prefix; + Node newNode = new Node<>(token); + if (prefixToTokenList.containsKey(prefix)) { + Node existingNode = prefixToTokenList.get(prefix); + existingNode.addNode(newNode); + } else { + prefixToTokenList.put(prefix, newNode); + } + } + + /** + * Defensive approach to avoid involuntary change since the UUIDs are generated at startup only for UI + * and so between restart they change + */ + public synchronized void reconfigure(@Nonnull Map tokenStoreDataMap) { + tokenList.forEach(hashedToken -> { + JSONObject receivedTokenData = tokenStoreDataMap.get(hashedToken.uuid); + if (receivedTokenData == null) { + LOGGER.log(Level.INFO, "No token received for {}", hashedToken.uuid); + return; + } + + String name = receivedTokenData.getString("tokenName"); + if (StringUtils.isBlank(name)) { + LOGGER.log(Level.INFO, "Empty name received for {}, we do not care about it", hashedToken.uuid); + return; + } + + hashedToken.setName(name); + }); + } + + private static class Node { + private T value; + private Node next; + + Node(T value) { + this.value = value; + } + + public void addNode(@Nonnull Node other) { + if (next == null) { + this.next = other; + } else { + this.next.addNode(other); + } + } + } + + public synchronized void generateTokenFromLegacy(@Nonnull Secret apiToken) { + String tokenUserUseNormally = Util.getDigestOf(apiToken.getPlainText()); + + String secretValueHashed = this.hashSecret(tokenUserUseNormally); + + HashValue hashValue = new HashValue(LEGACY_VERSION, LEGACY_PREFIX, secretValueHashed); + HashedToken token = HashedToken.buildNew(Messages.ApiTokenProperty_LegacyTokenName(), hashValue); + + this.addToken(token); + } + + public synchronized @Nonnull String generateNewTokenAndReturnHiddenValue(@Nonnull String name) { + // 16x8=128bit worth of randomness, since we use md5 digest as the API token + byte[] random = new byte[16]; + RANDOM.nextBytes(random); + String secretValue = Util.toHexString(random); + String prefix = generatePrefix(); + String tokenTheUserWillUse = HASH_VERSION + prefix + secretValue; + assert tokenTheUserWillUse.length() == 1 + 3 + 32; + + String secretValueHashed = this.hashSecret(secretValue); + + HashValue hashValue = new HashValue(HASH_VERSION, prefix, secretValueHashed); + HashedToken token = HashedToken.buildNew(name, hashValue); + + this.addToken(token); + + return tokenTheUserWillUse; + } + + private @Nonnull String hashSecret(@Nonnull String secretValue) { + return BCrypt.hashpw(secretValue, BCrypt.gensalt(BCRYPT_LOG_ROUND, RANDOM)); + } + + private @Nonnull String generatePrefix() { + int i = 0; + boolean unique; + String currentPrefix; + + do { + currentPrefix = generateRandomPrefix(); + unique = !prefixToTokenList.containsKey(currentPrefix); + i++; + } while (i < MAX_ATTEMPTS && !unique); + + Level logLevel = Level.FINE; + if (i == MAX_ATTEMPTS) { + logLevel = Level.WARNING; + } + LOGGER.log(logLevel, "Prefix generated after {0}/{1} attempts", new Object[]{i, MAX_ATTEMPTS}); + + return currentPrefix; + } + + /** + * Generate random 3-hex-character + */ + private @Nonnull String generateRandomPrefix() { + int prefixInteger = RANDOM.nextInt(4096); + String prefixString = Integer.toHexString(prefixInteger); + return StringUtils.leftPad(prefixString, 3, '0'); + } + + public synchronized boolean doesContainToken(@Nonnull String token) { + String prefixToSearch; + String plainToken; + + if (isLegacyToken(token)) { + prefixToSearch = LEGACY_PREFIX; + plainToken = token; + } else { + prefixToSearch = getPrefixOfToken(token); + plainToken = getHashOfToken(token); + } + + return searchMatchUsingPrefix(prefixToSearch, plainToken); + } + + private boolean isLegacyToken(String token) { + return token.length() != TOKEN_LENGTH_V2; + } + + /** + * [1: version][3: prefix][32: real token] + * ^^^^^^^^^^^^--------------------------- + */ + private String getVersionOfToken(String token) { + return String.valueOf(token.charAt(0)); + } + + /** + * [1: version][3: prefix][32: real token] + * ------------^^^^^^^^^^^---------------- + */ + private String getPrefixOfToken(String token) { + return token.substring(1, 4); + } + + /** + * [1: version][3: prefix][32: real token] + * -----------------------^^^^^^^^^^^^^^^^ + */ + private String getHashOfToken(String token) { + return token.substring(4); + } + + private boolean searchMatchUsingPrefix(String prefix, String plainToken) { + Node node = this.prefixToTokenList.get(prefix); + while (node != null) { + boolean matchFound = node.value.match(plainToken); + if (matchFound) { + node.value.incrementUse(); + return true; + } else { + node = node.next; + } + } + + return false; + } + + public synchronized void revokeToken(@Nonnull String tokenId) { + for (int i = 0; i < tokenList.size(); i++) { + HashedToken token = tokenList.get(i); + if (token.uuid.equals(tokenId)) { + tokenList.remove(i); + + removeTokenFromPrefixMap(token); + } + } + } + + private void removeTokenFromPrefixMap(HashedToken token) { + String prefix = token.value.prefix; + Node node = prefixToTokenList.get(prefix); + if (node == null) { + // normally not the case + return; + } + + // first node, we replace it by the next one or nothing + if (node.value.uuid.equals(token.uuid)) { + if (node.next == null) { + prefixToTokenList.remove(prefix); + } else { + prefixToTokenList.put(prefix, node.next); + } + } else { + Node previousNode = node; + node = node.next; + while (node != null) { + // 2-nth node, we replace the previous.next with new value + // but do not touch the initial node + if (node.value.uuid.equals(token.uuid)) { + if (node.next == null) { + previousNode.next = null; + } else { + previousNode.next = node.next; + } + return; + } + + previousNode = node; + node = node.next; + } + } + } + + public synchronized void renameToken(@Nonnull String tokenId, @Nonnull String newName) { + for (HashedToken token : tokenList) { + if (token.uuid.equals(tokenId)) { + token.rename(newName); + return; + } + } + } + + /** + * [1: version][3: prefix][32: real token] + */ + @Immutable + private static class HashValue { + /** + * Serve as an optimizer to avoid hashing all the tokens for the token-check + * not a "confidential" information + */ + private final String prefix; + /** To ease future implementation */ + private final String version; + /** The only confidential information. The token is stored only as a BCrypt hash */ + private final String hash; + + public HashValue(String version, String prefix, String hash) { + this.version = version; + this.prefix = prefix; + this.hash = hash; + } + } + + public static class HashedToken { + // to ease the modification of the token through the UI + private transient String uuid; + private String name; + private Date creationDate; + + private HashValue value; + + private Date lastUseDate; + private Integer useCounter; + + public HashedToken() { + this.init(); + } + + public HashedToken readResolve() { + this.init(); + return this; + } + + private void init() { + this.uuid = UUID.randomUUID().toString(); + } + + public static @Nonnull HashedToken buildNew(@Nonnull String name, @Nonnull HashValue value) { + HashedToken result = new HashedToken(); + result.name = name; + result.creationDate = new Date(); + + result.value = value; + + return result; + } + + public void rename(String newName) { + this.name = newName; + } + + public boolean match(String plainToken) { + return BCrypt.checkpw(plainToken, value.hash); + } + + public String getName() { + return name; + } + + public int getUseCounter() { + return useCounter == null ? 0 : useCounter; + } + + public long getNumDaysUse() { + return lastUseDate == null ? 0 : computeDeltaDays(lastUseDate.toInstant(), Instant.now()); + } + + public long getNumDaysCreation() { + // should not happen but just in case + return creationDate == null ? 0 : computeDeltaDays(creationDate.toInstant(), Instant.now()); + } + + public String getUuid() { + return this.uuid; + } + + private long computeDeltaDays(Instant a, Instant b) { + long deltaDays = ChronoUnit.DAYS.between(a, b); + deltaDays = Math.max(0, deltaDays); + return deltaDays; + } + + public boolean isLegacy() { + return this.value.version.equals(LEGACY_VERSION); + } + + public void incrementUse() { + this.useCounter = useCounter == null ? 1 : useCounter + 1; + this.lastUseDate = new Date(); + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/config.groovy b/core/src/main/resources/jenkins/security/ApiTokenProperty/config.groovy deleted file mode 100644 index 76ab6b5eb5..0000000000 --- a/core/src/main/resources/jenkins/security/ApiTokenProperty/config.groovy +++ /dev/null @@ -1,36 +0,0 @@ -/* - * The MIT License - * - * Copyright (c) 2011, 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.ApiTokenProperty; - -f=namespace(lib.FormTagLib) - -f.advanced(title:_("Show API Token"), align:"left") { - f.entry(title: _('User ID')) { - f.readOnlyTextbox(value: my.id) - } - f.entry(title:_("API Token"), field:"apiToken") { - f.readOnlyTextbox(id:"apiToken") // TODO: need to figure out the way to do this without using ID. - } - f.validateButton(title:_("Change API Token"),method:"changeToken") -} diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/config.jelly b/core/src/main/resources/jenkins/security/ApiTokenProperty/config.jelly new file mode 100644 index 0000000000..78503649a4 --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenProperty/config.jelly @@ -0,0 +1,91 @@ + + + + + + + + +
    + + + + + + + + + + diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/config.properties b/core/src/main/resources/jenkins/security/ApiTokenProperty/config.properties new file mode 100644 index 0000000000..ba92f439d4 --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenProperty/config.properties @@ -0,0 +1,9 @@ +TokenDisplayedOnce=Once generated the token will be displayed only once. The application knows only the hash of your token for the verification. +NoTokenYet=There is no registered token for this user +TokenLastUse=Used {0} time(s), last time was {1} day(s) ago +TokenNeverUsed=Never used +TokenNeverUsed.Title=If you do not plan to use it, we strongly advice you to revoke it for security reasons +ConfirmRevokeSingle=Are you sure you want to revoke this token ? Applications that are using it will be not able to connect anymore. +CurrentTokens=Current tokens +TokenCreation=Created {0} day(s) ago +RenameToken=Save the new name of the token diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/config_it.properties b/core/src/main/resources/jenkins/security/ApiTokenProperty/config_it.properties deleted file mode 100644 index 0f79a3b1d1..0000000000 --- a/core/src/main/resources/jenkins/security/ApiTokenProperty/config_it.properties +++ /dev/null @@ -1,5 +0,0 @@ -# This file is under the MIT License by authors - -Show\ API\ Token=Visualizza token API -API\ Token=Token API -Change\ API\ Token=Modifica token API diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/config_ja.properties b/core/src/main/resources/jenkins/security/ApiTokenProperty/config_ja.properties deleted file mode 100644 index 1ba9cbe07d..0000000000 --- a/core/src/main/resources/jenkins/security/ApiTokenProperty/config_ja.properties +++ /dev/null @@ -1,25 +0,0 @@ -# The MIT License -# -# Copyright 2011 Seiji Sogabe -# -# 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. - -Show\ API\ Token=API\u30c8\u30fc\u30af\u30f3\u306e\u8868\u793a -API\ Token=API\u30c8\u30fc\u30af\u30f3 -Change\ API\ Token=API\u30c8\u30fc\u30af\u30f3\u306e\u5909\u66f4 diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/config_sr.properties b/core/src/main/resources/jenkins/security/ApiTokenProperty/config_sr.properties deleted file mode 100644 index e7ad742d80..0000000000 --- a/core/src/main/resources/jenkins/security/ApiTokenProperty/config_sr.properties +++ /dev/null @@ -1,5 +0,0 @@ -# This file is under the MIT License by authors - -Show\ API\ Token=\u041F\u0440\u0438\u043A\u0430\u0436\u0438 \u0410\u041F\u0418 \u0422\u043E\u043A\u0435\u043D -API\ Token=\u0410\u041F\u0418 \u0422\u043E\u043A\u0435\u043D -Change\ API\ Token=\u0423\u0440\u0435\u0434\u0438 \u0410\u041F\u0418 \u0422\u043E\u043A\u0435\u043D diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/config_zh_TW.properties b/core/src/main/resources/jenkins/security/ApiTokenProperty/config_zh_TW.properties deleted file mode 100644 index 5c9cdbb193..0000000000 --- a/core/src/main/resources/jenkins/security/ApiTokenProperty/config_zh_TW.properties +++ /dev/null @@ -1,25 +0,0 @@ -# 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. - -Show\ API\ Token=\u986f\u793a API Token -API\ Token=API Token -Change\ API\ Token=\u8b8a\u66f4 API Token diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken.html b/core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken.html index 52babf7ea8..ae301ad53d 100644 --- a/core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken.html +++ b/core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken.html @@ -1,5 +1,12 @@
    - This API token can be used for authenticating yourself in the REST API call. - See our wiki for more details. - The API token should be protected like your password, as it allows other people to access Jenkins as you. -
    \ No newline at end of file + The API tokens can be used for authenticating yourself in the REST API call. + See our wiki for more details.
    + The associated username with the tokens is the regular one of your account.
    +
    + The best practices in terms of tokens are: +
      +
    • Use a different token for each application (if one application is compromised, you do not loose data from other)
    • +
    • Regenerate the tokens every x weeks/months. We put an indicator concerning the age of the token.
    • +
    • Protect it like your password, as it allows other people to access Jenkins as you.
    • +
    +
    diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.css b/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.css new file mode 100644 index 0000000000..1d9ac3c405 --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.css @@ -0,0 +1,83 @@ +/* + * 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. + */ +ul.token-list{ + /* reset to get rid of browser default */ + margin: 0; + padding: 0; + + max-width: 700px; + list-style: none; + border: 1px solid #cccccc; + border-radius: 3px; +} + +ul.token-list li.token-list-item { + min-height: inherit; + padding: 10px; + font-size: 13px; + line-height: 26px; +} +ul.token-list li.token-list-item.list-empty { + display: none; +} +ul.token-list li.token-list-item.list-empty:first-child { + display: block; +} +ul.token-list li.token-list-item.list-empty .list-empty-message { + padding: 0 10px; +} + +/* + * trick to display the "empty list" message when the list is directly empty + * or even after removing the last element + */ +ul.token-list li.token-list-item .token-name-input{ + font-weight: bold; +} +ul.token-list li.token-list-item .token-creation{ + margin-left: 5px; + font-size: 90%; +} +ul.token-list li.token-list-item .token-creation.age-ok{ + color: #487ba0; +} +ul.token-list li.token-list-item .token-creation.age-mmmh{ + color: #e09307; +} +ul.token-list li.token-list-item .token-creation.age-argh{ + color: #de6868; +} +ul.token-list li.token-list-item .token-hide{ + display: none; +} +ul.token-list li.token-list-item .to-right{ + float: right; +} +ul.token-list li.token-list-item .token-use-counter{ + font-size: 90%; + color: #586069; +} +ul.token-list li.token-list-item .token-revoke{ + margin-left: 10px; +} diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.js b/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.js new file mode 100644 index 0000000000..01028f5844 --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.js @@ -0,0 +1,83 @@ +/* + * 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. + */ +function revokeToken(anchorRevoke){ + var listItemParent = anchorRevoke.up('.token-list-item'); + var confirmMessage = anchorRevoke.attributes['data-confirm'].value; + var targetUrl = anchorRevoke.attributes['data-target-url'].value; + + var inputUuid = listItemParent.querySelector('input.token-uuid-input'); + var tokenId = inputUuid.value; + + console.warn('revokeToken'); + if(confirm(confirmMessage)){ + new Ajax.Request(targetUrl, { + method: "post", + parameters: {tokenId: tokenId}, + onSuccess: function(rsp,_) { + listItemParent.remove(); + } + }); + } + + return false; +} + +function saveApiToken(button){ + if(button.hasClassName('request-pending')){ + // avoid multiple requests to be sent if user is clicking multiple times + return; + } + button.addClassName('request-pending'); + var targetUrl = button.attributes['data-target-url'].value; + var rowParent = button.up('tr'); + var nameInput = rowParent.querySelector('[name="newTokenName"]'); + var tokenName = nameInput.value; + + new Ajax.Request(targetUrl, { + method: "post", + parameters: {"newTokenName": tokenName}, + onSuccess: function(rsp,_) { + var json = rsp.responseJSON; + var errorSpan = rowParent.querySelector('.error'); + if(json.status === 'error'){ + errorSpan.style.display = 'block'; + errorSpan.innerHTML = json.message; + + button.removeClassName('request-pending'); + }else{ + errorSpan.style.display = 'none'; + + nameInput.setAttribute('readonly', 'readonly'); + + var tokenValue = json.data.tokenValue; + var valueInput = rowParent.querySelector('#newTokenValue'); + valueInput.value = tokenValue; + valueInput.style.display = 'inline-block'; + + // we do not want to allow user to create twice a token using same name by mistake + button.remove(); + } + } + }); +} diff --git a/core/src/main/resources/jenkins/security/Messages.properties b/core/src/main/resources/jenkins/security/Messages.properties index fffe3bfe6c..1e7d472b43 100644 --- a/core/src/main/resources/jenkins/security/Messages.properties +++ b/core/src/main/resources/jenkins/security/Messages.properties @@ -24,6 +24,8 @@ ApiTokenProperty.DisplayName=API Token ApiTokenProperty.ChangeToken.TokenIsHidden=Token is hidden ApiTokenProperty.ChangeToken.Success=
    Updated. See the new token in the field above
    ApiTokenProperty.ChangeToken.SuccessHidden=
    Updated. You need to login as the user to see the token
    +ApiTokenProperty.NewTokenGenerated=
    Token generated! The value will not be displayed anymore, please save it.
    +ApiTokenProperty.LegacyTokenName=Legacy Token ApiTokenPropertyDisabledDefaultAdministrativeMonitor.displayName=API Token not generated by default RekeySecretAdminMonitor.DisplayName=Re-keying UpdateSiteWarningsMonitor.DisplayName=Update Site Warnings diff --git a/test/src/test/java/hudson/cli/CLIActionTest.java b/test/src/test/java/hudson/cli/CLIActionTest.java index 0c29a2d86e..132b0bb885 100644 --- a/test/src/test/java/hudson/cli/CLIActionTest.java +++ b/test/src/test/java/hudson/cli/CLIActionTest.java @@ -35,6 +35,7 @@ import org.apache.commons.io.output.TeeOutputStream; import org.codehaus.groovy.runtime.Security218; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.*; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -126,6 +127,7 @@ public class CLIActionTest { @Issue({"JENKINS-12543", "JENKINS-41745"}) @Test + @Ignore //TODO need to be fixed public void authentication() throws Exception { logging.record(PlainCLIProtocol.class, Level.FINE); File jar = tmp.newFile("jenkins-cli.jar"); diff --git a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java index 8737d26974..92fb1f986c 100644 --- a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java +++ b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java @@ -13,9 +13,9 @@ import hudson.Util; import hudson.model.User; import hudson.security.ACL; import hudson.security.ACLContext; -import hudson.util.Scrambler; import java.net.URL; import jenkins.model.Jenkins; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -37,9 +37,10 @@ public class ApiTokenPropertyTest { * Tests the UI interaction and authentication. */ @Test + @Ignore //TODO need to be fixed public void basics() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); - User u = User.get("foo"); + User u = User.getById("foo", true); final ApiTokenProperty t = u.getProperty(ApiTokenProperty.class); final String token = t.getApiToken(); @@ -67,6 +68,7 @@ public class ApiTokenPropertyTest { } @Test + @Ignore //TODO need to be fixed public void security49Upgrade() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User u = User.get("foo"); @@ -91,6 +93,7 @@ public class ApiTokenPropertyTest { @Issue("SECURITY-200") @Test + @Ignore //TODO need to be fixed public void adminsShouldBeUnableToSeeTokensByDefault() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User u = User.get("foo"); @@ -106,6 +109,7 @@ public class ApiTokenPropertyTest { @Issue("SECURITY-200") @Test + @Ignore //TODO need to be fixed public void adminsShouldBeUnableToChangeTokensByDefault() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User foo = User.get("foo"); @@ -130,14 +134,17 @@ public class ApiTokenPropertyTest { } @Nonnull - private WebClient createClientForUser(final String username) throws Exception { - User u = User.get(username); - final ApiTokenProperty t = u.getProperty(ApiTokenProperty.class); - // Yes, we use the insecure call in the test stuff - final String token = t.getApiTokenInsecure(); + private WebClient createClientForUser(final String id) throws Exception { + User u = User.getById(id, false); + + //TODO to be fixed +// final ApiTokenProperty t = u.getProperty(ApiTokenProperty.class); +// // Yes, we use the insecure call in the test stuff +// final String token = t.getApiTokenInsecure(); + WebClient wc = j.createWebClient(); - wc.addRequestHeader("Authorization", "Basic " + Scrambler.scramble(username + ":" + token)); +// wc.addRequestHeader("Authorization", "Basic " + Scrambler.scramble(id + ":" + token)); return wc; } diff --git a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java index 0b471dd2b3..9189e051e4 100644 --- a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java +++ b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java @@ -8,6 +8,7 @@ import hudson.model.UnprotectedRootAction; import hudson.model.User; import hudson.util.HttpResponses; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -44,6 +45,7 @@ public class BasicHeaderProcessorTest { * Tests various ways to send the Basic auth. */ @Test + @Ignore //TODO need to be fixed public void testVariousWaysToCall() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User foo = User.getById("foo", true); @@ -111,6 +113,7 @@ public class BasicHeaderProcessorTest { } @Test + @Ignore //TODO need to be fixed public void testAuthHeaderCaseInSensitive() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User foo = User.get("foo"); -- GitLab From d994e65a9a380908cfd6ac3e4c41c9f2040d9a73 Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Fri, 26 Jan 2018 10:16:34 -0500 Subject: [PATCH 049/863] [JENKINS-48463] Erring on the side of caution and incuding xpp3 libs back in, just in case some plugin might be depending on them --- core/pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/pom.xml b/core/pom.xml index 7ef2c3227d..9c4faab34d 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -246,6 +246,11 @@ THE SOFTWARE.
    + + xpp3 + xpp3 + 1.1.4c + net.sf.kxml kxml2 -- GitLab From c87f5fd801b0aeb67a867b74ac50904e09a02020 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Fri, 26 Jan 2018 16:20:50 +0100 Subject: [PATCH 050/863] - adapt for new UI style on warning message - correct the help file name --- ...abled.html => help-tokenGenerationOnCreationDisabled.html} | 0 .../message.jelly | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/{help-enabled.html => help-tokenGenerationOnCreationDisabled.html} (100%) diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-enabled.html b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-tokenGenerationOnCreationDisabled.html similarity index 100% rename from core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-enabled.html rename to core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-tokenGenerationOnCreationDisabled.html diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly index 4a310bedb3..f7899f670c 100644 --- a/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly @@ -23,11 +23,11 @@ THE SOFTWARE. --> -
    +
    - ${%warningMessage(rootURL)} + ${%warningMessage(rootURL)}
    -- GitLab From 720db118a808c04f2ae04dc36582ce7dd5031b9b Mon Sep 17 00:00:00 2001 From: Oleg Nenashev Date: Fri, 26 Jan 2018 17:26:16 +0100 Subject: [PATCH 051/863] [JENKINS-48725] - Update Lib Task Reactor to 1.5 --- core/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/pom.xml b/core/pom.xml index ce9d90f5ab..a4846b1b48 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -219,7 +219,7 @@ THE SOFTWARE. org.jenkins-ci task-reactor - 1.4 + 1.5 org.jvnet.localizer -- GitLab From e798f64854be85141ab7ba610c3a8ee0b093f36b Mon Sep 17 00:00:00 2001 From: Daniel Trebbien Date: Fri, 26 Jan 2018 09:35:20 -0800 Subject: [PATCH 052/863] [JENKINS-49112] Revert use of Files.newBufferedReader() in Util.loadFile() (#3259) * [JENKINS-49112] Revert use of Files.newBufferedReader() in Util.loadFile() This partially reverts 67076834a24f03b832ab03b1bf9b96e05a5da81e. * Restore the use of StringBuilder and reading into buf --- core/src/main/java/hudson/Util.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/hudson/Util.java b/core/src/main/java/hudson/Util.java index f2f242a4a1..ce381d7a0a 100644 --- a/core/src/main/java/hudson/Util.java +++ b/core/src/main/java/hudson/Util.java @@ -193,11 +193,26 @@ public class Util { StringBuilder str = new StringBuilder((int)logfile.length()); - try (BufferedReader r = Files.newBufferedReader(fileToPath(logfile), charset)) { + // We're not using Files.newBufferedReader() here because there is a + // difference in how an InputStreamReader constructed from a Charset and + // the reader returned by Files.newBufferedReader() handle malformed and + // unmappable byte sequences for the specified encoding; the latter is + // more picky and will throw a CharacterCodingException. See: + // https://issues.jenkins-ci.org/browse/JENKINS-49060?focusedCommentId=325989&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-325989 + // + // As reported at https://issues.jenkins-ci.org/browse/JENKINS-49112 + // Run.getLog() calls loadFile() to fully read the generated log file. + // Until charset handling is resolved (e.g. by implementing + // https://issues.jenkins-ci.org/browse/JENKINS-48923 ), malformed + // bytes will need to be tolerated. + try (InputStream rawIn = Files.newInputStream(fileToPath(logfile)); + Reader r = new BufferedReader(new InputStreamReader(rawIn, charset))) { char[] buf = new char[1024]; int len; while ((len = r.read(buf, 0, buf.length)) > 0) str.append(buf, 0, len); + } catch (Exception e) { + throw new IOException("Failed to fully read " + logfile + " using charset " + charset.name(), e); } return str.toString(); -- GitLab From 671772bf1c545c1853bf7bf73cc9189a5bc89eab Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Mon, 29 Jan 2018 09:45:07 +0100 Subject: [PATCH 053/863] Improvement to keep pure compatibility with legacy system --- .../java/jenkins/install/SetupWizard.java | 5 + .../jenkins/security/ApiTokenProperty.java | 179 +++++++++++++----- .../ApiTokenPropertyConfiguration.java | 37 +--- ...yDisabledDefaultAdministrativeMonitor.java | 2 +- ...EnabledNewLegacyAdministrativeMonitor.java | 66 +++++++ .../java/jenkins/security/ApiTokenStore.java | 33 +++- .../security/ApiTokenProperty/config.jelly | 31 ++- .../ApiTokenProperty/config.properties | 1 + ...elp-apiToken.html => help-tokenStore.html} | 0 ...iToken_bg.html => help-tokenStore_bg.html} | 0 ...iToken_it.html => help-tokenStore_it.html} | 0 ...iToken_ja.html => help-tokenStore_ja.html} | 0 ..._zh_TW.html => help-tokenStore_zh_TW.html} | 0 .../security/ApiTokenProperty/resources.css | 10 +- .../config.jelly | 5 +- .../help-creationOfLegacyTokenDisabled.html | 5 + ...elp-tokenGenerationOnCreationDisabled.html | 5 +- .../message.jelly | 2 +- .../message.properties | 4 +- .../message.jelly | 33 ++++ .../message.properties | 2 + .../jenkins/security/Messages.properties | 3 +- .../test/java/hudson/cli/CLIActionTest.java | 1 - .../security/ApiTokenPropertyTest.java | 4 - .../security/BasicHeaderProcessorTest.java | 2 - 25 files changed, 326 insertions(+), 104 deletions(-) create mode 100644 core/src/main/java/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor.java rename core/src/main/resources/jenkins/security/ApiTokenProperty/{help-apiToken.html => help-tokenStore.html} (100%) rename core/src/main/resources/jenkins/security/ApiTokenProperty/{help-apiToken_bg.html => help-tokenStore_bg.html} (100%) rename core/src/main/resources/jenkins/security/ApiTokenProperty/{help-apiToken_it.html => help-tokenStore_it.html} (100%) rename core/src/main/resources/jenkins/security/ApiTokenProperty/{help-apiToken_ja.html => help-tokenStore_ja.html} (100%) rename core/src/main/resources/jenkins/security/ApiTokenProperty/{help-apiToken_zh_TW.html => help-tokenStore_zh_TW.html} (100%) create mode 100644 core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-creationOfLegacyTokenDisabled.html create mode 100644 core/src/main/resources/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor/message.jelly create mode 100644 core/src/main/resources/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor/message.properties diff --git a/core/src/main/java/jenkins/install/SetupWizard.java b/core/src/main/java/jenkins/install/SetupWizard.java index 6c70482369..c671c50723 100644 --- a/core/src/main/java/jenkins/install/SetupWizard.java +++ b/core/src/main/java/jenkins/install/SetupWizard.java @@ -20,6 +20,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; +import jenkins.security.ApiTokenPropertyConfiguration; import jenkins.util.SystemProperties; import org.acegisecurity.Authentication; import org.acegisecurity.context.SecurityContextHolder; @@ -122,6 +123,10 @@ public class SetupWizard extends PageDecorator { authStrategy.setAllowAnonymousRead(false); jenkins.setAuthorizationStrategy(authStrategy); + // Disable the legacy system of API Token + ApiTokenPropertyConfiguration.get().setCreationOfLegacyTokenDisabled(true); + ApiTokenPropertyConfiguration.get().setTokenGenerationOnCreationDisabled(true); + // Disable jnlp by default, but honor system properties jenkins.setSlaveAgentPort(SystemProperties.getInteger(Jenkins.class.getName()+".slaveAgentPort",-1)); diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java index c11069c638..f7e28b010d 100644 --- a/core/src/main/java/jenkins/security/ApiTokenProperty.java +++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java @@ -25,6 +25,7 @@ package jenkins.security; import com.thoughtworks.xstream.converters.UnmarshallingContext; import hudson.Extension; +import hudson.Util; import hudson.diagnosis.OldDataMonitor; import hudson.util.XStream2; import jenkins.util.SystemProperties; @@ -47,6 +48,7 @@ import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import java.io.IOException; +import java.security.SecureRandom; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -72,7 +74,7 @@ import org.kohsuke.stapler.interceptor.RequirePOST; public class ApiTokenProperty extends UserProperty { private static final Logger LOGGER = Logger.getLogger(ApiTokenProperty.class.getName()); - private transient volatile Secret apiToken; + private volatile Secret apiToken; private ApiTokenStore tokenStore; /** @@ -83,9 +85,12 @@ public class ApiTokenProperty extends UserProperty { * * @since 1.638 */ - private static final boolean ADMIN_CAN_GENERATE_NEW_TOKENS = + private static final boolean SHOW_LEGACY_TOKEN_TO_ADMINS = SystemProperties.getBoolean(ApiTokenProperty.class.getName() + ".showTokenToAdmins"); + private static final boolean ADMIN_CAN_GENERATE_NEW_TOKENS = + SystemProperties.getBoolean(ApiTokenProperty.class.getName() + ".adminCanGenerateNewTokens"); + @DataBoundConstructor public ApiTokenProperty() { this.init(); @@ -106,32 +111,56 @@ public class ApiTokenProperty extends UserProperty { * We don't let the external code set the API token, * but for the initial value of the token we need to compute the seed by ourselves. */ - /*package*/ ApiTokenProperty(String seed) { - apiToken = Secret.fromString(seed); + /*package*/ ApiTokenProperty(@CheckForNull String seed) { + if(seed != null){ + apiToken = Secret.fromString(seed); + tokenStore = new ApiTokenStore(); + tokenStore.generateTokenFromLegacy(apiToken); + } this.init(); } /** * Gets the API token. * The method performs security checks since 1.638. Only the current user and SYSTEM may see it. - * Users with {@link Jenkins#ADMINISTER} may be allowed to do it using {@link #ADMIN_CAN_GENERATE_NEW_TOKENS}. + * Users with {@link Jenkins#ADMINISTER} may be allowed to do it using {@link #SHOW_LEGACY_TOKEN_TO_ADMINS}. * * @return API Token. Never null, but may be {@link Messages#ApiTokenProperty_ChangeToken_TokenIsHidden()} * if the user has no appropriate permissions. * @since 1.426, and since 1.638 the method performs security checks */ @Nonnull - @Deprecated public String getApiToken() { LOGGER.log(Level.WARNING, "Deprecated usage of getApiToken"); - return "deprecated"; + if(this.apiToken == null){ + return "deprecated"; + } + + return hasPermissionToSeeToken() ? getApiTokenInsecure() : Messages.ApiTokenProperty_ChangeToken_TokenIsHidden(); + } + + @Nonnull + @Restricted(NoExternalUse.class) + /*package*/ String getApiTokenInsecure() { + if(apiToken == null){ + return "deprecated"; + } + + String p = apiToken.getPlainText(); + if (p.equals(Util.getDigestOf(Jenkins.getInstance().getSecretKey()+":"+user.getId()))) { + // if the current token is the initial value created by pre SECURITY-49 Jenkins, we can't use that. + // force using the newer value + apiToken = Secret.fromString(p=API_KEY_SEED.mac(user.getId())); + } + return Util.getDigestOf(p); } public boolean matchesPassword(String token) { if(StringUtils.isBlank(token)){ return false; } - + + // use the new way to find a match in order to trigger the counter / lastUseDate logic boolean matchFound = tokenStore.doesContainToken(token); if (matchFound) { try { @@ -145,14 +174,25 @@ public class ApiTokenProperty extends UserProperty { } /** - * @deprecated Each token can be revoked now and new tokens can be requested without altering existing ones. + * Only for legacy token */ - @Deprecated - public void changeApiToken() throws IOException { - // just to keep the same level of security - user.checkPermission(Jenkins.ADMINISTER); + private boolean hasPermissionToSeeToken() { + // Administrators can do whatever they want + if (SHOW_LEGACY_TOKEN_TO_ADMINS && Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { + return true; + } - LOGGER.log(Level.WARNING, "Deprecated usage of changeApiToken"); + User current = User.current(); + if (current == null) { // Anonymous + return false; + } + + // SYSTEM user is always eligible to see tokens + if (Jenkins.getAuthentication() == ACL.SYSTEM) { + return true; + } + + return User.idStrategy().equals(user.getId(), current.getId()); } // only for Jelly @@ -161,6 +201,12 @@ public class ApiTokenProperty extends UserProperty { return tokenStore.getTokenListSortedByName(); } + // only for Jelly + @Restricted(NoExternalUse.class) + public boolean mustDisplayLegacyApiToken() { + return apiToken != null || !ApiTokenPropertyConfiguration.get().isCreationOfLegacyTokenDisabled(); + } + @Override public UserProperty reconfigure(StaplerRequest req, @CheckForNull JSONObject form) throws FormException { if(form == null){ @@ -202,29 +248,28 @@ public class ApiTokenProperty extends UserProperty { tokenMap.put(uuid, tokenData); } - // for Jelly view - @Restricted(NoExternalUse.class) - public boolean hasCurrentUserPermissionToGenerateTokenForThisUser() { - return hasPermissionToGenerateTokenForUser(user); - } + /** + * Only usable if the user still has his API token. After the revocation of the legacy token the method will do nothing. + * @deprecated Each token can be revoked now and new tokens can be requested without altering existing ones. + */ + @Deprecated + public void changeApiToken() throws IOException { + // just to keep the same level of security + user.checkPermission(Jenkins.ADMINISTER); - private static boolean hasPermissionToGenerateTokenForUser(User currentOwner) { - if (ADMIN_CAN_GENERATE_NEW_TOKENS && Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - return true; - } - - User currentUser = User.current(); - if (currentUser == null) { - // Anonymous - return false; - } - - if (Jenkins.getAuthentication() == ACL.SYSTEM) { - // SYSTEM user is always eligible to see tokens - return true; - } + LOGGER.log(Level.WARNING, "Deprecated usage of changeApiToken"); - return User.idStrategy().equals(currentOwner.getId(), currentUser.getId()); + _changeApiToken(); + tokenStore.generateTokenFromLegacy(apiToken); + + user.save(); + } + + @Deprecated + private void _changeApiToken(){ + byte[] random = new byte[16]; // 16x8=128bit worth of randomness, since we use md5 digest as the API token + RANDOM.nextBytes(random); + apiToken = Secret.fromString(Util.toHexString(random)); } public static class ConverterImpl extends XStream2.PassthruConverter { @@ -237,6 +282,8 @@ public class ApiTokenProperty extends UserProperty { // support legacy configuration if (apiTokenProperty.apiToken != null) { apiTokenProperty.tokenStore.generateTokenFromLegacy(apiTokenProperty.apiToken); + //TODO save will allow the save of the hash of the legacy token + // addition of a message could be nice in that API to provide more feedback to administrators OldDataMonitor.report(context, "@since TODO"); } @@ -271,11 +318,36 @@ public class ApiTokenProperty extends UserProperty { return null; } - return forceNewInstance(user); + return forceNewInstance(user, true); } - private ApiTokenProperty forceNewInstance(User user) { - return new ApiTokenProperty(API_KEY_SEED.mac(user.getId())); + private ApiTokenProperty forceNewInstance(User user, boolean withLegacyToken) { + if(withLegacyToken){ + return new ApiTokenProperty(API_KEY_SEED.mac(user.getId())); + }else{ + return new ApiTokenProperty(null); + } + } + + // for Jelly view + @Restricted(NoExternalUse.class) + public boolean hasCurrentUserRightToGenerateNewToken(User propertyOwner){ + if (ADMIN_CAN_GENERATE_NEW_TOKENS && Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { + return true; + } + + User currentUser = User.current(); + if (currentUser == null) { + // Anonymous + return false; + } + + if (Jenkins.getAuthentication() == ACL.SYSTEM) { + // SYSTEM user is always eligible to see tokens + return true; + } + + return User.idStrategy().equals(propertyOwner.getId(), currentUser.getId()); } /** @@ -286,24 +358,26 @@ public class ApiTokenProperty extends UserProperty { public HttpResponse doChangeToken(@AncestorInPath User u, StaplerResponse rsp) throws IOException { LOGGER.log(Level.WARNING, "Deprecated action /changeToken used, consider using /generateNewToken instead"); + u.checkPermission(Jenkins.ADMINISTER); + ApiTokenProperty p = u.getProperty(ApiTokenProperty.class); if (p == null) { - p = forceNewInstance(u); + p = forceNewInstance(u, true); u.addProperty(p); + } else { + // even if the user does not have legacy token, this method let some legacy system to regenerate one + p.changeApiToken(); } - String newValue = p.tokenStore.generateNewTokenAndReturnHiddenValue("Created using deprecated method"); - - rsp.setHeader("script","document.getElementById('apiToken').value='"+newValue+"'"); - return HttpResponses.html(p.hasCurrentUserPermissionToGenerateTokenForThisUser() + rsp.setHeader("script","document.getElementById('apiToken').value='"+p.getApiToken()+"'"); + return HttpResponses.html(p.hasPermissionToSeeToken() ? Messages.ApiTokenProperty_ChangeToken_Success() : Messages.ApiTokenProperty_ChangeToken_SuccessHidden()); - } @RequirePOST public HttpResponse doGenerateNewToken(@AncestorInPath User u, StaplerResponse rsp, @QueryParameter String newTokenName) throws IOException { - if(!hasPermissionToGenerateTokenForUser(u)){ + if(!hasCurrentUserRightToGenerateNewToken(u)){ return HttpResponses.forbidden(); } @@ -313,7 +387,7 @@ public class ApiTokenProperty extends UserProperty { ApiTokenProperty p = u.getProperty(ApiTokenProperty.class); if (p == null) { - p = forceNewInstance(u); + p = forceNewInstance(u, false); u.addProperty(p); } @@ -366,16 +440,25 @@ public class ApiTokenProperty extends UserProperty { return HttpResponses.errorWithoutStack(400, "The user does not have any ApiToken yet, try generating one before."); } - p.tokenStore.revokeToken(tokenId); + ApiTokenStore.HashedToken revoked = p.tokenStore.revokeToken(tokenId); + if(revoked != null && revoked.isLegacy()){ + // if the user revoked the API Token, we can delete it + p.apiToken = null; + } u.save(); return HttpResponses.ok(); } } + /** + * Only used for legacy API Token generation and change. After that token is revoked, it will be useless. + */ + @Deprecated + private static final SecureRandom RANDOM = new SecureRandom(); + /** * We don't want an API key that's too long, so cut the length to 16 (which produces 32-letter MAC code in hexdump) - * @deprecated only used for the migration of data from previous save */ @Deprecated private static final HMACConfidentialKey API_KEY_SEED = new HMACConfidentialKey(ApiTokenProperty.class, "seed", 16); diff --git a/core/src/main/java/jenkins/security/ApiTokenPropertyConfiguration.java b/core/src/main/java/jenkins/security/ApiTokenPropertyConfiguration.java index b1a1437c05..58e70530c1 100644 --- a/core/src/main/java/jenkins/security/ApiTokenPropertyConfiguration.java +++ b/core/src/main/java/jenkins/security/ApiTokenPropertyConfiguration.java @@ -24,39 +24,12 @@ package jenkins.security; import hudson.Extension; -import hudson.Util; -import hudson.model.AdministrativeMonitor; -import hudson.model.Descriptor.FormException; -import hudson.model.User; -import hudson.model.UserProperty; -import hudson.model.UserPropertyDescriptor; -import hudson.security.ACL; -import hudson.util.HttpResponses; -import hudson.util.Secret; import jenkins.model.GlobalConfiguration; import jenkins.model.GlobalConfigurationCategory; import jenkins.model.Jenkins; -import jenkins.util.SystemProperties; -import net.sf.json.JSONObject; -import org.apache.commons.lang.StringUtils; import org.jenkinsci.Symbol; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; -import org.kohsuke.stapler.AncestorInPath; -import org.kohsuke.stapler.DataBoundConstructor; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.QueryParameter; -import org.kohsuke.stapler.StaplerRequest; -import org.kohsuke.stapler.StaplerResponse; -import org.kohsuke.stapler.interceptor.RequirePOST; -import org.mindrot.jbcrypt.BCrypt; - -import javax.annotation.Nonnull; -import java.io.IOException; -import java.nio.charset.Charset; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.Date; /** * Configuration for the new token generation when a user is created @@ -68,6 +41,7 @@ import java.util.Date; @Restricted(NoExternalUse.class) public class ApiTokenPropertyConfiguration extends GlobalConfiguration { private boolean tokenGenerationOnCreationDisabled = false; + private boolean creationOfLegacyTokenDisabled = false; public static ApiTokenPropertyConfiguration get() { return Jenkins.get().getInjector().getInstance(ApiTokenPropertyConfiguration.class); @@ -86,6 +60,15 @@ public class ApiTokenPropertyConfiguration extends GlobalConfiguration { save(); } + public boolean isCreationOfLegacyTokenDisabled() { + return creationOfLegacyTokenDisabled; + } + + public void setCreationOfLegacyTokenDisabled(boolean creationOfLegacyTokenDisabled) { + this.creationOfLegacyTokenDisabled = creationOfLegacyTokenDisabled; + save(); + } + @Override public GlobalConfigurationCategory getCategory() { return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class); diff --git a/core/src/main/java/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor.java b/core/src/main/java/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor.java index 9e8b2f42b5..a9629074c4 100644 --- a/core/src/main/java/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor.java +++ b/core/src/main/java/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor.java @@ -41,7 +41,7 @@ import java.io.IOException; * @since TODO */ @Extension -@Symbol("apiToken") +@Symbol("apiTokenLegacyAutoGeneration") @Restricted(NoExternalUse.class) public class ApiTokenPropertyDisabledDefaultAdministrativeMonitor extends AdministrativeMonitor { @Override diff --git a/core/src/main/java/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor.java b/core/src/main/java/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor.java new file mode 100644 index 0000000000..565d481b4f --- /dev/null +++ b/core/src/main/java/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor.java @@ -0,0 +1,66 @@ +/* + * 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.security; + +import hudson.Extension; +import hudson.model.AdministrativeMonitor; +import hudson.util.HttpResponses; +import org.jenkinsci.Symbol; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; +import org.kohsuke.stapler.HttpResponse; +import org.kohsuke.stapler.QueryParameter; +import org.kohsuke.stapler.interceptor.RequirePOST; + +import java.io.IOException; + +/** + * Monitor that the API Token cannot be created for a user without existing legacy token + * + * @since TODO + */ +@Extension +@Symbol("apiTokenNewLegacyWithoutExisting") +@Restricted(NoExternalUse.class) +public class ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor extends AdministrativeMonitor { + @Override + public String getDisplayName() { + return Messages.ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor_displayName(); + } + + @Override + public boolean isActivated() { + return !ApiTokenPropertyConfiguration.get().isCreationOfLegacyTokenDisabled(); + } + + @RequirePOST + public HttpResponse doAct(@QueryParameter String no) throws IOException { + if (no == null) { + ApiTokenPropertyConfiguration.get().setCreationOfLegacyTokenDisabled(true); + } else { + disable(true); + } + return HttpResponses.redirectViaContextPath("manage"); + } +} diff --git a/core/src/main/java/jenkins/security/ApiTokenStore.java b/core/src/main/java/jenkins/security/ApiTokenStore.java index 78fadab1fa..86b3d33b1b 100644 --- a/core/src/main/java/jenkins/security/ApiTokenStore.java +++ b/core/src/main/java/jenkins/security/ApiTokenStore.java @@ -31,6 +31,7 @@ import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.mindrot.jbcrypt.BCrypt; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; import java.security.SecureRandom; @@ -164,14 +165,31 @@ public class ApiTokenStore { } } - public synchronized void generateTokenFromLegacy(@Nonnull Secret apiToken) { - String tokenUserUseNormally = Util.getDigestOf(apiToken.getPlainText()); - + public synchronized void generateTokenFromLegacy(@Nonnull Secret newLegacyApiToken){ + deleteAllLegacyTokens(); + addLegacyToken(newLegacyApiToken); + } + + private void deleteAllLegacyTokens(){ + // normally there is only one, but just in case + for (int i = tokenList.size() - 1; i >= 0; i--) { + HashedToken token = tokenList.get(i); + if (token.isLegacy()) { + tokenList.remove(i); + + removeTokenFromPrefixMap(token); + } + } + } + + private void addLegacyToken(@Nonnull Secret legacyToken){ + String tokenUserUseNormally = Util.getDigestOf(legacyToken.getPlainText()); + String secretValueHashed = this.hashSecret(tokenUserUseNormally); - + HashValue hashValue = new HashValue(LEGACY_VERSION, LEGACY_PREFIX, secretValueHashed); HashedToken token = HashedToken.buildNew(Messages.ApiTokenProperty_LegacyTokenName(), hashValue); - + this.addToken(token); } @@ -285,15 +303,18 @@ public class ApiTokenStore { return false; } - public synchronized void revokeToken(@Nonnull String tokenId) { + public synchronized @CheckForNull HashedToken revokeToken(@Nonnull String tokenId) { for (int i = 0; i < tokenList.size(); i++) { HashedToken token = tokenList.get(i); if (token.uuid.equals(tokenId)) { tokenList.remove(i); removeTokenFromPrefixMap(token); + return token; } } + + return null; } private void removeTokenFromPrefixMap(HashedToken token) { diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/config.jelly b/core/src/main/resources/jenkins/security/ApiTokenProperty/config.jelly index 78503649a4..d1e41a3518 100644 --- a/core/src/main/resources/jenkins/security/ApiTokenProperty/config.jelly +++ b/core/src/main/resources/jenkins/security/ApiTokenProperty/config.jelly @@ -27,13 +27,21 @@ THE SOFTWARE. - +
      -
    • + + + + +
    • + + + + @@ -43,7 +51,7 @@ THE SOFTWARE. ${%TokenCreation(daysOld)} - + @@ -54,10 +62,10 @@ THE SOFTWARE. - + ${%TokenNeverUsed} - + @@ -75,7 +83,8 @@ THE SOFTWARE.
    - + + @@ -88,4 +97,14 @@ THE SOFTWARE. + + + + + + + + + +
    diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/config.properties b/core/src/main/resources/jenkins/security/ApiTokenProperty/config.properties index ba92f439d4..d4536152ee 100644 --- a/core/src/main/resources/jenkins/security/ApiTokenProperty/config.properties +++ b/core/src/main/resources/jenkins/security/ApiTokenProperty/config.properties @@ -7,3 +7,4 @@ ConfirmRevokeSingle=Are you sure you want to revoke this token ? Applications th CurrentTokens=Current tokens TokenCreation=Created {0} day(s) ago RenameToken=Save the new name of the token +LegacyToken=We strongly recommend you to revoke this Legacy API Token as it''s not as secure as the others diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken.html b/core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore.html similarity index 100% rename from core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken.html rename to core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore.html diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken_bg.html b/core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore_bg.html similarity index 100% rename from core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken_bg.html rename to core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore_bg.html diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken_it.html b/core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore_it.html similarity index 100% rename from core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken_it.html rename to core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore_it.html diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken_ja.html b/core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore_ja.html similarity index 100% rename from core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken_ja.html rename to core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore_ja.html diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken_zh_TW.html b/core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore_zh_TW.html similarity index 100% rename from core/src/main/resources/jenkins/security/ApiTokenProperty/help-apiToken_zh_TW.html rename to core/src/main/resources/jenkins/security/ApiTokenProperty/help-tokenStore_zh_TW.html diff --git a/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.css b/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.css index 1d9ac3c405..80723549e0 100644 --- a/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.css +++ b/core/src/main/resources/jenkins/security/ApiTokenProperty/resources.css @@ -38,6 +38,12 @@ ul.token-list li.token-list-item { font-size: 13px; line-height: 26px; } +ul.token-list li.token-list-item.legacy-token { + padding: 8px 5px 8px 5px; + border: 2px solid #ffe262; + border-left-width: 5px; + border-right-width: 5px; +} ul.token-list li.token-list-item.list-empty { display: none; } @@ -60,7 +66,7 @@ ul.token-list li.token-list-item .token-creation{ font-size: 90%; } ul.token-list li.token-list-item .token-creation.age-ok{ - color: #487ba0; + color: #6d7680; } ul.token-list li.token-list-item .token-creation.age-mmmh{ color: #e09307; @@ -76,7 +82,7 @@ ul.token-list li.token-list-item .to-right{ } ul.token-list li.token-list-item .token-use-counter{ font-size: 90%; - color: #586069; + color: #6d7680; } ul.token-list li.token-list-item .token-revoke{ margin-left: 10px; diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/config.jelly b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/config.jelly index 985a5e77b8..7208fa5408 100644 --- a/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/config.jelly +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/config.jelly @@ -27,7 +27,10 @@ THE SOFTWARE. - + + + + diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-creationOfLegacyTokenDisabled.html b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-creationOfLegacyTokenDisabled.html new file mode 100644 index 0000000000..578047e45c --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-creationOfLegacyTokenDisabled.html @@ -0,0 +1,5 @@ +
    + Even if this is disabled (recommended for security reasons), the users are still able to generate new tokens + themselves on their configure page using the new system.
    + It affects only the legacy tokens that are now deprecated. +
    diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-tokenGenerationOnCreationDisabled.html b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-tokenGenerationOnCreationDisabled.html index c0cc87bbed..e8c9bfb99b 100644 --- a/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-tokenGenerationOnCreationDisabled.html +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyConfiguration/help-tokenGenerationOnCreationDisabled.html @@ -1,4 +1,5 @@
    - Even if this is disabled (recommended for security reasons), the users are still able to generate new tokens - themselves on their configure page. + By disabling this capability, the users will be forced to use the new system of Token.
    + This behavior was the legacy one used until the new system was implemented. + It affects only the legacy tokens that are now deprecated.
    diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly index f7899f670c..004d73c999 100644 --- a/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.jelly @@ -25,7 +25,7 @@ THE SOFTWARE.
    - + ${%warningMessage(rootURL)} diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.properties b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.properties index 5638ccb00e..71e8f0ab02 100644 --- a/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.properties +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyDisabledDefaultAdministrativeMonitor/message.properties @@ -1,2 +1,2 @@ -warningMessage=The API Token are currently generated directly when a new user is created. The recommended way is to \ - disable this behavior and let users request new tokens themselves. +warningMessage=A Legacy API Token is currently generated when a new user is created.
    \ + This behavior is just implemented to help users migrate at their own pace but not meant for long-term use. diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor/message.jelly b/core/src/main/resources/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor/message.jelly new file mode 100644 index 0000000000..8aa1bd3d08 --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor/message.jelly @@ -0,0 +1,33 @@ + + + +
    +
    + + + + ${%warningMessage(rootURL)} +
    +
    diff --git a/core/src/main/resources/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor/message.properties b/core/src/main/resources/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor/message.properties new file mode 100644 index 0000000000..0221fc76ee --- /dev/null +++ b/core/src/main/resources/jenkins/security/ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor/message.properties @@ -0,0 +1,2 @@ +warningMessage=Users without Legacy API Token have the capability to create new Legacy API Token.
    \ + This behavior is just implemented to help users migrate at their own pace but not meant for long-term use. diff --git a/core/src/main/resources/jenkins/security/Messages.properties b/core/src/main/resources/jenkins/security/Messages.properties index 1e7d472b43..f6e4f71fc6 100644 --- a/core/src/main/resources/jenkins/security/Messages.properties +++ b/core/src/main/resources/jenkins/security/Messages.properties @@ -26,6 +26,7 @@ ApiTokenProperty.ChangeToken.Success=
    Updated. See the new token in the fiel ApiTokenProperty.ChangeToken.SuccessHidden=
    Updated. You need to login as the user to see the token
    ApiTokenProperty.NewTokenGenerated=
    Token generated! The value will not be displayed anymore, please save it.
    ApiTokenProperty.LegacyTokenName=Legacy Token -ApiTokenPropertyDisabledDefaultAdministrativeMonitor.displayName=API Token not generated by default +ApiTokenPropertyDisabledDefaultAdministrativeMonitor.displayName=Legacy API Token not generated by default +ApiTokenPropertyEnabledNewLegacyAdministrativeMonitor.displayName=Legacy API Token can be created even without existing RekeySecretAdminMonitor.DisplayName=Re-keying UpdateSiteWarningsMonitor.DisplayName=Update Site Warnings diff --git a/test/src/test/java/hudson/cli/CLIActionTest.java b/test/src/test/java/hudson/cli/CLIActionTest.java index 132b0bb885..6738ee0612 100644 --- a/test/src/test/java/hudson/cli/CLIActionTest.java +++ b/test/src/test/java/hudson/cli/CLIActionTest.java @@ -127,7 +127,6 @@ public class CLIActionTest { @Issue({"JENKINS-12543", "JENKINS-41745"}) @Test - @Ignore //TODO need to be fixed public void authentication() throws Exception { logging.record(PlainCLIProtocol.class, Level.FINE); File jar = tmp.newFile("jenkins-cli.jar"); diff --git a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java index 92fb1f986c..6b94e54322 100644 --- a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java +++ b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java @@ -37,7 +37,6 @@ public class ApiTokenPropertyTest { * Tests the UI interaction and authentication. */ @Test - @Ignore //TODO need to be fixed public void basics() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User u = User.getById("foo", true); @@ -68,7 +67,6 @@ public class ApiTokenPropertyTest { } @Test - @Ignore //TODO need to be fixed public void security49Upgrade() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User u = User.get("foo"); @@ -93,7 +91,6 @@ public class ApiTokenPropertyTest { @Issue("SECURITY-200") @Test - @Ignore //TODO need to be fixed public void adminsShouldBeUnableToSeeTokensByDefault() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User u = User.get("foo"); @@ -109,7 +106,6 @@ public class ApiTokenPropertyTest { @Issue("SECURITY-200") @Test - @Ignore //TODO need to be fixed public void adminsShouldBeUnableToChangeTokensByDefault() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User foo = User.get("foo"); diff --git a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java index 9189e051e4..b1bb90381a 100644 --- a/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java +++ b/test/src/test/java/jenkins/security/BasicHeaderProcessorTest.java @@ -45,7 +45,6 @@ public class BasicHeaderProcessorTest { * Tests various ways to send the Basic auth. */ @Test - @Ignore //TODO need to be fixed public void testVariousWaysToCall() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User foo = User.getById("foo", true); @@ -113,7 +112,6 @@ public class BasicHeaderProcessorTest { } @Test - @Ignore //TODO need to be fixed public void testAuthHeaderCaseInSensitive() throws Exception { j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User foo = User.get("foo"); -- GitLab From 91237450bca18b64a3acb3bf02ef165d2d86db57 Mon Sep 17 00:00:00 2001 From: Baptiste Mathus Date: Mon, 29 Jan 2018 11:55:44 +0100 Subject: [PATCH 054/863] Disable test temporarily to get the release out Note: this test is wrong, the production code is correct. So that is why disabling this test until we fix it is OK. --- core/src/test/java/hudson/util/AtomicFileWriterTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/test/java/hudson/util/AtomicFileWriterTest.java b/core/src/test/java/hudson/util/AtomicFileWriterTest.java index 8d231c487b..949964eb64 100644 --- a/core/src/test/java/hudson/util/AtomicFileWriterTest.java +++ b/core/src/test/java/hudson/util/AtomicFileWriterTest.java @@ -3,6 +3,7 @@ package hudson.util; import org.apache.commons.io.FileUtils; import org.hamcrest.core.StringContains; import org.junit.Before; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -141,6 +142,7 @@ public class AtomicFileWriterTest { } } + @Ignore // Need to fix the testing done here, since it assumes umask=002, which is wrong... Including on Kohsuke's release envt :-\. @Issue("JENKINS-48407") @Test public void checkPermissions() throws IOException, InterruptedException { -- GitLab From 4176b0c05ec37291c41532ad1b41719ce71c56d6 Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 29 Jan 2018 03:35:21 -0800 Subject: [PATCH 055/863] [maven-release-plugin] prepare release jenkins-2.104 --- 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 5ccf113f0c..8dddcb25f5 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.104-SNAPSHOT + 2.104 cli diff --git a/core/pom.xml b/core/pom.xml index ce9d90f5ab..847a454a44 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.104-SNAPSHOT + 2.104 jenkins-core diff --git a/pom.xml b/pom.xml index a7f4b0a388..eea0aacda5 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.104-SNAPSHOT + 2.104 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.104 diff --git a/test/pom.xml b/test/pom.xml index 1328f05bb5..75ff48b5f7 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.104-SNAPSHOT + 2.104 test diff --git a/war/pom.xml b/war/pom.xml index b1cd21a8e9..9f79413192 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.104-SNAPSHOT + 2.104 jenkins-war -- GitLab From 53b9bdff752b97bece30d450697852cbf8089c6d Mon Sep 17 00:00:00 2001 From: Kohsuke Kawaguchi Date: Mon, 29 Jan 2018 03:35:21 -0800 Subject: [PATCH 056/863] [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 8dddcb25f5..e306fa9f74 100644 --- a/cli/pom.xml +++ b/cli/pom.xml @@ -5,7 +5,7 @@ org.jenkins-ci.main pom - 2.104 + 2.105-SNAPSHOT cli diff --git a/core/pom.xml b/core/pom.xml index 847a454a44..1259e93805 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -29,7 +29,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.104 + 2.105-SNAPSHOT jenkins-core diff --git a/pom.xml b/pom.xml index eea0aacda5..b49d34a3e9 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.104 + 2.105-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.104 + HEAD diff --git a/test/pom.xml b/test/pom.xml index 75ff48b5f7..79cb741f3c 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.104 + 2.105-SNAPSHOT test diff --git a/war/pom.xml b/war/pom.xml index 9f79413192..8293af9dcf 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci.main pom - 2.104 + 2.105-SNAPSHOT jenkins-war -- GitLab From d0110590fc1ae2bdcd3acc925de41b9a279b5a07 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Mon, 29 Jan 2018 15:58:20 +0100 Subject: [PATCH 057/863] - add a short-living cache for the hash with just sha256(salt + plainToken) to speed up things after the first "complete" check --- .../jenkins/security/ApiTokenProperty.java | 16 +- .../java/jenkins/security/ApiTokenStore.java | 173 +++++++++++++++++- 2 files changed, 179 insertions(+), 10 deletions(-) diff --git a/core/src/main/java/jenkins/security/ApiTokenProperty.java b/core/src/main/java/jenkins/security/ApiTokenProperty.java index f7e28b010d..18f4d35d9c 100644 --- a/core/src/main/java/jenkins/security/ApiTokenProperty.java +++ b/core/src/main/java/jenkins/security/ApiTokenProperty.java @@ -24,6 +24,7 @@ package jenkins.security; import com.thoughtworks.xstream.converters.UnmarshallingContext; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Extension; import hudson.Util; import hudson.diagnosis.OldDataMonitor; @@ -74,15 +75,12 @@ import org.kohsuke.stapler.interceptor.RequirePOST; public class ApiTokenProperty extends UserProperty { private static final Logger LOGGER = Logger.getLogger(ApiTokenProperty.class.getName()); - private volatile Secret apiToken; - private ApiTokenStore tokenStore; - /** * If enabled, the users with {@link Jenkins#ADMINISTER) permissions can generate new tokens for * other users. Normally only a user can generate tokens for himself. * Disabled by default due to the security reasons. * If enabled, it restores the original Jenkins behavior (SECURITY-200). - * + * * @since 1.638 */ private static final boolean SHOW_LEGACY_TOKEN_TO_ADMINS = @@ -90,6 +88,9 @@ public class ApiTokenProperty extends UserProperty { private static final boolean ADMIN_CAN_GENERATE_NEW_TOKENS = SystemProperties.getBoolean(ApiTokenProperty.class.getName() + ".adminCanGenerateNewTokens"); + + private volatile Secret apiToken; + private ApiTokenStore tokenStore; @DataBoundConstructor public ApiTokenProperty() { @@ -130,13 +131,16 @@ public class ApiTokenProperty extends UserProperty { * @since 1.426, and since 1.638 the method performs security checks */ @Nonnull + @SuppressFBWarnings("NP_NONNULL_RETURN_VIOLATION") public String getApiToken() { LOGGER.log(Level.WARNING, "Deprecated usage of getApiToken"); if(this.apiToken == null){ return "deprecated"; } - - return hasPermissionToSeeToken() ? getApiTokenInsecure() : Messages.ApiTokenProperty_ChangeToken_TokenIsHidden(); + + return hasPermissionToSeeToken() + ? getApiTokenInsecure() + : Messages.ApiTokenProperty_ChangeToken_TokenIsHidden(); } @Nonnull diff --git a/core/src/main/java/jenkins/security/ApiTokenStore.java b/core/src/main/java/jenkins/security/ApiTokenStore.java index 86b3d33b1b..797a0f2945 100644 --- a/core/src/main/java/jenkins/security/ApiTokenStore.java +++ b/core/src/main/java/jenkins/security/ApiTokenStore.java @@ -23,6 +23,7 @@ */ package jenkins.security; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Util; import hudson.diagnosis.OldDataMonitor; import hudson.util.Secret; @@ -34,10 +35,15 @@ import org.mindrot.jbcrypt.BCrypt; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.concurrent.Immutable; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.time.Instant; +import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; +import java.util.Base64; import java.util.Comparator; import java.util.Date; import java.util.HashMap; @@ -60,12 +66,14 @@ public class ApiTokenStore { * default value corresponds to * BCrypt#GENSALT_DEFAULT_LOG2_ROUNDS is 10 which is way too small in 2018 */ - private static final int BCRYPT_LOG_ROUND = + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts") + public static int BCRYPT_LOG_ROUND = SystemProperties.getInteger(ApiTokenStore.class.getName() + ".bcryptLogRound", 13); /** * Determine the number of attempt to generate an unique prefix (over 4096 possibilities) that is not currently used */ - private static final int MAX_ATTEMPTS = + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts") + public static final int MAX_ATTEMPTS = SystemProperties.getInteger(ApiTokenStore.class.getName() + ".maxAttempt", 100); private static final int TOKEN_LENGTH_V2 = 36; @@ -74,6 +82,12 @@ public class ApiTokenStore { private static final String HASH_VERSION = "2"; private static final String LEGACY_PREFIX = ""; + + /** + * Cache the computation of hash in order to speed up API call that were already verified some times ago + * Does not keep deleted data alive, just optimize the computation of the hashes. + */ + private static final HashCache HASH_CACHE = new HashCache(); private List tokenList; private transient Map> prefixToTokenList; @@ -94,6 +108,7 @@ public class ApiTokenStore { this.prefixToTokenList = new HashMap<>(); } + @SuppressFBWarnings("NP_NONNULL_RETURN_VIOLATION") public synchronized @Nonnull List getTokenListSortedByName() { List sortedTokenList = tokenList.stream() .sorted(SORT_BY_LOWERCASED_NAME) @@ -212,8 +227,10 @@ public class ApiTokenStore { return tokenTheUserWillUse; } + @SuppressFBWarnings("NP_NONNULL_RETURN_VIOLATION") private @Nonnull String hashSecret(@Nonnull String secretValue) { - return BCrypt.hashpw(secretValue, BCrypt.gensalt(BCRYPT_LOG_ROUND, RANDOM)); + String salt = BCrypt.gensalt(BCRYPT_LOG_ROUND, RANDOM); + return BCrypt.hashpw(secretValue, salt); } private @Nonnull String generatePrefix() { @@ -239,8 +256,10 @@ public class ApiTokenStore { /** * Generate random 3-hex-character */ + @SuppressFBWarnings("NP_NONNULL_RETURN_VIOLATION") private @Nonnull String generateRandomPrefix() { int prefixInteger = RANDOM.nextInt(4096); + String prefixString = Integer.toHexString(prefixInteger); return StringUtils.leftPad(prefixString, 3, '0'); } @@ -289,11 +308,27 @@ public class ApiTokenStore { } private boolean searchMatchUsingPrefix(String prefix, String plainToken) { + String plainTokenCacheKey = HASH_CACHE.getCorrespondingCacheKey(plainToken); + String uuidFromCache = HASH_CACHE.getCachedUuid(plainTokenCacheKey); + if(uuidFromCache != null){ + for (HashedToken token : tokenList) { + if (token.uuid.equals(uuidFromCache)) { + LOGGER.log(Level.FINER, "Cache hit for prefix = ", prefix); + token.incrementUse(); + HASH_CACHE.insertOrRefreshCache(plainTokenCacheKey, token.uuid); + return true; + } + } + } + LOGGER.log(Level.FINER, "Cache miss for prefix = ", prefix); + Node node = this.prefixToTokenList.get(prefix); while (node != null) { boolean matchFound = node.value.match(plainToken); if (matchFound) { node.value.incrementUse(); + + HASH_CACHE.insertOrRefreshCache(plainTokenCacheKey, node.value.uuid); return true; } else { node = node.next; @@ -421,7 +456,10 @@ public class ApiTokenStore { public void rename(String newName) { this.name = newName; } - + + /** + * This operation should take some time (between 100ms and 1s) + */ public boolean match(String plainToken) { return BCrypt.checkpw(plainToken, value.hash); } @@ -466,4 +504,131 @@ public class ApiTokenStore { this.name = name; } } + + public static class HashCache { + /** + * A "session" duration, meaning we do not compute the long hash each time but use a weak hash instead after the first success + */ + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts") + public static long HASH_CACHE_TIMEOUT_IN_MS = + SystemProperties.getLong(ApiTokenStore.class.getName() + ".hashCacheTimeout", (long)(5 * 60 * 1000)); + + /** + * At minimum we compute the long hash every 30 minutes + */ + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts") + public static long HASH_CACHE_MAX_LIVETIME_IN_MS = + SystemProperties.getLong(ApiTokenStore.class.getName() + ".hashCacheMaxLivetime", (long)(30 * 60 * 1000)); + + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts") + public static boolean HASH_CACHE_DISABLED = + SystemProperties.getBoolean(ApiTokenStore.class.getName() + ".hashCacheDisabled", false); + + @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts") + public static int HASH_CACHE_MIN_SIZE_FOR_CLEANUP = + SystemProperties.getInteger(ApiTokenStore.class.getName() + ".hashCacheMinSizeForCleanup", 20); + + private static final byte[] HASH_CACHE_SALT; + static { + // no requirement to keep it between restart since the cache is temporary + SecureRandom random = new SecureRandom(); + HASH_CACHE_SALT = new byte[16]; + random.nextBytes(HASH_CACHE_SALT); + } + + private static final String HASH_ALGORITHM = "SHA-256"; + + private final Map cache = new HashMap<>(); + + @SuppressFBWarnings("NP_NONNULL_RETURN_VIOLATION") + public @Nonnull String getCorrespondingCacheKey(String plainToken){ + if(HASH_CACHE_DISABLED){ + return "cache-disabled"; + } + byte[] hashedTokenBytes = hashWithSalt(plainToken.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(hashedTokenBytes); + } + + @SuppressFBWarnings("NP_NONNULL_RETURN_VIOLATION") + private @Nonnull byte[] hashWithSalt(byte[] tokenBytes){ + MessageDigest digest; + try { + digest = MessageDigest.getInstance(HASH_ALGORITHM); + } catch (NoSuchAlgorithmException e) { + throw new AssertionError("There is no " + HASH_ALGORITHM + " available in this system"); + } + digest.update(HASH_CACHE_SALT); + return digest.digest(tokenBytes); + } + + public synchronized @CheckForNull String getCachedUuid(String cacheKey){ + if(HASH_CACHE_DISABLED){ + return null; + } + + cleanup(); + + if(cache.containsKey(cacheKey)){ + CacheEntry entry = cache.get(cacheKey); + if(entry != null){ + if(entry.hasExpired()){ + cache.remove(cacheKey); + }else{ + return entry.data; + } + } + } + + return null; + } + + private void cleanup(){ + if(cache.size() < HASH_CACHE_MIN_SIZE_FOR_CLEANUP){ + return; + } + + cache.entrySet().removeIf(entry -> entry.getValue().hasExpired()); + } + + public synchronized void insertOrRefreshCache(String cacheKey, String uuid){ + if(HASH_CACHE_DISABLED){ + return; + } + + CacheEntry entry = cache.get(cacheKey); + if(entry == null){ + cache.put(cacheKey, new CacheEntry(uuid)); + }else{ + entry.touch(); + } + } + + private static class CacheEntry { + LocalDateTime firstCheck; + LocalDateTime lastCheck; + String data; + + CacheEntry(String data){ + this.data = data; + this.firstCheck = this.lastCheck = LocalDateTime.now(); + } + + public void touch(){ + this.lastCheck = LocalDateTime.now(); + } + + public boolean hasExpired(){ + LocalDateTime now = LocalDateTime.now(); + if(now.isAfter(lastCheck.plus(HASH_CACHE_TIMEOUT_IN_MS, ChronoUnit.MILLIS))){ + // not recently used + return true; + } + if(now.isAfter(firstCheck.plus(HASH_CACHE_MAX_LIVETIME_IN_MS, ChronoUnit.MILLIS))){ + // full check required after a certain time + return true; + } + return false; + } + } + } } -- GitLab From a2377c747622b560eb6a0ee91e80c2507bf98511 Mon Sep 17 00:00:00 2001 From: Wadeck Follonier Date: Mon, 29 Jan 2018 16:28:23 +0100 Subject: [PATCH 058/863] - fix for test --- .../jenkins/security/ApiTokenPropertyTest.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java index 6b94e54322..77a3376121 100644 --- a/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java +++ b/test/src/test/java/jenkins/security/ApiTokenPropertyTest.java @@ -14,6 +14,8 @@ import hudson.model.User; import hudson.security.ACL; import hudson.security.ACLContext; import java.net.URL; + +import hudson.util.Scrambler; import jenkins.model.Jenkins; import org.junit.Ignore; import org.junit.Rule; @@ -107,6 +109,7 @@ public class ApiTokenPropertyTest { @Issue("SECURITY-200") @Test public void adminsShouldBeUnableToChangeTokensByDefault() throws Exception { + j.jenkins.setCrumbIssuer(null); j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); User foo = User.get("foo"); User bar = User.get("bar"); @@ -131,16 +134,14 @@ public class ApiTokenPropertyTest { @Nonnull private WebClient createClientForUser(final String id) throws Exception { - User u = User.getById(id, false); - - //TODO to be fixed -// final ApiTokenProperty t = u.getProperty(ApiTokenProperty.class); -// // Yes, we use the insecure call in the test stuff -// final String token = t.getApiTokenInsecure(); + User u = User.getById(id, true); + final ApiTokenProperty t = u.getProperty(ApiTokenProperty.class); + // Yes, we use the insecure call in the test stuff + final String token = t.getApiTokenInsecure(); WebClient wc = j.createWebClient(); -// wc.addRequestHeader("Authorization", "Basic " + Scrambler.scramble(id + ":" + token)); + wc.addRequestHeader("Authorization", "Basic " + Scrambler.scramble(id + ":" + token)); return wc; } -- GitLab From 5f0f9f2b87be0a2d2c1dfb34df59db117f9ea90f Mon Sep 17 00:00:00 2001 From: mike cirioli Date: Tue, 30 Jan 2018 09:31:15 -0500 Subject: [PATCH 059/863] [JENKINS-48463] added try-with-resources to XmlFileTest to ensure proper file handle cleanup in the event of failure Fixed small nits regarding typo's and code cleanup Added comment explaining why Xpp3 is still included as a dep --- core/pom.xml | 10 +++++++--- core/src/main/java/hudson/util/XStream2.java | 2 +- core/src/test/java/hudson/XmlFileTest.java | 12 ++++-------- test/src/test/java/hudson/XMLFileTest.java | 10 ++++++---- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 9c4faab34d..a0752203b3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -246,15 +246,19 @@ THE SOFTWARE. + xpp3 xpp3 1.1.4c - net.sf.kxml - kxml2 - 2.3.0 + net.sf.kxml + kxml2 + 2.3.0 jfree diff --git a/core/src/main/java/hudson/util/XStream2.java b/core/src/main/java/hudson/util/XStream2.java index 13d0b359c9..4f90342ea9 100644 --- a/core/src/main/java/hudson/util/XStream2.java +++ b/core/src/main/java/hudson/util/XStream2.java @@ -99,7 +99,7 @@ public class XStream2 extends XStream { private MapperInjectionPoint mapperInjectionPoint; /** - * Convinience method so we only have to change the driver in one place + * Convenience method so we only have to change the driver in one place * if we switch to something new in the future * * @return a new instance of the HierarchicalStreamDriver we want to use diff --git a/core/src/test/java/hudson/XmlFileTest.java b/core/src/test/java/hudson/XmlFileTest.java index f4c3c42e02..69c85a93f7 100644 --- a/core/src/test/java/hudson/XmlFileTest.java +++ b/core/src/test/java/hudson/XmlFileTest.java @@ -20,11 +20,10 @@ public class XmlFileTest { @Test public void canReadXml1_0Test() throws IOException { URL configUrl = getClass().getResource("/hudson/config_1_0.xml"); - File configFile = new File(configUrl.getFile()); XStream2 xs = new XStream2(); xs.alias("hudson", Jenkins.class); - XmlFile xmlFile = new XmlFile(xs, configFile); + XmlFile xmlFile = new XmlFile(xs, new File(configUrl.getFile())); if (xmlFile.exists()) { Node n = (Node) xmlFile.read(); assertThat(n.getNumExecutors(), is(2)); @@ -38,11 +37,10 @@ public class XmlFileTest { @Test(expected = SAXParseException.class) public void xml1_0_withSpecialCharsShouldFail() throws IOException { URL configUrl = getClass().getResource("/hudson/config_1_0_with_special_chars.xml"); - File configFile = new File(configUrl.getFile()); XStream2 xs = new XStream2(); xs.alias("hudson", Jenkins.class); - XmlFile xmlFile = new XmlFile(xs, configFile); + XmlFile xmlFile = new XmlFile(xs, new File(configUrl.getFile())); if (xmlFile.exists()) { Node n = (Node) xmlFile.read(); assertThat(n.getNumExecutors(), is(2)); @@ -53,11 +51,10 @@ public class XmlFileTest { @Test public void canReadXml1_1Test() throws IOException { URL configUrl = getClass().getResource("/hudson/config_1_1.xml"); - File configFile = new File(configUrl.getFile()); XStream2 xs = new XStream2(); xs.alias("hudson", Jenkins.class); - XmlFile xmlFile = new XmlFile(xs, configFile); + XmlFile xmlFile = new XmlFile(xs, new File(configUrl.getFile())); if (xmlFile.exists()) { Node n = (Node) xmlFile.read(); assertThat(n.getNumExecutors(), is(2)); @@ -68,11 +65,10 @@ public class XmlFileTest { @Test public void canReadXmlWithControlCharsTest() throws IOException { URL configUrl = getClass().getResource("/hudson/config_1_1_with_special_chars.xml"); - File configFile = new File(configUrl.getFile()); XStream2 xs = new XStream2(); xs.alias("hudson", Jenkins.class); - XmlFile xmlFile = new XmlFile(xs, configFile); + XmlFile xmlFile = new XmlFile(xs, new File(configUrl.getFile())); if (xmlFile.exists()) { Node n = (Node) xmlFile.read(); assertThat(n.getNumExecutors(), is(2)); diff --git a/test/src/test/java/hudson/XMLFileTest.java b/test/src/test/java/hudson/XMLFileTest.java index b384ee7dfb..1b3a9ff3ea 100644 --- a/test/src/test/java/hudson/XMLFileTest.java +++ b/test/src/test/java/hudson/XMLFileTest.java @@ -36,10 +36,12 @@ public class XMLFileTest { // verify that we did indeed load our test config.xml assertThat(j.jenkins.getLabelString(), is("I am a label")); //verify that the persisted top level config.xml is v1.1 - File configFile = new File(j.jenkins.getRootPath().getRemote() + File.separator + "config.xml"); + File configFile = new File(j.jenkins.getRootDir(), "config.xml"); assertThat(configFile.exists(), is(true)); - BufferedReader config = new BufferedReader(new FileReader(configFile)); - assertThat(config.readLine(), is("")); - config.close(); + + try (BufferedReader config = new BufferedReader(new FileReader(configFile))) { + assertThat(config.readLine(), is("")); + config.close(); + } } } -- GitLab From 2eda7816e8007851ced3d618cd0fe42c78a486e2 Mon Sep 17 00:00:00 2001 From: Kseniia Nenasheva Date: Tue, 30 Jan 2018 17:33:53 +0100 Subject: [PATCH 060/863] [JENKINS-47793] - Jenkins master and agent configuration pages do not verify negative executor numbers (#3141) * Fixed Regexps for number & positive-number * Progress saving * Fix for validation * bug fixing * bug fixing * Address comments * Address comments * Address comments from @jglick * Whitespace cleanup * Address comment for @jglick --- core/src/main/java/hudson/model/Computer.java | 5 +++++ core/src/main/java/jenkins/model/Jenkins.java | 12 +++++++++++- .../java/jenkins/model/MasterBuildConfiguration.java | 5 +++++ .../model/Jenkins/MasterComputer/configure.jelly | 2 +- .../model/MasterBuildConfiguration/config.groovy | 2 +- .../main/resources/jenkins/model/Messages.properties | 1 + war/src/main/webapp/scripts/hudson-behavior.js | 7 +++++-- 7 files changed, 29 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/hudson/model/Computer.java b/core/src/main/java/hudson/model/Computer.java index 43fa2d31d7..dff02c5ab1 100644 --- a/core/src/main/java/hudson/model/Computer.java +++ b/core/src/main/java/hudson/model/Computer.java @@ -1472,6 +1472,11 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces throw new FormException(Messages.ComputerSet_SlaveAlreadyExists(proposedName), "name"); } + String nExecutors = req.getSubmittedForm().getString("numExecutors"); + if (Integer.parseInt(nExecutors)<=0) { + throw new FormException(Messages.Slave_InvalidConfig_Executors(nodeName), "numExecutors"); + } + Node result = node.reconfigure(req, req.getSubmittedForm()); Jenkins.getInstance().getNodesObject().replaceNode(this.getNode(), result); diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 01951e03f9..15a4edce00 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -290,6 +290,7 @@ import static hudson.init.InitMilestone.*; import hudson.init.Initializer; import hudson.util.LogTaskListener; import static java.util.logging.Level.*; +import javax.annotation.Nonnegative; import static javax.servlet.http.HttpServletResponse.*; import org.kohsuke.stapler.WebMethod; @@ -2713,7 +2714,16 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve return initLevel; } - public void setNumExecutors(int n) throws IOException { + /** + * Sets a number of executors. + * @param n Number of executors + * @throws IOException Failed to save the configuration + * @throws IllegalArgumentException Negative value has been passed + */ + public void setNumExecutors(@Nonnegative int n) throws IOException, IllegalArgumentException { + if (n < 0) { + throw new IllegalArgumentException("Incorrect field \"# of executors\": " + n +". It should be a non-negative number."); + } if (this.numExecutors != n) { this.numExecutors = n; updateComputerList(); diff --git a/core/src/main/java/jenkins/model/MasterBuildConfiguration.java b/core/src/main/java/jenkins/model/MasterBuildConfiguration.java index 6ae797adf2..3bc348419c 100644 --- a/core/src/main/java/jenkins/model/MasterBuildConfiguration.java +++ b/core/src/main/java/jenkins/model/MasterBuildConfiguration.java @@ -51,6 +51,11 @@ public class MasterBuildConfiguration extends GlobalConfiguration { Jenkins j = Jenkins.getInstance(); try { // for compatibility reasons, this value is stored in Jenkins + String num = json.getString("numExecutors"); + if (!num.matches("\\d+")) { + throw new FormException(Messages.Hudson_Computer_IncorrectNumberOfExecutors(),"numExecutors"); + } + j.setNumExecutors(json.getInt("numExecutors")); if (req.hasParameter("master.mode")) j.setMode(Mode.valueOf(req.getParameter("master.mode"))); diff --git a/core/src/main/resources/jenkins/model/Jenkins/MasterComputer/configure.jelly b/core/src/main/resources/jenkins/model/Jenkins/MasterComputer/configure.jelly index 19ad03f335..e06cc9a7a9 100644 --- a/core/src/main/resources/jenkins/model/Jenkins/MasterComputer/configure.jelly +++ b/core/src/main/resources/jenkins/model/Jenkins/MasterComputer/configure.jelly @@ -42,7 +42,7 @@ THE SOFTWARE. --> - + diff --git a/core/src/main/resources/jenkins/model/MasterBuildConfiguration/config.groovy b/core/src/main/resources/jenkins/model/MasterBuildConfiguration/config.groovy index 8b13c1ab11..f645dcbc41 100644 --- a/core/src/main/resources/jenkins/model/MasterBuildConfiguration/config.groovy +++ b/core/src/main/resources/jenkins/model/MasterBuildConfiguration/config.groovy @@ -3,7 +3,7 @@ package jenkins.model.MasterBuildConfiguration def f=namespace(lib.FormTagLib) f.entry(title:_("# of executors"), field:"numExecutors") { - f.number(clazz:"number", min:0, step:1) + f.number(clazz:"non-negative-number", min:0, step:1) } f.entry(title:_("Labels"),field:"labelString") { f.textbox() diff --git a/core/src/main/resources/jenkins/model/Messages.properties b/core/src/main/resources/jenkins/model/Messages.properties index adf5c9da94..f7d11a855f 100644 --- a/core/src/main/resources/jenkins/model/Messages.properties +++ b/core/src/main/resources/jenkins/model/Messages.properties @@ -26,6 +26,7 @@ Hudson.BadPortNumber=Bad port number {0} Hudson.Computer.Caption=Master Hudson.Computer.DisplayName=master +Hudson.Computer.IncorrectNumberOfExecutors=Incorrect field "# of executors". It should be a non-negative number. Hudson.ControlCodeNotAllowed=No control code is allowed: {0} Hudson.DisplayName=Jenkins Hudson.JobAlreadyExists=A job already exists with the name \u2018{0}\u2019 diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js index d8989d5dea..01c1b21861 100644 --- a/war/src/main/webapp/scripts/hudson-behavior.js +++ b/war/src/main/webapp/scripts/hudson-behavior.js @@ -694,9 +694,12 @@ var jenkinsRules = { "INPUT.required" : function(e) { registerRegexpValidator(e,/./,"Field is required"); }, // validate form values to be an integer - "INPUT.number" : function(e) { registerRegexpValidator(e,/^(\d+|)$/,"Not an integer"); }, + "INPUT.number" : function(e) { registerRegexpValidator(e,/^\-?(\d+)$/,"Not an integer"); }, + "INPUT.non-negative-number" : function(e) { + registerRegexpValidator(e,/^\d+$/,"Not a non-negative number"); + }, "INPUT.positive-number" : function(e) { - registerRegexpValidator(e,/^(\d*[1-9]\d*|)$/,"Not a positive integer"); + registerRegexpValidator(e,/^[1-9]\d*$/,"Not a positive integer"); }, "INPUT.auto-complete": function(e) {// form field with auto-completion support -- GitLab From 2d16b459205730d85e51499c2457109b234ca9d9 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 30 Jan 2018 12:15:48 -0500 Subject: [PATCH 061/863] [SECURITY-506] Require admin permission to validate proxy config. --- core/src/main/java/hudson/ProxyConfiguration.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java index 7c2809c53f..408d5e9c4f 100644 --- a/core/src/main/java/hudson/ProxyConfiguration.java +++ b/core/src/main/java/hudson/ProxyConfiguration.java @@ -341,6 +341,8 @@ public final class ProxyConfiguration extends AbstractDescribableImpl Date: Tue, 30 Jan 2018 12:16:42 -0500 Subject: [PATCH 062/863] [SECURITY-705] Path traversal vulnerability in Plugin.doDynamic. --- core/src/main/java/hudson/Plugin.java | 23 ++++++++++------------- test/src/test/java/hudson/PluginTest.java | 15 ++++++++++----- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/hudson/Plugin.java b/core/src/main/java/hudson/Plugin.java index bfe8e0fbe4..b955a6d3ad 100644 --- a/core/src/main/java/hudson/Plugin.java +++ b/core/src/main/java/hudson/Plugin.java @@ -35,6 +35,7 @@ import org.kohsuke.stapler.StaplerResponse; import javax.servlet.ServletContext; import javax.servlet.ServletException; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.File; @@ -42,10 +43,9 @@ import net.sf.json.JSONObject; import com.thoughtworks.xstream.XStream; import hudson.init.Initializer; import hudson.init.Terminator; -import java.net.URI; -import java.net.URISyntaxException; +import java.net.URL; +import java.util.logging.Logger; import jenkins.model.GlobalConfiguration; -import org.kohsuke.stapler.HttpResponses; /** * Base class of Hudson plugin. @@ -81,6 +81,8 @@ import org.kohsuke.stapler.HttpResponses; */ public abstract class Plugin implements Saveable { + private static final Logger LOGGER = Logger.getLogger(Plugin.class.getName()); + /** * You do not need to create custom subtypes: *
      @@ -224,13 +226,12 @@ public abstract class Plugin implements Saveable { public void doDynamic(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException { String path = req.getRestOfPath(); - if (path.startsWith("/META-INF/") || path.startsWith("/WEB-INF/")) { - throw HttpResponses.notFound(); + if (path.isEmpty() || path.contains("..") || path.contains("%") || path.contains("META-INF") || path.contains("WEB-INF")) { + LOGGER.warning("rejecting possibly malicious " + req.getRequestURIWithQueryString()); + rsp.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; } - if(path.length()==0) - path = "/"; - // Stapler routes requests like the "/static/.../foo/bar/zot" to be treated like "/foo/bar/zot" // and this is used to serve long expiration header, by using Jenkins.VERSION_HASH as "..." // to create unique URLs. Recognize that and set a long expiration header. @@ -240,11 +241,7 @@ public abstract class Plugin implements Saveable { long expires = staticLink ? TimeUnit2.DAYS.toMillis(365) : -1; // use serveLocalizedFile to support automatic locale selection - try { - rsp.serveLocalizedFile(req, wrapper.baseResourceURL.toURI().resolve(new URI(null, '.' + path, null)).toURL(), expires); - } catch (URISyntaxException x) { - throw new IOException(x); - } + rsp.serveLocalizedFile(req, new URL(wrapper.baseResourceURL, '.' + path), expires); } // diff --git a/test/src/test/java/hudson/PluginTest.java b/test/src/test/java/hudson/PluginTest.java index 19cd52bac7..dd7026406b 100644 --- a/test/src/test/java/hudson/PluginTest.java +++ b/test/src/test/java/hudson/PluginTest.java @@ -34,18 +34,23 @@ public class PluginTest { @Rule public JenkinsRule r = new JenkinsRule(); - @Issue({"SECURITY-131", "SECURITY-155"}) + @Issue({"SECURITY-131", "SECURITY-155", "SECURITY-705"}) @Test public void doDynamic() throws Exception { r.createWebClient().goTo("plugin/credentials/images/24x24/credentials.png", "image/png"); /* Collapsed somewhere before it winds up in restOfPath: r.createWebClient().assertFails("plugin/credentials/images/../images/24x24/credentials.png", HttpServletResponse.SC_BAD_REQUEST); */ r.createWebClient().assertFails("plugin/credentials/images/%2E%2E/images/24x24/credentials.png", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); // IAE from TokenList. - r.createWebClient().assertFails("plugin/credentials/images/%252E%252E/images/24x24/credentials.png", HttpServletResponse.SC_NOT_FOUND); // SECURITY-131 - r.createWebClient().assertFails("plugin/credentials/images/%25252E%25252E/images/24x24/credentials.png", HttpServletResponse.SC_NOT_FOUND); // just checking + r.createWebClient().assertFails("plugin/credentials/images/%252E%252E/images/24x24/credentials.png", HttpServletResponse.SC_BAD_REQUEST); // SECURITY-131 + r.createWebClient().assertFails("plugin/credentials/images/%25252E%25252E/images/24x24/credentials.png", HttpServletResponse.SC_BAD_REQUEST); // just checking + // SECURITY-705: + r.createWebClient().assertFails("plugin/credentials/images/..%2fWEB-INF/licenses.xml", HttpServletResponse.SC_BAD_REQUEST); + r.createWebClient().assertFails("plugin/credentials/images/%2e%2e%2fWEB-INF/licenses.xml", HttpServletResponse.SC_BAD_REQUEST); + r.createWebClient().assertFails("plugin/credentials/images/%2e.%2fWEB-INF/licenses.xml", HttpServletResponse.SC_BAD_REQUEST); + r.createWebClient().assertFails("plugin/credentials/images/..%2f..%2f..%2f" + r.jenkins.getRootDir().getName() + "%2fsecrets%2fmaster.key", HttpServletResponse.SC_BAD_REQUEST); // SECURITY-155: - r.createWebClient().assertFails("plugin/credentials/WEB-INF/licenses.xml", HttpServletResponse.SC_NOT_FOUND); - r.createWebClient().assertFails("plugin/credentials/META-INF/MANIFEST.MF", HttpServletResponse.SC_NOT_FOUND); + r.createWebClient().assertFails("plugin/credentials/WEB-INF/licenses.xml", HttpServletResponse.SC_BAD_REQUEST); + r.createWebClient().assertFails("plugin/credentials/META-INF/MANIFEST.MF", HttpServletResponse.SC_BAD_REQUEST); } } -- GitLab From 38249b561b61564830dcfe937351e54d53f5d394 Mon Sep 17 00:00:00 2001 From: Jesse Glick Date: Tue, 30 Jan 2018 12:18:09 -0500 Subject: [PATCH 063/863] [SECURITY-624] Stored XSS vulnerability in tool names. --- core/src/main/resources/lib/form/option.jelly | 2 +- test/src/test/java/lib/form/OptionTest.java | 393 ++++++++++++++++++ .../OptionTest/UsingGroovyView/index.groovy | 64 +++ .../OptionTest/UsingJellyView/index.jelly | 61 +++ 4 files changed, 519 insertions(+), 1 deletion(-) create mode 100644 test/src/test/java/lib/form/OptionTest.java create mode 100644 test/src/test/resources/lib/form/OptionTest/UsingGroovyView/index.groovy create mode 100644 test/src/test/resources/lib/form/OptionTest/UsingJellyView/index.jelly diff --git a/core/src/main/resources/lib/form/option.jelly b/core/src/main/resources/lib/form/option.jelly index ef6e7d9155..a5c779da29 100644 --- a/core/src/main/resources/lib/form/option.jelly +++ b/core/src/main/resources/lib/form/option.jelly @@ -39,5 +39,5 @@ THE SOFTWARE. + selected="${attrs.selected?'true':null}">${h.xmlUnescape(optionBody)} diff --git a/test/src/test/java/lib/form/OptionTest.java b/test/src/test/java/lib/form/OptionTest.java new file mode 100644 index 0000000000..9e126376eb --- /dev/null +++ b/test/src/test/java/lib/form/OptionTest.java @@ -0,0 +1,393 @@ +/* + * 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 lib.form; + +import com.gargoylesoftware.htmlunit.html.DomElement; +import com.gargoylesoftware.htmlunit.html.DomNodeList; +import com.gargoylesoftware.htmlunit.html.HtmlElement; +import com.gargoylesoftware.htmlunit.html.HtmlOption; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import hudson.model.RootAction; +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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +/** + * Tests for lib/form/option.jelly + */ +public class OptionTest { + private static final int MODE_JELLY_REGULAR = 0; + private static final int MODE_JELLY_FORCE_RAW = 1; + + private static final int MODE_GROOVY_REGULAR = 0; + private static final int MODE_GROOVY_TEXT = 1; + + private static final int MODE_XML_ESCAPE = 2; + private static final int MODE_NATIVE_OPTION = 3; + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Test + @Issue("SECURITY-624") + public void optionsAreCorrectlyEscaped() throws Exception { + checkNonDangerousOutputCorrect_simple(); + checkNonDangerousOutputCorrect_advanced(); + checkDangerousOutputNotActive(); + } + + private void checkNonDangerousOutputCorrect_simple() throws Exception { + String simpleText = "Simple text"; + benchOfTest_acceptEscapedCharacters(simpleText, simpleText); + } + + private void checkNonDangerousOutputCorrect_advanced() throws Exception { + String advancedText = "Markdown -> HTML & XHTML even with \"'_/$\\< characters"; + + // all those variants are displayed normally to the user since the escaping is un-escaped by the browser + // for display purpose (and only for display, not executing) + String escapeForValue = escapeForValue(advancedText); + String escapeForBody = escapeForBody(advancedText); + String escapeForBody_alternate = escapeForBody_alternate(advancedText); + + // those variants are ugly for the user since they have some escaped visible characters + // they are produced by too much escaping done manually + // those tests are provided to ensure the security, + // normally they are not used since they are displaying the value in a ugly way + // => Markdown -&gt; HTML &amp; XHTML even with "'_/$\&lt; characters + String escapeForBody_uglyButSafe = escapeForBody_uglyButSafe(advancedText); + // => Markdown -&gt; HTML &amp; XHTML even with "'_/$\&lt; characters + String escapeForValue_uglyButSafe = escapeForValue_uglyButSafe(advancedText); + + { // lenient mode + checkJelly(MODE_JELLY_REGULAR, advancedText, advancedText, false); + checkGroovy(MODE_GROOVY_TEXT, advancedText, advancedText, false); + checkGroovy(MODE_XML_ESCAPE, advancedText, advancedText, false); + + checkJelly(MODE_NATIVE_OPTION, advancedText, advancedText, advancedText, false, true, false); + checkGroovy(MODE_NATIVE_OPTION, advancedText, advancedText, advancedText, false, true, false); + + // those ones were vulnerable before the patch, you can test that by undoing the changes + checkJelly(MODE_JELLY_FORCE_RAW, advancedText, advancedText, false); + checkGroovy(MODE_GROOVY_REGULAR, advancedText, advancedText, false); + + // those ones are ugly and the display value is a string with escape characters. + checkJelly(MODE_XML_ESCAPE, advancedText, escapeForBody_alternate, advancedText, false, true, false); + checkJelly(MODE_XML_ESCAPE, advancedText, escapeForBody_alternate, escapeForBody_alternate, false, false, true); + } + + { // in strict mode, we need to provide the exact characters that are expected + checkJelly(MODE_JELLY_REGULAR, advancedText, escapeForBody, escapeForValue, true); + checkGroovy(MODE_GROOVY_TEXT, advancedText, escapeForBody, escapeForValue, true); + checkJelly(MODE_NATIVE_OPTION, advancedText, escapeForBody, escapeForValue, true, true, false); + checkGroovy(MODE_XML_ESCAPE, advancedText, escapeForBody, escapeForValue, true); + checkGroovy(MODE_NATIVE_OPTION, advancedText, escapeForBody_alternate, escapeForValue, true, true, false); + + // those ones were vulnerable before the patch, you can test that by undoing the changes + checkJelly(MODE_JELLY_FORCE_RAW, advancedText, escapeForBody, escapeForValue, true); + checkGroovy(MODE_GROOVY_REGULAR, advancedText, escapeForBody, escapeForValue, true); + + // ugly display, was already the case, just shown here to be sure of the safety + checkJelly(MODE_XML_ESCAPE, advancedText, escapeForBody_uglyButSafe, escapeForValue, true, true, false); + checkJelly(MODE_XML_ESCAPE, advancedText, escapeForBody_uglyButSafe, escapeForValue_uglyButSafe, true, false, true); + } + } + + private String escapeForBody(String str){ + return str + .replace("&", "&") + .replace("<", "<") + ; + } + + private String escapeForBody_alternate(String str){ + return str + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + ; + } + + + + private String escapeForValue(String str){ + return str + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + ; + } + + private String escapeForBody_uglyButSafe(String str){ + return str + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + // double escaping, make the text ugly but it was exactly the same case before + // when we are using too much escaping (manual h.xmlEscape on the body) + .replace("&", "&") + ; + } + + private String escapeForValue_uglyButSafe(String str){ + return str + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + // double escaping, make the text ugly but it was exactly the same case before + // when we are using too much escaping (manual h.xmlEscape on the body) + .replace("&", "&") + // html attribute escaping done after the double escaping + .replace("\"", """) + ; + } + + private void checkDangerousOutputNotActive() throws Exception { + // this javascript simply replace the whole document by "hacked" and so the "FLAG" will not be present + // but it will not be executed (thanks to the escaping) + benchOfTest_strictContains( + "_FLAG", + "FLAG" + ); + } + + private void benchOfTest_strictContains(String msg, String containsExpected) throws Exception { + _benchOfTest(msg, containsExpected, true); + } + + private void benchOfTest_acceptEscapedCharacters(String msg, String containsExpected) throws Exception { + _benchOfTest(msg, containsExpected, false); + } + + private void _benchOfTest(String msg, String containsExpected, boolean checkExactCharacters) throws Exception { + checkJelly(MODE_JELLY_REGULAR, msg, containsExpected, checkExactCharacters); + checkGroovy(MODE_GROOVY_TEXT, msg, containsExpected, checkExactCharacters); + + checkJelly(MODE_XML_ESCAPE, msg, containsExpected, checkExactCharacters); + checkJelly(MODE_NATIVE_OPTION, msg, containsExpected, containsExpected, checkExactCharacters, true, false); + checkGroovy(MODE_XML_ESCAPE, msg, containsExpected, checkExactCharacters); + checkGroovy(MODE_NATIVE_OPTION, msg, containsExpected, containsExpected, checkExactCharacters, true, false); + + // those ones were vulnerable before the patch, you can test that by undoing the changes + checkJelly(MODE_JELLY_FORCE_RAW, msg, containsExpected, checkExactCharacters); + checkGroovy(MODE_GROOVY_REGULAR, msg, containsExpected, checkExactCharacters); + } + + private void checkJelly(int mode, String msgToInject, String bothContainsExpected, boolean checkExactCharacters) throws Exception { + checkJelly(mode, msgToInject, bothContainsExpected, bothContainsExpected, checkExactCharacters); + } + + private void checkJelly(int mode, String msgToInject, + String bodyContainsExpected, String valueContainsExpected, + boolean checkExactCharacters) throws Exception { + checkJelly(mode, msgToInject, bodyContainsExpected, valueContainsExpected, checkExactCharacters, true, true); + } + + private void checkJelly(int mode, String msgToInject, + String bodyContainsExpected, String valueContainsExpected, + boolean checkExactCharacters, + boolean withValueTrue, boolean withValueFalse) throws Exception { + UsingJellyView view = j.jenkins.getExtensionList(UsingJellyView.class).get(0); + view.setMode(mode); + view.setInjection(msgToInject); + + if(withValueTrue){ + view.setWithValue(true); + callPageAndCheckIfResultContainsExpected("usingJelly", bodyContainsExpected, valueContainsExpected, checkExactCharacters); + } + if(withValueFalse){ + view.setWithValue(false); + callPageAndCheckIfResultContainsExpected("usingJelly", bodyContainsExpected, valueContainsExpected, checkExactCharacters); + } + } + + private void checkGroovy(int mode, String msgToInject, String bothContainsExpected, boolean checkExactCharacters) throws Exception { + checkGroovy(mode, msgToInject, bothContainsExpected, bothContainsExpected, checkExactCharacters); + } + + private void checkGroovy(int mode, String msgToInject, String bodyContainsExpected, String valueContainsExpected, boolean checkExactCharacters) throws Exception { + checkGroovy(mode, msgToInject, bodyContainsExpected, valueContainsExpected, checkExactCharacters, true, true); + } + + private void checkGroovy(int mode, String msgToInject, + String bodyContainsExpected, String valueContainsExpected, + boolean checkExactCharacters, + boolean withValueTrue, boolean withValueFalse) throws Exception { + UsingGroovyView view = j.jenkins.getExtensionList(UsingGroovyView.class).get(0); + view.setMode(mode); + view.setInjection(msgToInject); + + if(withValueTrue){ + view.setWithValue(true); + callPageAndCheckIfResultContainsExpected("usingGroovy", bodyContainsExpected, valueContainsExpected, checkExactCharacters); + } + if(withValueFalse){ + view.setWithValue(false); + callPageAndCheckIfResultContainsExpected("usingGroovy", bodyContainsExpected, valueContainsExpected, checkExactCharacters); + } + } + + private void callPageAndCheckIfResultContainsExpected(String url, String bodyContainsExpected, String valueContainsExpected, boolean checkExactCharacters) throws Exception { + HtmlPage page = (HtmlPage) j.createWebClient().goTo(url, null); + String responseContent = page.getWebResponse().getContentAsString(); + + if(checkExactCharacters){ + // in this mode, we check the data directly received by the response, + // without any un-escaping done by HtmlElement + + // first value shown as value + int indexOfValue = responseContent.indexOf(valueContainsExpected); + assertTrue(indexOfValue != -1); + + // second as body + int indexOfBody = responseContent.indexOf(bodyContainsExpected, indexOfValue + 1); + + assertTrue(indexOfBody != -1); + + // also check there is no " + +
    + + + diff --git a/core/src/main/resources/jenkins/install/SetupWizard/setupWizardConfigureInstance.properties b/core/src/main/resources/jenkins/install/SetupWizard/setupWizardConfigureInstance.properties new file mode 100644 index 0000000000..4d08f82ba2 --- /dev/null +++ b/core/src/main/resources/jenkins/install/SetupWizard/setupWizardConfigureInstance.properties @@ -0,0 +1,6 @@ +Instance\ Configuration=Instance Configuration +Jenkins\ URL=Jenkins URL +jenkinsURL_help=The Jenkins URL is used to provide the root URL for absolute links to various Jenkins resources. \ +That means this value is required for proper operation of many Jenkins features including email notifications, PR status updates, and the BUILD_URL environment variable provided to build steps.
    \ +The proposed default value shown is not saved yet and is generated from the current request, if possible. \ +The best practice is to set this value to the URL that users are expected to use. This will avoid confusion when sharing or viewing links. diff --git a/core/src/main/resources/jenkins/install/SetupWizard/setupWizardConfigureInstance_fr.properties b/core/src/main/resources/jenkins/install/SetupWizard/setupWizardConfigureInstance_fr.properties new file mode 100644 index 0000000000..7c8aae3d61 --- /dev/null +++ b/core/src/main/resources/jenkins/install/SetupWizard/setupWizardConfigureInstance_fr.properties @@ -0,0 +1,2 @@ +Instance\ Configuration=Configuration de l''instance +Jenkins\ URL=Jenkins URL diff --git a/core/src/main/resources/jenkins/install/SetupWizard/setupWizardFirstUser.jelly b/core/src/main/resources/jenkins/install/SetupWizard/setupWizardFirstUser.jelly index 788b353a9e..96e972b9ca 100644 --- a/core/src/main/resources/jenkins/install/SetupWizard/setupWizardFirstUser.jelly +++ b/core/src/main/resources/jenkins/install/SetupWizard/setupWizardFirstUser.jelly @@ -7,7 +7,7 @@