mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-14 17:26:22 +01:00
merge with branch 1.x
This commit is contained in:
@@ -65,7 +65,9 @@ import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import sonia.scm.debug.DebugModule;
|
||||
import sonia.scm.filter.WebElementModule;
|
||||
import sonia.scm.schedule.Scheduler;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -108,6 +110,8 @@ public class ScmContextListener extends GuiceServletContextListener
|
||||
{
|
||||
if ((globalInjector != null) &&!startupError)
|
||||
{
|
||||
// close Scheduler
|
||||
IOUtil.close(globalInjector.getInstance(Scheduler.class));
|
||||
|
||||
// close RepositoryManager
|
||||
IOUtil.close(globalInjector.getInstance(RepositoryManager.class));
|
||||
@@ -259,6 +263,10 @@ public class ScmContextListener extends GuiceServletContextListener
|
||||
);
|
||||
appendModules(pluginLoader.getExtensionProcessor(), moduleList);
|
||||
moduleList.addAll(overrides.getModules());
|
||||
|
||||
if (SCMContext.getContext().getStage() == Stage.DEVELOPMENT){
|
||||
moduleList.add(new DebugModule());
|
||||
}
|
||||
|
||||
SCMContextProvider ctx = SCMContext.getContext();
|
||||
|
||||
|
||||
@@ -128,6 +128,18 @@ import java.util.Map;
|
||||
import javax.servlet.ServletContext;
|
||||
import sonia.scm.store.ConfigurationStoreFactory;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import sonia.scm.net.SSLContextProvider;
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
import sonia.scm.net.ahc.ContentTransformer;
|
||||
import sonia.scm.net.ahc.DefaultAdvancedHttpClient;
|
||||
import sonia.scm.net.ahc.JsonContentTransformer;
|
||||
import sonia.scm.net.ahc.XmlContentTransformer;
|
||||
import sonia.scm.schedule.QuartzScheduler;
|
||||
import sonia.scm.schedule.Scheduler;
|
||||
import sonia.scm.security.XsrfProtectionFilter;
|
||||
import sonia.scm.web.UserAgentParser;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
@@ -249,6 +261,9 @@ public class ScmServletModule extends JerseyServletModule
|
||||
bind(PluginLoader.class).toInstance(pluginLoader);
|
||||
bind(PluginManager.class, DefaultPluginManager.class);
|
||||
|
||||
// bind scheduler
|
||||
bind(Scheduler.class).to(QuartzScheduler.class);
|
||||
|
||||
// note CipherUtil uses an other generator
|
||||
bind(KeyGenerator.class).to(DefaultKeyGenerator.class);
|
||||
bind(CipherHandler.class).toInstance(cu.getCipherHandler());
|
||||
@@ -280,8 +295,18 @@ public class ScmServletModule extends JerseyServletModule
|
||||
GroupManagerProvider.class);
|
||||
bind(CGIExecutorFactory.class, DefaultCGIExecutorFactory.class);
|
||||
|
||||
// bind sslcontext provider
|
||||
bind(SSLContext.class).toProvider(SSLContextProvider.class);
|
||||
|
||||
// bind httpclient
|
||||
bind(HttpClient.class, URLHttpClient.class);
|
||||
|
||||
// bind ahc
|
||||
Multibinder<ContentTransformer> transformers =
|
||||
Multibinder.newSetBinder(binder(), ContentTransformer.class);
|
||||
transformers.addBinding().to(XmlContentTransformer.class);
|
||||
transformers.addBinding().to(JsonContentTransformer.class);
|
||||
bind(AdvancedHttpClient.class).to(DefaultAdvancedHttpClient.class);
|
||||
|
||||
// bind resourcemanager
|
||||
if (context.getStage() == Stage.DEVELOPMENT)
|
||||
@@ -310,13 +335,16 @@ public class ScmServletModule extends JerseyServletModule
|
||||
// bind new hook api
|
||||
bind(HookContextFactory.class);
|
||||
bind(HookEventFacade.class);
|
||||
|
||||
// bind user-agent parser
|
||||
bind(UserAgentParser.class);
|
||||
|
||||
// bind debug logging filter
|
||||
if ("true".equalsIgnoreCase(System.getProperty(SYSTEM_PROPERTY_DEBUG_HTTP)))
|
||||
{
|
||||
filter(PATTERN_ALL).through(LoggingFilter.class);
|
||||
}
|
||||
|
||||
|
||||
// debug servlet
|
||||
serve(PATTERN_DEBUG).with(DebugServlet.class);
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ import javax.ws.rs.core.Response;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import sonia.scm.security.XsrfCookies;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -266,6 +267,9 @@ public class AuthenticationResource
|
||||
public Response logout(@Context HttpServletRequest request,
|
||||
@Context HttpServletResponse response)
|
||||
{
|
||||
// remove xsrf token
|
||||
XsrfCookies.remove(request, response);
|
||||
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
|
||||
subject.logout();
|
||||
|
||||
@@ -553,7 +553,8 @@ public class RepositoryImportResource
|
||||
}
|
||||
catch (RepositoryAlreadyExistsException ex)
|
||||
{
|
||||
logger.warn("a {} repository with the name {} already exists", ex);
|
||||
logger.warn("a {} repository with the name {} already exists", type,
|
||||
name);
|
||||
|
||||
throw new WebApplicationException(Response.Status.CONFLICT);
|
||||
}
|
||||
@@ -692,7 +693,7 @@ public class RepositoryImportResource
|
||||
private void handleGenericCreationFailure(Exception ex, String type,
|
||||
String name)
|
||||
{
|
||||
logger.error(String.format("could not create repository {} with type {}",
|
||||
logger.error(String.format("could not create repository %s with type %s",
|
||||
type, name), ex);
|
||||
|
||||
throw new WebApplicationException(ex);
|
||||
|
||||
@@ -1111,7 +1111,7 @@ public class RepositoryResource
|
||||
*/
|
||||
private String getContentDispositionName(String name)
|
||||
{
|
||||
return "attachment; filename=\"".concat(name).concat("\"");
|
||||
return HttpUtil.createContentDispositionAttachmentHeader(name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
88
scm-webapp/src/main/java/sonia/scm/debug/DebugHook.java
Normal file
88
scm-webapp/src/main/java/sonia/scm/debug/DebugHook.java
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.debug;
|
||||
|
||||
import com.github.legman.ReferenceType;
|
||||
import com.github.legman.Subscribe;
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.Collections2;
|
||||
import javax.inject.Inject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.EagerSingleton;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||
|
||||
/**
|
||||
* {@link PostReceiveRepositoryHookEvent} which stores receives data and passes it to the {@link DebugService}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@EagerSingleton
|
||||
public final class DebugHook
|
||||
{
|
||||
/**
|
||||
* the logger for DebugHook
|
||||
*/
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DebugHook.class);
|
||||
|
||||
private final DebugService debugService;
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param debugService debug service
|
||||
*/
|
||||
@Inject
|
||||
public DebugHook(DebugService debugService)
|
||||
{
|
||||
this.debugService = debugService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the received {@link PostReceiveRepositoryHookEvent} and transforms it to a {@link DebugHookData} and
|
||||
* passes it to the {@link DebugService}.
|
||||
*
|
||||
* @param event received event
|
||||
*/
|
||||
@Subscribe(referenceType = ReferenceType.STRONG)
|
||||
public void processEvent(PostReceiveRepositoryHookEvent event){
|
||||
LOG.trace("store changeset ids from repository", event.getRepository().getId());
|
||||
|
||||
debugService.put(
|
||||
event.getRepository().getId(),
|
||||
new DebugHookData(Collections2.transform(
|
||||
event.getContext().getChangesetProvider().getChangesetList(), IDEXTRACTOR)
|
||||
));
|
||||
}
|
||||
|
||||
private static final Function<Changeset, String> IDEXTRACTOR = (Changeset changeset) -> changeset.getId();
|
||||
}
|
||||
90
scm-webapp/src/main/java/sonia/scm/debug/DebugHookData.java
Normal file
90
scm-webapp/src/main/java/sonia/scm/debug/DebugHookData.java
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.debug;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/**
|
||||
* Received data from repository hook event.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@XmlRootElement(name = "hook")
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class DebugHookData
|
||||
{
|
||||
private Date date;
|
||||
private Collection<String> changesets;
|
||||
|
||||
/**
|
||||
* Constructs a new instance. This constructor should only be used by JAXB.
|
||||
*/
|
||||
public DebugHookData()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param changesets collection of changeset ids
|
||||
*/
|
||||
public DebugHookData(Collection<String> changesets)
|
||||
{
|
||||
this.date = new Date();
|
||||
this.changesets = changesets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the receiving date.
|
||||
*
|
||||
* @return receiving date
|
||||
*/
|
||||
public Date getDate()
|
||||
{
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return collection of changeset ids.
|
||||
*
|
||||
* @return collection of changeset ids
|
||||
*/
|
||||
public Collection<String> getChangesets()
|
||||
{
|
||||
return changesets;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
52
scm-webapp/src/main/java/sonia/scm/debug/DebugModule.java
Normal file
52
scm-webapp/src/main/java/sonia/scm/debug/DebugModule.java
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.debug;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
|
||||
/**
|
||||
* DebugModule binds all required classes around the {@link DebugService}. The module will only be activated, if the
|
||||
* application was started in development stage.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public final class DebugModule extends AbstractModule
|
||||
{
|
||||
|
||||
@Override
|
||||
protected void configure()
|
||||
{
|
||||
bind(DebugService.class);
|
||||
bind(DebugHook.class);
|
||||
bind(DebugResource.class);
|
||||
}
|
||||
|
||||
}
|
||||
89
scm-webapp/src/main/java/sonia/scm/debug/DebugResource.java
Normal file
89
scm-webapp/src/main/java/sonia/scm/debug/DebugResource.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.debug;
|
||||
|
||||
import java.util.Collection;
|
||||
import javax.inject.Inject;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
/**
|
||||
* Rest api resource for the {@link DebugService}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Path("debug/{repository}/post-receive")
|
||||
public final class DebugResource
|
||||
{
|
||||
private final DebugService debugService;
|
||||
|
||||
/**
|
||||
* Constructs a new instance.
|
||||
*
|
||||
* @param debugService debug service
|
||||
*/
|
||||
@Inject
|
||||
public DebugResource(DebugService debugService)
|
||||
{
|
||||
this.debugService = debugService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all received hook data for the given repository.
|
||||
*
|
||||
* @param repository repository id
|
||||
*
|
||||
* @return all received hook data for the given repository
|
||||
*/
|
||||
@GET
|
||||
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
|
||||
public Collection<DebugHookData> getAll(@PathParam("repository") String repository){
|
||||
return debugService.getAll(repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last received hook data for the given repository.
|
||||
*
|
||||
* @param repository repository id
|
||||
*
|
||||
* @return the last received hook data for the given repository
|
||||
*/
|
||||
@GET
|
||||
@Path("last")
|
||||
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
|
||||
public DebugHookData getLast(@PathParam("repository") String repository){
|
||||
return debugService.getLast(repository);
|
||||
}
|
||||
|
||||
}
|
||||
92
scm-webapp/src/main/java/sonia/scm/debug/DebugService.java
Normal file
92
scm-webapp/src/main/java/sonia/scm/debug/DebugService.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.debug;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.LinkedListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.inject.Singleton;
|
||||
import java.util.Collection;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import sonia.scm.security.Role;
|
||||
|
||||
/**
|
||||
* The DebugService stores and returns received data from repository hook events.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Singleton
|
||||
public final class DebugService
|
||||
{
|
||||
|
||||
private final Multimap<String,DebugHookData> receivedHooks = LinkedListMultimap.create();
|
||||
|
||||
/**
|
||||
* Stores {@link DebugHookData} for the given repository.
|
||||
*
|
||||
* @param repository repository id
|
||||
* @param hookData received hook data
|
||||
*/
|
||||
void put(String repository, DebugHookData hookData)
|
||||
{
|
||||
receivedHooks.put(repository, hookData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last received hook data for the given repository.
|
||||
*
|
||||
* @param repository repository id
|
||||
*
|
||||
* @return the last received hook data for the given repository
|
||||
*/
|
||||
public DebugHookData getLast(String repository){
|
||||
SecurityUtils.getSubject().checkRole(Role.ADMIN);
|
||||
DebugHookData hookData = null;
|
||||
Collection<DebugHookData> receivedHookData = receivedHooks.get(repository);
|
||||
if (receivedHookData != null && ! receivedHookData.isEmpty()){
|
||||
hookData = Iterables.getLast(receivedHookData);
|
||||
}
|
||||
return hookData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all received hook data for the given repository.
|
||||
*
|
||||
* @param repository repository id
|
||||
*
|
||||
* @return all received hook data for the given repository
|
||||
*/
|
||||
public Collection<DebugHookData> getAll(String repository){
|
||||
SecurityUtils.getSubject().checkRole(Role.ADMIN);
|
||||
return receivedHooks.get(repository);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -92,8 +92,9 @@ public class LegmanScmEventBus extends ScmEventBus
|
||||
@Override
|
||||
public void register(Object object)
|
||||
{
|
||||
logger.debug("register {} to event bus", object);
|
||||
logger.trace("register {} to event bus", object);
|
||||
eventBus.register(object);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,8 +106,8 @@ public class LegmanScmEventBus extends ScmEventBus
|
||||
@Override
|
||||
public void unregister(Object object)
|
||||
{
|
||||
logger.debug("unregister {} from event bus", object);
|
||||
|
||||
logger.trace("unregister {} from event bus", object);
|
||||
|
||||
try
|
||||
{
|
||||
eventBus.unregister(object);
|
||||
|
||||
@@ -44,7 +44,9 @@ import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.security.Role;
|
||||
|
||||
/**
|
||||
*
|
||||
* Security filter which allow only administrators to access the underlying
|
||||
* resources.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@WebElement(
|
||||
@@ -60,10 +62,9 @@ public class AdminSecurityFilter extends SecurityFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
* Constructs a new instance.
|
||||
*
|
||||
*
|
||||
* @param configuration
|
||||
* @param configuration scm-manager main configuration
|
||||
*/
|
||||
@Inject
|
||||
public AdminSecurityFilter(ScmConfiguration configuration)
|
||||
@@ -74,12 +75,17 @@ public class AdminSecurityFilter extends SecurityFilter
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
* Returns {@code true} if the subject has the admin role.
|
||||
*
|
||||
<<<<<<< working copy
|
||||
* @param subject
|
||||
*
|
||||
* @return
|
||||
=======
|
||||
* @param subject subject
|
||||
*
|
||||
* @return {@code true} if the subject has the admin role
|
||||
>>>>>>> merge rev
|
||||
*/
|
||||
@Override
|
||||
protected boolean hasPermission(Subject subject)
|
||||
|
||||
@@ -33,6 +33,7 @@ package sonia.scm.filter;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@@ -63,13 +64,24 @@ public class MDCFilter extends HttpFilter
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
private static final String MDC_CLIEN_HOST = "client_host";
|
||||
@VisibleForTesting
|
||||
static final String MDC_CLIEN_HOST = "client_host";
|
||||
|
||||
/** Field description */
|
||||
private static final String MDC_CLIEN_IP = "client_ip";
|
||||
@VisibleForTesting
|
||||
static final String MDC_CLIEN_IP = "client_ip";
|
||||
|
||||
/** url of the current request */
|
||||
@VisibleForTesting
|
||||
static final String MDC_REQUEST_URI = "request_uri";
|
||||
|
||||
/** request method */
|
||||
@VisibleForTesting
|
||||
static final String MDC_REQUEST_METHOD = "request_method";
|
||||
|
||||
/** Field description */
|
||||
private static final String MDC_USERNAME = "username";
|
||||
@VisibleForTesting
|
||||
static final String MDC_USERNAME = "username";
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
@@ -92,6 +104,8 @@ public class MDCFilter extends HttpFilter
|
||||
MDC.put(MDC_USERNAME, getUsername());
|
||||
MDC.put(MDC_CLIEN_IP, request.getRemoteAddr());
|
||||
MDC.put(MDC_CLIEN_HOST, request.getRemoteHost());
|
||||
MDC.put(MDC_REQUEST_METHOD, request.getMethod());
|
||||
MDC.put(MDC_REQUEST_URI, request.getRequestURI());
|
||||
|
||||
try
|
||||
{
|
||||
@@ -102,6 +116,8 @@ public class MDCFilter extends HttpFilter
|
||||
MDC.remove(MDC_USERNAME);
|
||||
MDC.remove(MDC_CLIEN_IP);
|
||||
MDC.remove(MDC_CLIEN_HOST);
|
||||
MDC.remove(MDC_REQUEST_METHOD);
|
||||
MDC.remove(MDC_REQUEST_URI);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@ package sonia.scm.filter;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
@@ -44,7 +44,6 @@ import org.apache.shiro.subject.Subject;
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.SCMContext;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.user.User;
|
||||
import sonia.scm.web.filter.HttpFilter;
|
||||
import sonia.scm.web.filter.SecurityHttpServletRequestWrapper;
|
||||
|
||||
@@ -62,11 +61,14 @@ import javax.servlet.http.HttpServletResponse;
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@Priority(Filters.PRIORITY_AUTHORIZATION)
|
||||
@WebElement(value = Filters.PATTERN_RESTAPI,
|
||||
morePatterns = { Filters.PATTERN_DEBUG })
|
||||
@WebElement(value = Filters.PATTERN_RESTAPI, morePatterns = { Filters.PATTERN_DEBUG })
|
||||
public class SecurityFilter extends HttpFilter
|
||||
{
|
||||
|
||||
/** name of request attribute for the primary principal */
|
||||
@VisibleForTesting
|
||||
static final String ATTRIBUTE_REMOTE_USER = "principal";
|
||||
|
||||
/** Field description */
|
||||
public static final String URL_AUTHENTICATION = "/api/rest/authentication";
|
||||
|
||||
@@ -102,17 +104,21 @@ public class SecurityFilter extends HttpFilter
|
||||
HttpServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
|
||||
String uri =
|
||||
request.getRequestURI().substring(request.getContextPath().length());
|
||||
|
||||
if (!uri.startsWith(URL_AUTHENTICATION))
|
||||
{
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
if (hasPermission(subject))
|
||||
{
|
||||
chain.doFilter(new SecurityHttpServletRequestWrapper(request,
|
||||
getUsername(subject)), response);
|
||||
// add primary principal as request attribute
|
||||
// see https://goo.gl/JRjNmf
|
||||
String username = getUsername(subject);
|
||||
request.setAttribute(ATTRIBUTE_REMOTE_USER, username);
|
||||
|
||||
// wrap servlet request to provide authentication informations
|
||||
chain.doFilter(new SecurityHttpServletRequestWrapper(request, username), response);
|
||||
}
|
||||
else if (subject.isAuthenticated() || subject.isRemembered())
|
||||
{
|
||||
@@ -150,14 +156,6 @@ public class SecurityFilter extends HttpFilter
|
||||
|| subject.isRemembered();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param subject
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getUsername(Subject subject)
|
||||
{
|
||||
String username = SCMContext.USER_ANONYMOUS;
|
||||
|
||||
@@ -208,15 +208,14 @@ public class DefaultGroupManager extends AbstractGroupManager
|
||||
String name = group.getName();
|
||||
GroupPermissions.modify().check(name);
|
||||
|
||||
Group oldGroup = groupDAO.get(name);
|
||||
|
||||
if (oldGroup != null)
|
||||
Group notModified = groupDAO.get(name);
|
||||
if (notModified != null)
|
||||
{
|
||||
removeDuplicateMembers(group);
|
||||
fireEvent(HandlerEventType.BEFORE_MODIFY, group, notModified);
|
||||
group.setLastModified(System.currentTimeMillis());
|
||||
fireEvent(HandlerEventType.BEFORE_MODIFY, group, oldGroup);
|
||||
groupDAO.modify(group);
|
||||
fireEvent(HandlerEventType.MODIFY, group, oldGroup);
|
||||
fireEvent(HandlerEventType.MODIFY, group, notModified);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.net;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.inject.Inject;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Provider for {@link SSLContext}. The provider will first try to retrieve the {@link SSLContext} from an "default"
|
||||
* named optional provider, if this fails the provider will return the jvm default context.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @version 1.47
|
||||
*/
|
||||
public final class SSLContextProvider implements Provider<SSLContext>
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for SSLContextProvider
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(SSLContextProvider.class);
|
||||
|
||||
@Named("default")
|
||||
@Inject(optional = true)
|
||||
private Provider<SSLContext> sslContextProvider;
|
||||
|
||||
@Override
|
||||
public SSLContext get()
|
||||
{
|
||||
SSLContext context = null;
|
||||
if (sslContextProvider != null)
|
||||
{
|
||||
context = sslContextProvider.get();
|
||||
}
|
||||
|
||||
if (context == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.trace("could not find ssl context provider, use jvm default");
|
||||
context = SSLContext.getDefault();
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex)
|
||||
{
|
||||
throw Throwables.propagate(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.trace("use custom ssl context from provider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,8 +48,10 @@ import sonia.scm.util.Util;
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import com.sun.jersey.core.util.Base64;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
@@ -67,11 +69,14 @@ import java.util.Map;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @deprecated use {@link sonia.scm.net.ahc.AdvancedHttpClient}
|
||||
*/
|
||||
@Deprecated
|
||||
public class URLHttpClient implements HttpClient
|
||||
{
|
||||
|
||||
@@ -152,6 +157,9 @@ public class URLHttpClient implements HttpClient
|
||||
url);
|
||||
|
||||
connection.setRequestMethod(METHOD_POST);
|
||||
// send empty content-length
|
||||
// see issue #701 http://goo.gl/oyTdrA
|
||||
setContentLength(connection, 0);
|
||||
|
||||
return new URLHttpResponse(connection);
|
||||
}
|
||||
@@ -347,41 +355,85 @@ public class URLHttpClient implements HttpClient
|
||||
{
|
||||
if (Util.isNotEmpty(parameters))
|
||||
{
|
||||
// use a ByteArrayOutputStream in order to get the final content-length
|
||||
// see issue #701 http://goo.gl/oyTdrA
|
||||
connection.setDoOutput(true);
|
||||
|
||||
OutputStreamWriter writer = null;
|
||||
ByteArrayOutputStream baos = null;
|
||||
|
||||
try
|
||||
{
|
||||
writer = new OutputStreamWriter(connection.getOutputStream());
|
||||
baos = new ByteArrayOutputStream();
|
||||
writer = new OutputStreamWriter(baos);
|
||||
|
||||
Iterator<Map.Entry<String, List<String>>> it =
|
||||
parameters.entrySet().iterator();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
Map.Entry<String, List<String>> p = it.next();
|
||||
List<String> values = p.getValue();
|
||||
|
||||
if (Util.isNotEmpty(values))
|
||||
{
|
||||
String key = encode(p.getKey());
|
||||
|
||||
for (String value : values)
|
||||
{
|
||||
writer.append(key).append("=").append(encode(value));
|
||||
}
|
||||
|
||||
if (it.hasNext())
|
||||
{
|
||||
writer.write("&");
|
||||
}
|
||||
}
|
||||
}
|
||||
appendPostParameters(writer, parameters);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtil.close(writer);
|
||||
IOUtil.close(baos);
|
||||
}
|
||||
|
||||
if ( baos != null ){
|
||||
byte[] data = baos.toByteArray();
|
||||
appendBody(connection, data);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
setContentLength(connection, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void appendBody(HttpURLConnection connection, byte[] body) throws IOException
|
||||
{
|
||||
int length = body.length;
|
||||
logger.trace("write {} bytes to output", length);
|
||||
setContentLength(connection, length);
|
||||
connection.setFixedLengthStreamingMode(length);
|
||||
OutputStream os = null;
|
||||
try {
|
||||
os = connection.getOutputStream();
|
||||
os.write(body);
|
||||
} finally {
|
||||
IOUtil.close(os);
|
||||
}
|
||||
}
|
||||
|
||||
private void setContentLength(HttpURLConnection connection, int length)
|
||||
{
|
||||
connection.setRequestProperty(HttpUtil.HEADER_CONTENT_LENGTH, String.valueOf(length));
|
||||
}
|
||||
|
||||
private void appendPostParameters(OutputStreamWriter writer, Map<String, List<String>> parameters) throws IOException
|
||||
{
|
||||
Iterator<Map.Entry<String, List<String>>> it = parameters.entrySet().iterator();
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
Map.Entry<String, List<String>> p = it.next();
|
||||
List<String> values = p.getValue();
|
||||
|
||||
if (Util.isNotEmpty(values))
|
||||
{
|
||||
String key = encode(p.getKey());
|
||||
|
||||
Iterator<String> valueIt = values.iterator();
|
||||
|
||||
while(valueIt.hasNext())
|
||||
{
|
||||
String value = valueIt.next();
|
||||
writer.append(key).append("=").append(encode(value));
|
||||
if ( valueIt.hasNext() ){
|
||||
writer.write("&");
|
||||
}
|
||||
}
|
||||
|
||||
if (it.hasNext())
|
||||
{
|
||||
writer.write("&");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer. 2. Redistributions in
|
||||
* binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other
|
||||
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
|
||||
* nor the names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.net.ahc;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.io.Closeables;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.shiro.codec.Base64;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.net.Proxies;
|
||||
import sonia.scm.net.TrustAllHostnameVerifier;
|
||||
import sonia.scm.net.TrustAllTrustManager;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.Proxy;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URL;
|
||||
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import java.util.Set;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link AdvancedHttpClient}. The default
|
||||
* implementation uses {@link HttpURLConnection}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.46
|
||||
*/
|
||||
public class DefaultAdvancedHttpClient extends AdvancedHttpClient
|
||||
{
|
||||
|
||||
/** proxy authorization header */
|
||||
@VisibleForTesting
|
||||
static final String HEADER_PROXY_AUTHORIZATION = "Proxy-Authorization";
|
||||
|
||||
/** connection timeout */
|
||||
@VisibleForTesting
|
||||
static final int TIMEOUT_CONNECTION = 30000;
|
||||
|
||||
/** read timeout */
|
||||
@VisibleForTesting
|
||||
static final int TIMEOUT_RAED = 1200000;
|
||||
|
||||
/** credential separator */
|
||||
private static final String CREDENTIAL_SEPARATOR = ":";
|
||||
|
||||
/** basic authentication prefix */
|
||||
private static final String PREFIX_BASIC_AUTHENTICATION = "Basic ";
|
||||
|
||||
/**
|
||||
* the logger for DefaultAdvancedHttpClient
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(DefaultAdvancedHttpClient.class);
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs a new {@link DefaultAdvancedHttpClient}.
|
||||
*
|
||||
*
|
||||
* @param configuration scm-manager main configuration
|
||||
* @param contentTransformers content transformer
|
||||
* @param sslContextProvider ssl context provider
|
||||
*/
|
||||
@Inject
|
||||
public DefaultAdvancedHttpClient(ScmConfiguration configuration,
|
||||
Set<ContentTransformer> contentTransformers, Provider<SSLContext> sslContextProvider)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.contentTransformers = contentTransformers;
|
||||
this.sslContextProvider = sslContextProvider;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a new {@link HttpURLConnection} from the given {@link URL}. The
|
||||
* method is visible for testing.
|
||||
*
|
||||
*
|
||||
* @param url url
|
||||
*
|
||||
* @return new {@link HttpURLConnection}
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected HttpURLConnection createConnection(URL url) throws IOException
|
||||
{
|
||||
return (HttpURLConnection) url.openConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new proxy {@link HttpURLConnection} from the given {@link URL}
|
||||
* and {@link SocketAddress}. The method is visible for testing.
|
||||
*
|
||||
*
|
||||
* @param url url
|
||||
* @param address proxy socket address
|
||||
*
|
||||
* @return new proxy {@link HttpURLConnection}
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@VisibleForTesting
|
||||
protected HttpURLConnection createProxyConnecton(URL url,
|
||||
SocketAddress address)
|
||||
throws IOException
|
||||
{
|
||||
return (HttpURLConnection) url.openConnection(new Proxy(Proxy.Type.HTTP,
|
||||
address));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected ContentTransformer createTransformer(Class<?> type, String contentType)
|
||||
{
|
||||
ContentTransformer responsible = null;
|
||||
|
||||
for (ContentTransformer transformer : contentTransformers)
|
||||
{
|
||||
if (transformer.isResponsible(type, contentType))
|
||||
{
|
||||
responsible = transformer;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (responsible == null)
|
||||
{
|
||||
throw new ContentTransformerNotFoundException(
|
||||
"could not find content transformer for content type ".concat(
|
||||
contentType));
|
||||
}
|
||||
|
||||
return responsible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given request and returns the server response.
|
||||
*
|
||||
*
|
||||
* @param request http request
|
||||
*
|
||||
* @return server response
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
protected AdvancedHttpResponse request(BaseHttpRequest<?> request)
|
||||
throws IOException
|
||||
{
|
||||
HttpURLConnection connection = openConnection(request,
|
||||
new URL(request.getUrl()));
|
||||
|
||||
applyBaseSettings(request, connection);
|
||||
|
||||
if (connection instanceof HttpsURLConnection)
|
||||
{
|
||||
applySSLSettings(request, (HttpsURLConnection) connection);
|
||||
}
|
||||
|
||||
Content content = null;
|
||||
|
||||
if (request instanceof AdvancedHttpRequestWithBody)
|
||||
{
|
||||
AdvancedHttpRequestWithBody ahrwb = (AdvancedHttpRequestWithBody) request;
|
||||
|
||||
content = ahrwb.getContent();
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
content.prepare(ahrwb);
|
||||
}
|
||||
else
|
||||
{
|
||||
request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
request.header(HttpUtil.HEADER_CONTENT_LENGTH, "0");
|
||||
}
|
||||
|
||||
applyHeaders(request, connection);
|
||||
|
||||
if (content != null)
|
||||
{
|
||||
applyContent(connection, content);
|
||||
}
|
||||
|
||||
return new DefaultAdvancedHttpResponse(this, connection,
|
||||
connection.getResponseCode(), connection.getResponseMessage());
|
||||
}
|
||||
|
||||
private void appendProxyAuthentication(HttpURLConnection connection)
|
||||
{
|
||||
String username = configuration.getProxyUser();
|
||||
String password = configuration.getProxyPassword();
|
||||
|
||||
if (!Strings.isNullOrEmpty(username) ||!Strings.isNullOrEmpty(password))
|
||||
{
|
||||
logger.debug("append proxy authentication header for user {}", username);
|
||||
|
||||
String auth = username.concat(CREDENTIAL_SEPARATOR).concat(password);
|
||||
|
||||
auth = Base64.encodeToString(auth.getBytes());
|
||||
connection.addRequestProperty(HEADER_PROXY_AUTHORIZATION,
|
||||
PREFIX_BASIC_AUTHENTICATION.concat(auth));
|
||||
}
|
||||
}
|
||||
|
||||
private void applyBaseSettings(BaseHttpRequest<?> request,
|
||||
HttpURLConnection connection)
|
||||
throws ProtocolException
|
||||
{
|
||||
connection.setRequestMethod(request.getMethod());
|
||||
connection.setReadTimeout(TIMEOUT_RAED);
|
||||
connection.setConnectTimeout(TIMEOUT_CONNECTION);
|
||||
}
|
||||
|
||||
private void applyContent(HttpURLConnection connection, Content content)
|
||||
throws IOException
|
||||
{
|
||||
connection.setDoOutput(true);
|
||||
|
||||
OutputStream output = null;
|
||||
|
||||
try
|
||||
{
|
||||
output = connection.getOutputStream();
|
||||
content.process(output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Closeables.close(output, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyHeaders(BaseHttpRequest<?> request,
|
||||
HttpURLConnection connection)
|
||||
{
|
||||
Multimap<String, String> headers = request.getHeaders();
|
||||
|
||||
for (String key : headers.keySet())
|
||||
{
|
||||
for (String value : headers.get(key))
|
||||
{
|
||||
connection.addRequestProperty(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applySSLSettings(BaseHttpRequest<?> request,
|
||||
HttpsURLConnection connection)
|
||||
{
|
||||
if (request.isDisableCertificateValidation())
|
||||
{
|
||||
logger.trace("disable certificate validation");
|
||||
|
||||
try
|
||||
{
|
||||
TrustManager[] trustAllCerts = new TrustManager[] {
|
||||
new TrustAllTrustManager() };
|
||||
SSLContext sc = SSLContext.getInstance("SSL");
|
||||
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
connection.setSSLSocketFactory(sc.getSocketFactory());
|
||||
}
|
||||
catch (KeyManagementException ex)
|
||||
{
|
||||
logger.error("could not disable certificate validation", ex);
|
||||
}
|
||||
catch (NoSuchAlgorithmException ex)
|
||||
{
|
||||
logger.error("could not disable certificate validation", ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.trace("set ssl socker factory from provider");
|
||||
connection.setSSLSocketFactory(sslContextProvider.get().getSocketFactory());
|
||||
}
|
||||
|
||||
if (request.isDisableHostnameValidation())
|
||||
{
|
||||
logger.trace("disable hostname validation");
|
||||
connection.setHostnameVerifier(new TrustAllHostnameVerifier());
|
||||
}
|
||||
}
|
||||
|
||||
private HttpURLConnection openConnection(BaseHttpRequest<?> request, URL url)
|
||||
throws IOException
|
||||
{
|
||||
HttpURLConnection connection;
|
||||
|
||||
if (isProxyEnabled(request))
|
||||
{
|
||||
connection = openProxyConnection(request, url);
|
||||
appendProxyAuthentication(connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (request.isIgnoreProxySettings())
|
||||
{
|
||||
logger.trace("ignore proxy settings");
|
||||
}
|
||||
|
||||
logger.debug("fetch {}", url.toExternalForm());
|
||||
|
||||
connection = createConnection(url);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
private HttpURLConnection openProxyConnection(BaseHttpRequest<?> request,
|
||||
URL url)
|
||||
throws IOException
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("fetch '{}' using proxy {}:{}", url.toExternalForm(),
|
||||
configuration.getProxyServer(), configuration.getProxyPort());
|
||||
}
|
||||
|
||||
SocketAddress address =
|
||||
new InetSocketAddress(configuration.getProxyServer(),
|
||||
configuration.getProxyPort());
|
||||
|
||||
return createProxyConnecton(url, address);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
private boolean isProxyEnabled(BaseHttpRequest<?> request)
|
||||
{
|
||||
return !request.isIgnoreProxySettings()
|
||||
&& Proxies.isEnabled(configuration, request.getUrl());
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** scm-manager main configuration */
|
||||
private final ScmConfiguration configuration;
|
||||
|
||||
/** set of content transformers */
|
||||
private final Set<ContentTransformer> contentTransformers;
|
||||
|
||||
/** ssl context provider */
|
||||
private final Provider<SSLContext> sslContextProvider;
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer. 2. Redistributions in
|
||||
* binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other
|
||||
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
|
||||
* nor the names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.net.ahc;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.io.ByteSource;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Http server response object of {@link DefaultAdvancedHttpClient}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.46
|
||||
*/
|
||||
public class DefaultAdvancedHttpResponse extends AdvancedHttpResponse
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs a new {@link DefaultAdvancedHttpResponse}.
|
||||
*
|
||||
* @param client ahc
|
||||
* @param connection http connection
|
||||
* @param status response status code
|
||||
* @param statusText response status text
|
||||
*/
|
||||
DefaultAdvancedHttpResponse(DefaultAdvancedHttpClient client,
|
||||
HttpURLConnection connection, int status, String statusText)
|
||||
{
|
||||
this.client = client;
|
||||
this.connection = connection;
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ByteSource contentAsByteSource() throws IOException
|
||||
{
|
||||
return new URLConnectionByteSource(connection);
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Multimap<String, String> getHeaders()
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
headers = LinkedHashMultimap.create();
|
||||
|
||||
for (Entry<String, List<String>> e :
|
||||
connection.getHeaderFields().entrySet())
|
||||
{
|
||||
headers.putAll(e.getKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getStatusText()
|
||||
{
|
||||
return statusText;
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected ContentTransformer createTransformer(Class<?> type,
|
||||
String contentType)
|
||||
{
|
||||
return client.createTransformer(type, contentType);
|
||||
}
|
||||
|
||||
//~--- inner classes --------------------------------------------------------
|
||||
|
||||
/**
|
||||
* {@link ByteSource} implementation of a http connection.
|
||||
*/
|
||||
private static class URLConnectionByteSource extends ByteSource
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for URLConnectionByteSource
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(URLConnectionByteSource.class);
|
||||
|
||||
//~--- constructors -------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs a new {@link URLConnectionByteSource}.
|
||||
*
|
||||
*
|
||||
* @param connection http connection
|
||||
*/
|
||||
private URLConnectionByteSource(HttpURLConnection connection)
|
||||
{
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
//~--- methods ------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Opens the input stream of http connection, if an error occurs during
|
||||
* opening the method will return the error stream instead.
|
||||
*
|
||||
*
|
||||
* @return input or error stream of http connection
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public InputStream openStream() throws IOException
|
||||
{
|
||||
InputStream stream;
|
||||
|
||||
try
|
||||
{
|
||||
stream = connection.getInputStream();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(
|
||||
"could not open input stream, open error stream instead", ex);
|
||||
}
|
||||
|
||||
stream = connection.getErrorStream();
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
//~--- fields -------------------------------------------------------------
|
||||
|
||||
/** http connection */
|
||||
private final HttpURLConnection connection;
|
||||
}
|
||||
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final DefaultAdvancedHttpClient client;
|
||||
|
||||
/** http connection */
|
||||
private final HttpURLConnection connection;
|
||||
|
||||
/** server response status */
|
||||
private final int status;
|
||||
|
||||
/** server response text */
|
||||
private final String statusText;
|
||||
|
||||
/** http headers */
|
||||
private Multimap<String, String> headers;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer. 2. Redistributions in
|
||||
* binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other
|
||||
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
|
||||
* nor the names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.net.ahc;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.io.ByteSource;
|
||||
|
||||
import org.codehaus.jackson.map.AnnotationIntrospector;
|
||||
import org.codehaus.jackson.map.DeserializationConfig;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.introspect.JacksonAnnotationIntrospector;
|
||||
import org.codehaus.jackson.xc.JaxbAnnotationIntrospector;
|
||||
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
/**
|
||||
* {@link ContentTransformer} for json. The {@link JsonContentTransformer} uses
|
||||
* jacksons {@link ObjectMapper} to marshalling/unmarshalling.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.46
|
||||
*/
|
||||
@Extension
|
||||
public class JsonContentTransformer implements ContentTransformer
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs a new {@link JsonContentTransformer}.
|
||||
*
|
||||
*/
|
||||
public JsonContentTransformer()
|
||||
{
|
||||
this.mapper = new ObjectMapper();
|
||||
|
||||
// allow jackson and jaxb annotations
|
||||
AnnotationIntrospector jackson = new JacksonAnnotationIntrospector();
|
||||
AnnotationIntrospector jaxb = new JaxbAnnotationIntrospector();
|
||||
|
||||
this.mapper.setAnnotationIntrospector(new AnnotationIntrospector.Pair(jackson, jaxb));
|
||||
|
||||
// do not fail on unknown json properties
|
||||
this.mapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ByteSource marshall(Object object)
|
||||
{
|
||||
ByteSource source = null;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
try
|
||||
{
|
||||
mapper.writeValue(baos, object);
|
||||
source = ByteSource.wrap(baos.toByteArray());
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new ContentTransformerException("could not marshall object", ex);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public <T> T unmarshall(Class<T> type, ByteSource content)
|
||||
{
|
||||
T object = null;
|
||||
InputStream stream = null;
|
||||
|
||||
try
|
||||
{
|
||||
stream = content.openBufferedStream();
|
||||
object = mapper.readValue(stream, type);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new ContentTransformerException("could not unmarshall content", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtil.close(stream);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns {@code true}, if the content type is compatible with
|
||||
* application/json.
|
||||
*
|
||||
*
|
||||
* @param type object type
|
||||
* @param contentType content type
|
||||
*
|
||||
* @return {@code true}, if the content type is compatible with
|
||||
* application/json
|
||||
*/
|
||||
@Override
|
||||
public boolean isResponsible(Class<?> type, String contentType)
|
||||
{
|
||||
return MediaType.valueOf(contentType).isCompatible(MediaType.APPLICATION_JSON_TYPE);
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** object mapper */
|
||||
private final ObjectMapper mapper;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer. 2. Redistributions in
|
||||
* binary form must reproduce the above copyright notice, this list of
|
||||
* conditions and the following disclaimer in the documentation and/or other
|
||||
* materials provided with the distribution. 3. Neither the name of SCM-Manager;
|
||||
* nor the names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
|
||||
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.net.ahc;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.io.ByteSource;
|
||||
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import javax.xml.bind.DataBindingException;
|
||||
import javax.xml.bind.JAXB;
|
||||
|
||||
/**
|
||||
* {@link ContentTransformer} for xml. The {@link XmlContentTransformer} uses
|
||||
* jaxb to marshalling/unmarshalling.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.46
|
||||
*/
|
||||
@Extension
|
||||
public class XmlContentTransformer implements ContentTransformer
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ByteSource marshall(Object object)
|
||||
{
|
||||
ByteSource source = null;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
try
|
||||
{
|
||||
JAXB.marshal(object, baos);
|
||||
source = ByteSource.wrap(baos.toByteArray());
|
||||
}
|
||||
catch (DataBindingException ex)
|
||||
{
|
||||
throw new ContentTransformerException("could not marshall object", ex);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public <T> T unmarshall(Class<T> type, ByteSource content)
|
||||
{
|
||||
T object = null;
|
||||
InputStream stream = null;
|
||||
|
||||
try
|
||||
{
|
||||
stream = content.openBufferedStream();
|
||||
object = JAXB.unmarshal(stream, type);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
throw new ContentTransformerException("could not unmarshall content", ex);
|
||||
}
|
||||
catch (DataBindingException ex)
|
||||
{
|
||||
throw new ContentTransformerException("could not unmarshall content", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtil.close(stream);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns {@code true}, if the content type is compatible with
|
||||
* application/xml.
|
||||
*
|
||||
*
|
||||
* @param type object type
|
||||
* @param contentType content type
|
||||
*
|
||||
* @return {@code true}, if the content type is compatible with
|
||||
* application/xml
|
||||
*/
|
||||
@Override
|
||||
public boolean isResponsible(Class<?> type, String contentType)
|
||||
{
|
||||
return MediaType.valueOf(contentType).isCompatible(
|
||||
MediaType.APPLICATION_XML_TYPE);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSet.Builder;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.inject.Module;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -40,20 +40,17 @@ import com.github.legman.Subscribe;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sonia.scm.ConfigurationException;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.cache.Cache;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.config.ScmConfigurationChangedEvent;
|
||||
import sonia.scm.io.ZipUnArchiver;
|
||||
import sonia.scm.net.HttpClient;
|
||||
import sonia.scm.util.AssertUtil;
|
||||
import sonia.scm.util.IOUtil;
|
||||
import sonia.scm.util.SystemUtil;
|
||||
@@ -78,12 +75,12 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.xml.bind.JAXB;
|
||||
import javax.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
|
||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||
|
||||
/**
|
||||
* TODO replace aether stuff
|
||||
* TODO replace aether stuff.
|
||||
* TODO check AdvancedPluginConfiguration from 1.x
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
@@ -117,17 +114,17 @@ public class DefaultPluginManager implements PluginManager
|
||||
* @param configuration
|
||||
* @param pluginLoader
|
||||
* @param cacheManager
|
||||
* @param clientProvider
|
||||
* @param httpClient
|
||||
*/
|
||||
@Inject
|
||||
public DefaultPluginManager(SCMContextProvider context,
|
||||
ScmConfiguration configuration, PluginLoader pluginLoader,
|
||||
CacheManager cacheManager, Provider<HttpClient> clientProvider)
|
||||
CacheManager cacheManager, AdvancedHttpClient httpClient)
|
||||
{
|
||||
this.context = context;
|
||||
this.configuration = configuration;
|
||||
this.cache = cacheManager.getCache(CACHE_NAME);
|
||||
this.clientProvider = clientProvider;
|
||||
this.httpClient = httpClient;
|
||||
installedPlugins = new HashMap<>();
|
||||
|
||||
for (PluginWrapper wrapper : pluginLoader.getInstalledPlugins())
|
||||
@@ -140,16 +137,6 @@ public class DefaultPluginManager implements PluginManager
|
||||
installedPlugins.put(info.getId(), plugin);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
unmarshaller =
|
||||
JAXBContext.newInstance(PluginCenter.class).createUnmarshaller();
|
||||
}
|
||||
catch (JAXBException ex)
|
||||
{
|
||||
throw new ConfigurationException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
@@ -624,21 +611,9 @@ public class DefaultPluginManager implements PluginManager
|
||||
*/
|
||||
if (REMOTE_PLUGINS_ENABLED && Util.isNotEmpty(pluginUrl))
|
||||
{
|
||||
InputStream input = null;
|
||||
|
||||
try
|
||||
{
|
||||
input = clientProvider.get().get(pluginUrl).getContent();
|
||||
|
||||
/*
|
||||
* TODO: add gzip support
|
||||
*
|
||||
* if (gzip)
|
||||
* {
|
||||
* input = new GZIPInputStream(input);
|
||||
* }
|
||||
*/
|
||||
center = (PluginCenter) unmarshaller.unmarshal(input);
|
||||
center = httpClient.get(pluginUrl).request().contentFromXml(PluginCenter.class);
|
||||
preparePlugins(center);
|
||||
cache.put(PluginCenter.class.getName(), center);
|
||||
|
||||
@@ -653,14 +628,10 @@ public class DefaultPluginManager implements PluginManager
|
||||
* pluginHandler.setPluginRepositories(center.getRepositories());
|
||||
*/
|
||||
}
|
||||
catch (IOException | JAXBException ex)
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("could not load plugins from plugin center", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IOUtil.close(input);
|
||||
}
|
||||
}
|
||||
|
||||
if (center == null)
|
||||
@@ -758,7 +729,7 @@ public class DefaultPluginManager implements PluginManager
|
||||
private final Cache<String, PluginCenter> cache;
|
||||
|
||||
/** Field description */
|
||||
private final Provider<HttpClient> clientProvider;
|
||||
private final AdvancedHttpClient httpClient;
|
||||
|
||||
/** Field description */
|
||||
private final ScmConfiguration configuration;
|
||||
@@ -768,7 +739,4 @@ public class DefaultPluginManager implements PluginManager
|
||||
|
||||
/** Field description */
|
||||
private final Map<String, Plugin> installedPlugins;
|
||||
|
||||
/** Field description */
|
||||
private Unmarshaller unmarshaller;
|
||||
}
|
||||
|
||||
@@ -146,6 +146,9 @@ public abstract class AbstractResourceManager implements ResourceManager
|
||||
processPlugin(resources, plugin.getPlugin());
|
||||
}
|
||||
}
|
||||
|
||||
// fix order of script resources, see https://goo.gl/ok03l4
|
||||
Collections.sort(resources);
|
||||
|
||||
return resources;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/***
|
||||
* Copyright (c) 2015, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* https://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
package sonia.scm.schedule;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Provider;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.JobExecutionContext;
|
||||
import org.quartz.JobExecutionException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.web.security.AdministrationContext;
|
||||
import sonia.scm.web.security.PrivilegedAction;
|
||||
|
||||
/**
|
||||
* InjectionEnabledJob allows the execution of quartz jobs and enable injection on them.
|
||||
*
|
||||
* @author Sebastian Sdorra <sebastian.sdorra@triology.de>
|
||||
* @since 1.47
|
||||
*/
|
||||
public class InjectionEnabledJob implements Job {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(InjectionEnabledJob.class);
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext jec) throws JobExecutionException {
|
||||
Preconditions.checkNotNull(jec, "execution context is null");
|
||||
|
||||
JobDetail detail = jec.getJobDetail();
|
||||
Preconditions.checkNotNull(detail, "job detail not provided");
|
||||
|
||||
JobDataMap dataMap = detail.getJobDataMap();
|
||||
Preconditions.checkNotNull(dataMap, "job detail does not contain data map");
|
||||
|
||||
Injector injector = (Injector) dataMap.get(Injector.class.getName());
|
||||
Preconditions.checkNotNull(injector, "data map does not contain injector");
|
||||
|
||||
final Provider<Runnable> runnableProvider = (Provider<Runnable>) dataMap.get(Runnable.class.getName());
|
||||
if (runnableProvider == null) {
|
||||
throw new JobExecutionException("could not find runnable provider");
|
||||
}
|
||||
|
||||
AdministrationContext ctx = injector.getInstance(AdministrationContext.class);
|
||||
ctx.runAsAdmin(new PrivilegedAction()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
logger.trace("create runnable from provider");
|
||||
Runnable runnable = runnableProvider.get();
|
||||
logger.debug("execute injection enabled job {}", runnable.getClass());
|
||||
runnable.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
175
scm-webapp/src/main/java/sonia/scm/schedule/QuartzScheduler.java
Normal file
175
scm-webapp/src/main/java/sonia/scm/schedule/QuartzScheduler.java
Normal file
@@ -0,0 +1,175 @@
|
||||
/***
|
||||
* Copyright (c) 2015, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* https://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
package sonia.scm.schedule;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import javax.inject.Inject;
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDataMap;
|
||||
import org.quartz.JobDetail;
|
||||
import org.quartz.SchedulerException;
|
||||
import org.quartz.Trigger;
|
||||
import org.quartz.TriggerBuilder;
|
||||
import org.quartz.impl.StdSchedulerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.Initable;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
|
||||
/**
|
||||
* {@link Scheduler} which uses the quartz scheduler.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @since 1.47
|
||||
*
|
||||
* @see <a href="http://www.quartz-scheduler.org/">Quartz Job Scheduler</a>
|
||||
*/
|
||||
@Singleton
|
||||
public class QuartzScheduler implements Scheduler, Initable {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(QuartzScheduler.class);
|
||||
|
||||
private final Injector injector;
|
||||
private final org.quartz.Scheduler scheduler;
|
||||
|
||||
/**
|
||||
* Creates a new quartz scheduler.
|
||||
*
|
||||
* @param injector injector
|
||||
*/
|
||||
@Inject
|
||||
public QuartzScheduler(Injector injector)
|
||||
{
|
||||
this.injector = injector;
|
||||
|
||||
// get default scheduler
|
||||
try {
|
||||
scheduler = StdSchedulerFactory.getDefaultScheduler();
|
||||
} catch (SchedulerException ex) {
|
||||
throw Throwables.propagate(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new quartz scheduler. This constructor is only for testing.
|
||||
*
|
||||
* @param injector injector
|
||||
* @param scheduler quartz scheduler
|
||||
*/
|
||||
QuartzScheduler(Injector injector, org.quartz.Scheduler scheduler)
|
||||
{
|
||||
this.injector = injector;
|
||||
this.scheduler = scheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(SCMContextProvider context)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!scheduler.isStarted())
|
||||
{
|
||||
scheduler.start();
|
||||
}
|
||||
}
|
||||
catch (SchedulerException ex)
|
||||
{
|
||||
logger.error("can not start scheduler", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
if (scheduler.isStarted()){
|
||||
scheduler.shutdown();
|
||||
}
|
||||
}
|
||||
catch (SchedulerException ex)
|
||||
{
|
||||
logger.error("can not stop scheduler", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task schedule(String expression, final Runnable runnable)
|
||||
{
|
||||
return schedule(expression, new Provider<Runnable>(){
|
||||
@Override
|
||||
public Runnable get()
|
||||
{
|
||||
return runnable;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Task schedule(String expression, Class<? extends Runnable> runnable)
|
||||
{
|
||||
return schedule(expression, injector.getProvider(runnable));
|
||||
}
|
||||
|
||||
private Task schedule(String expression, Provider<? extends Runnable> provider){
|
||||
// create data map with injection provider for InjectionEnabledJob
|
||||
JobDataMap map = new JobDataMap();
|
||||
map.put(Runnable.class.getName(), provider);
|
||||
map.put(Injector.class.getName(), injector);
|
||||
|
||||
// create job detail for InjectionEnabledJob with the provider for the annotated class
|
||||
JobDetail detail = JobBuilder.newJob(InjectionEnabledJob.class)
|
||||
.usingJobData(map)
|
||||
.build();
|
||||
|
||||
// create a trigger with the cron expression from the annotation
|
||||
Trigger trigger = TriggerBuilder.newTrigger()
|
||||
.forJob(detail)
|
||||
.withSchedule(CronScheduleBuilder.cronSchedule(expression))
|
||||
.build();
|
||||
|
||||
try {
|
||||
scheduler.scheduleJob(detail, trigger);
|
||||
} catch (SchedulerException ex) {
|
||||
throw Throwables.propagate(ex);
|
||||
}
|
||||
|
||||
return new QuartzTask(scheduler, trigger.getJobKey());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
68
scm-webapp/src/main/java/sonia/scm/schedule/QuartzTask.java
Normal file
68
scm-webapp/src/main/java/sonia/scm/schedule/QuartzTask.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/***
|
||||
* Copyright (c) 2015, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* https://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
package sonia.scm.schedule;
|
||||
|
||||
import com.google.common.base.Throwables;
|
||||
import org.quartz.JobKey;
|
||||
import org.quartz.Scheduler;
|
||||
import org.quartz.SchedulerException;
|
||||
|
||||
/**
|
||||
* Task implementation for the {@link QuartzScheduler}.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
public class QuartzTask implements Task {
|
||||
|
||||
private final org.quartz.Scheduler scheduler;
|
||||
private final JobKey jobKey;
|
||||
|
||||
QuartzTask(Scheduler scheduler, JobKey jobKey)
|
||||
{
|
||||
this.scheduler = scheduler;
|
||||
this.jobKey = jobKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel()
|
||||
{
|
||||
try
|
||||
{
|
||||
scheduler.deleteJob(jobKey);
|
||||
}
|
||||
catch (SchedulerException ex)
|
||||
{
|
||||
throw Throwables.propagate(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,6 +70,10 @@ import sonia.scm.util.Util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import sonia.scm.group.Group;
|
||||
import sonia.scm.group.GroupModificationEvent;
|
||||
import sonia.scm.repository.RepositoryModificationEvent;
|
||||
import sonia.scm.user.UserModificationEvent;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -80,6 +84,9 @@ import java.util.Set;
|
||||
public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
{
|
||||
|
||||
// TODO move to util class
|
||||
private static final String SEPARATOR = System.getProperty("line.separator", "\n");
|
||||
|
||||
/** Field description */
|
||||
private static final String ADMIN_PERMISSION = "*";
|
||||
|
||||
@@ -139,10 +146,15 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Invalidates the cache of a user which was modified. The cache entries for the user will be invalidated for the
|
||||
* following reasons:
|
||||
* <ul>
|
||||
* <li>Admin or Active flag was modified.</li>
|
||||
* <li>New user created, for the case of old cache values</li>
|
||||
* <li>User deleted</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param event
|
||||
* @param event user event
|
||||
*/
|
||||
@Subscribe
|
||||
public void onEvent(UserEvent event)
|
||||
@@ -150,82 +162,160 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
if (event.getEventType().isPost())
|
||||
{
|
||||
User user = event.getItem();
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
String username = user.getId();
|
||||
if (event instanceof UserModificationEvent)
|
||||
{
|
||||
logger.debug(
|
||||
"clear cache of user {}, because user properties have changed",
|
||||
user.getName());
|
||||
User beforeModification = ((UserModificationEvent) event).getItemBeforeModification();
|
||||
if (shouldCacheBeCleared(user, beforeModification))
|
||||
{
|
||||
logger.debug("invalidate cache of user {}, because of a permission relevant field has changed", username);
|
||||
invalidateUserCache(username);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("cache of user {} is not invalidated, because no permission relevant field has changed", username);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("invalidate cache of user {}, because of user {} event", username, event.getEventType());
|
||||
invalidateUserCache(username);
|
||||
}
|
||||
|
||||
// check if this is neccessary
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCacheBeCleared(User user, User beforeModification)
|
||||
{
|
||||
return user.isAdmin() != beforeModification.isAdmin() || user.isActive() != beforeModification.isActive();
|
||||
}
|
||||
|
||||
private void invalidateUserCache(final String username)
|
||||
{
|
||||
cache.removeAll((CacheKey item) -> username.equalsIgnoreCase(item.username));
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Invalidates the whole cache, if a repository has changed. The cache get cleared for one of the following reasons:
|
||||
* <ul>
|
||||
* <li>New repository created</li>
|
||||
* <li>Repository was removed</li>
|
||||
* <li>Archived, Public readable or permission field of the repository was modified</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param event
|
||||
* @param event repository event
|
||||
*/
|
||||
@Subscribe
|
||||
public void onEvent(RepositoryEvent event)
|
||||
{
|
||||
if (event.getEventType().isPost())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
Repository repository = event.getItem();
|
||||
|
||||
if (event instanceof RepositoryModificationEvent)
|
||||
{
|
||||
logger.debug("clear cache, because repository {} has changed",
|
||||
event.getItem().getName());
|
||||
Repository beforeModification = ((RepositoryModificationEvent) event).getItemBeforeModification();
|
||||
if (shouldCacheBeCleared(repository, beforeModification))
|
||||
{
|
||||
logger.debug("clear cache, because a relevant field of repository {} has changed", repository.getName());
|
||||
cache.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug(
|
||||
"cache is not invalidated, because non relevant field of repository {} has changed",
|
||||
repository.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("clear cache, received {} event of repository {}", event.getEventType(), repository.getName());
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCacheBeCleared(Repository repository, Repository beforeModification)
|
||||
{
|
||||
return repository.isArchived() != beforeModification.isArchived()
|
||||
|| repository.isPublicReadable() != beforeModification.isPublicReadable()
|
||||
|| ! repository.getPermissions().equals(beforeModification.getPermissions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Invalidates the whole cache if a group permission has changed and invalidates the cached entries of a user, if a
|
||||
* user permission has changed.
|
||||
*
|
||||
*
|
||||
* @param event
|
||||
* @param event permission event
|
||||
*/
|
||||
@Subscribe
|
||||
public void onEvent(StoredAssignedPermissionEvent event)
|
||||
{
|
||||
if (event.getEventType().isPost())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
StoredAssignedPermission permission = event.getPermission();
|
||||
if (permission.isGroupPermission())
|
||||
{
|
||||
logger.debug("clear cache, because permission {} has changed",
|
||||
event.getPermission().getId());
|
||||
logger.debug("clear cache, because global group permission {} has changed", permission.getId());
|
||||
cache.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug(
|
||||
"clear cache of user {}, because permission {} has changed",
|
||||
permission.getName(), event.getPermission().getId()
|
||||
);
|
||||
invalidateUserCache(permission.getName());
|
||||
}
|
||||
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
* Invalidates the whole cache, if a group has changed. The cache get cleared for one of the following reasons:
|
||||
* <ul>
|
||||
* <li>New group created</li>
|
||||
* <li>Group was removed</li>
|
||||
* <li>Group members was modified</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param event
|
||||
* @param event group event
|
||||
*/
|
||||
@Subscribe
|
||||
public void onEvent(GroupEvent event)
|
||||
{
|
||||
if (event.getEventType().isPost())
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
Group group = event.getItem();
|
||||
if (event instanceof GroupModificationEvent)
|
||||
{
|
||||
logger.debug("clear cache, because group {} has changed",
|
||||
event.getItem().getId());
|
||||
Group beforeModification = ((GroupModificationEvent) event).getItemBeforeModification();
|
||||
if (shouldCacheBeCleared(group, beforeModification))
|
||||
{
|
||||
logger.debug("clear cache, because group {} has changed", group.getId());
|
||||
cache.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug(
|
||||
"cache is not invalidated, because non relevant field of group {} has changed",
|
||||
group.getId()
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug("clear cache, received group event {} for group {}", event.getEventType(), group.getId());
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldCacheBeCleared(Group group, Group beforeModification)
|
||||
{
|
||||
return !group.getMembers().equals(beforeModification.getMembers());
|
||||
}
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -251,18 +341,13 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("collect AuthorizationInfo for user {}", user.getName());
|
||||
}
|
||||
|
||||
logger.trace("collect AuthorizationInfo for user {}", user.getName());
|
||||
info = createAuthorizationInfo(user, groupNames);
|
||||
cache.put(cacheKey, info);
|
||||
}
|
||||
else if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("retrieve AuthorizationInfo for user {} from cache",
|
||||
user.getName());
|
||||
logger.trace("retrieve AuthorizationInfo for user {} from cache", user.getName());
|
||||
}
|
||||
|
||||
return info;
|
||||
@@ -271,21 +356,8 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
private void collectGlobalPermissions(Builder<String> builder,
|
||||
final User user, final GroupNames groups)
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("collect global permissions for user {}", user.getName());
|
||||
}
|
||||
|
||||
List<StoredAssignedPermission> globalPermissions =
|
||||
securitySystem.getPermissions(new Predicate<AssignedPermission>()
|
||||
{
|
||||
|
||||
@Override
|
||||
public boolean apply(AssignedPermission input)
|
||||
{
|
||||
return isUserPermission(user, groups, input);
|
||||
}
|
||||
});
|
||||
securitySystem.getPermissions((AssignedPermission input) -> isUserPermitted(user, groups, input));
|
||||
|
||||
for (StoredAssignedPermission gp : globalPermissions)
|
||||
{
|
||||
@@ -301,12 +373,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
{
|
||||
for (Repository repository : repositoryDAO.getAll())
|
||||
{
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("collect permissions for repository {} and user {}",
|
||||
repository.getName(), user.getName());
|
||||
}
|
||||
|
||||
collectRepositoryPermissions(builder, repository, user, groups);
|
||||
}
|
||||
}
|
||||
@@ -314,30 +380,36 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
private void collectRepositoryPermissions(Builder<String> builder,
|
||||
Repository repository, User user, GroupNames groups)
|
||||
{
|
||||
List<sonia.scm.repository.Permission> repositoryPermissions =
|
||||
repository.getPermissions();
|
||||
List<sonia.scm.repository.Permission> repositoryPermissions
|
||||
= repository.getPermissions();
|
||||
|
||||
if (Util.isNotEmpty(repositoryPermissions))
|
||||
{
|
||||
|
||||
boolean hasPermission = false;
|
||||
for (sonia.scm.repository.Permission permission : repositoryPermissions)
|
||||
{
|
||||
if (isUserPermission(user, groups, permission))
|
||||
if (isUserPermitted(user, groups, permission))
|
||||
{
|
||||
|
||||
String perm = permission.getType().getPermissionPrefix().concat(
|
||||
repository.getId());
|
||||
|
||||
logger.trace("add repository permission {} for user {}", perm,
|
||||
user.getName());
|
||||
String perm = permission.getType().getPermissionPrefix().concat(repository.getId());
|
||||
if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("add repository permission {} for user {} at repository {}",
|
||||
perm, user.getName(), repository.getName());
|
||||
}
|
||||
|
||||
builder.add(perm);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPermission && logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("no permission for user {} defined at repository {}", user.getName(), repository.getName());
|
||||
}
|
||||
}
|
||||
else if (logger.isTraceEnabled())
|
||||
{
|
||||
logger.trace("repository {} has not permission entries",
|
||||
logger.trace("repository {} has no permission entries",
|
||||
repository.getName());
|
||||
}
|
||||
}
|
||||
@@ -371,19 +443,47 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
}
|
||||
|
||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
|
||||
|
||||
info.addStringPermissions(permissions);
|
||||
|
||||
|
||||
if (logger.isTraceEnabled()){
|
||||
logger.trace(createAuthorizationSummary(user, groups, info));
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private String createAuthorizationSummary(User user, GroupNames groups, AuthorizationInfo authzInfo)
|
||||
{
|
||||
StringBuilder buffer = new StringBuilder("authorization summary: ");
|
||||
|
||||
buffer.append(SEPARATOR).append("username : ").append(user.getName());
|
||||
buffer.append(SEPARATOR).append("groups : ");
|
||||
append(buffer, groups);
|
||||
buffer.append(SEPARATOR).append("roles : ");
|
||||
append(buffer, authzInfo.getRoles());
|
||||
buffer.append(SEPARATOR).append("permissions:");
|
||||
append(buffer, authzInfo.getStringPermissions());
|
||||
append(buffer, authzInfo.getObjectPermissions());
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private void append(StringBuilder buffer, Iterable<?> iterable){
|
||||
if (iterable != null){
|
||||
for ( Object item : iterable )
|
||||
{
|
||||
buffer.append(SEPARATOR).append(" - ").append(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
|
||||
private boolean isUserPermission(User user, GroupNames groups,
|
||||
private boolean isUserPermitted(User user, GroupNames groups,
|
||||
PermissionObject perm)
|
||||
{
|
||||
//J-
|
||||
return (perm.isGroupPermission() && groups.contains(perm.getName()))
|
||||
return (perm.isGroupPermission() && groups.contains(perm.getName()))
|
||||
|| ((!perm.isGroupPermission()) && user.getName().equals(perm.getName()));
|
||||
//J+
|
||||
}
|
||||
@@ -443,7 +543,6 @@ public class DefaultAuthorizationCollector implements AuthorizationCollector
|
||||
private final String username;
|
||||
}
|
||||
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** authorization cache */
|
||||
|
||||
87
scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java
Normal file
87
scm-webapp/src/main/java/sonia/scm/security/XsrfCookies.java
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.security;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Util methods to handle XsrfCookies.
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
* @version 1.47
|
||||
*/
|
||||
public final class XsrfCookies
|
||||
{
|
||||
|
||||
private XsrfCookies()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new xsrf protection cookie and add it to the response.
|
||||
*
|
||||
* @param request http servlet request
|
||||
* @param response http servlet response
|
||||
* @param token xsrf token
|
||||
*/
|
||||
public static void create(HttpServletRequest request, HttpServletResponse response, String token){
|
||||
applyCookie(request, response, new Cookie(XsrfProtectionFilter.KEY, token));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the current xsrf protection cookie from response.
|
||||
*
|
||||
* @param request http servlet request
|
||||
* @param response http servlet response
|
||||
*/
|
||||
public static void remove(HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if ( cookies != null ){
|
||||
for ( Cookie c : cookies ){
|
||||
if ( XsrfProtectionFilter.KEY.equals(c.getName()) ){
|
||||
c.setMaxAge(0);
|
||||
c.setValue(null);
|
||||
applyCookie(request, response, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void applyCookie(HttpServletRequest request, HttpServletResponse response, Cookie cookie){
|
||||
cookie.setPath(request.getContextPath());
|
||||
response.addCookie(cookie);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* Copyright (c) 2014, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
package sonia.scm.security;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.inject.Inject;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import javax.inject.Singleton;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.Priority;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.filter.Filters;
|
||||
import sonia.scm.filter.WebElement;
|
||||
import sonia.scm.util.HttpUtil;
|
||||
import sonia.scm.web.filter.HttpFilter;
|
||||
|
||||
/**
|
||||
* Xsrf protection http filter. The filter will issue an cookie with an xsrf protection token on the first ajax request
|
||||
* of the scm web interface and marks the http session as xsrf protected. On every other request within a protected
|
||||
* session, the web interface has to send the token from the cookie as http header on every request. If the filter
|
||||
* receives an request to a protected session, without proper xsrf header the filter will abort the request and send an
|
||||
* http error code back to the client. If the filter receives an request to a non protected session, from a non web
|
||||
* interface client the filter will call the chain. The {@link XsrfProtectionFilter} is disabled by default and can be
|
||||
* enabled with {@link ScmConfiguration#setEnabledXsrfProtection(boolean)}.
|
||||
*
|
||||
* TODO for scm-manager 2 we have to store the csrf token as part of the jwt token instead of session.
|
||||
*
|
||||
* @see https://bitbucket.org/sdorra/scm-manager/issues/793/json-hijacking-vulnerability-cwe-116-cwe
|
||||
* @author Sebastian Sdorra
|
||||
* @version 1.47
|
||||
*/
|
||||
@WebElement(Filters.PATTERN_RESTAPI)
|
||||
@Priority(Filters.PRIORITY_PRE_BASEURL)
|
||||
public final class XsrfProtectionFilter extends HttpFilter
|
||||
{
|
||||
|
||||
/**
|
||||
* the logger for XsrfProtectionFilter
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(XsrfProtectionFilter.class);
|
||||
|
||||
/**
|
||||
* Key used for session, header and cookie.
|
||||
*/
|
||||
static final String KEY = "X-XSRF-Token";
|
||||
|
||||
@Inject
|
||||
public XsrfProtectionFilter(ScmConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws
|
||||
IOException, ServletException
|
||||
{
|
||||
if (configuration.isEnabledXsrfProtection())
|
||||
{
|
||||
doXsrfProtection(request, response, chain);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.trace("xsrf protection is disabled, skipping check");
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private void doXsrfProtection(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws
|
||||
IOException, ServletException
|
||||
{
|
||||
HttpSession session = request.getSession(true);
|
||||
String storedToken = (String) session.getAttribute(KEY);
|
||||
if ( ! Strings.isNullOrEmpty(storedToken) ){
|
||||
String headerToken = request.getHeader(KEY);
|
||||
if ( storedToken.equals(headerToken) ){
|
||||
logger.trace("received valid xsrf protected request");
|
||||
chain.doFilter(request, response);
|
||||
} else {
|
||||
// is forbidden the correct status code?
|
||||
logger.warn("received request to a xsrf protected session without proper xsrf token");
|
||||
response.sendError(HttpServletResponse.SC_FORBIDDEN);
|
||||
}
|
||||
} else if (HttpUtil.isWUIRequest(request)) {
|
||||
logger.debug("received wui request, mark session as xsrf protected and issue a new token");
|
||||
String token = createToken();
|
||||
session.setAttribute(KEY, token);
|
||||
XsrfCookies.create(request, response, token);
|
||||
chain.doFilter(request, response);
|
||||
} else {
|
||||
// handle non webinterface clients, which does not need xsrf protection
|
||||
logger.trace("received request to a non xsrf protected session");
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
private String createToken()
|
||||
{
|
||||
// TODO create interface and use a better method
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
private ScmConfiguration configuration;
|
||||
}
|
||||
@@ -184,8 +184,8 @@ public class ErrorServlet extends HttpServlet
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private SCMContextProvider context;
|
||||
private final SCMContextProvider context;
|
||||
|
||||
/** Field description */
|
||||
private TemplateEngineFactory templateEngineFactory;
|
||||
private final TemplateEngineFactory templateEngineFactory;
|
||||
}
|
||||
|
||||
@@ -237,15 +237,14 @@ public class DefaultUserManager extends AbstractUserManager
|
||||
}
|
||||
|
||||
UserPermissions.modify(user).check();
|
||||
User oldUser = userDAO.get(name);
|
||||
|
||||
if (oldUser != null)
|
||||
User notModified = userDAO.get(name);
|
||||
if (notModified != null)
|
||||
{
|
||||
AssertUtil.assertIsValid(user);
|
||||
fireEvent(HandlerEventType.BEFORE_MODIFY, user, notModified);
|
||||
user.setLastModified(System.currentTimeMillis());
|
||||
fireEvent(HandlerEventType.BEFORE_MODIFY, user, oldUser);
|
||||
userDAO.modify(user);
|
||||
fireEvent(HandlerEventType.MODIFY, user, oldUser);
|
||||
fireEvent(HandlerEventType.MODIFY, user, notModified);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -33,6 +33,12 @@ package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||
import org.apache.shiro.codec.Base64;
|
||||
|
||||
@@ -59,15 +65,28 @@ public class BasicWebTokenGenerator extends SchemeBasedWebTokenGenerator
|
||||
{
|
||||
|
||||
/** credential separator for basic authentication */
|
||||
public static final String CREDENTIAL_SEPARATOR = ":";
|
||||
private static final String CREDENTIAL_SEPARATOR = ":";
|
||||
|
||||
/** default encoding to decode basic authentication header */
|
||||
private static final Charset DEFAULT_ENCODING = Charsets.ISO_8859_1;
|
||||
|
||||
/**
|
||||
* the logger for BasicWebTokenGenerator
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(BasicWebTokenGenerator.class);
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
private final UserAgentParser userAgentParser;
|
||||
|
||||
/**
|
||||
* Constructs a new BasicWebTokenGenerator.
|
||||
*
|
||||
* @param userAgentParser parser for user-agent header
|
||||
*/
|
||||
@Inject
|
||||
public BasicWebTokenGenerator(UserAgentParser userAgentParser) {
|
||||
this.userAgentParser = userAgentParser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link UsernamePasswordToken} from an authorization header with
|
||||
@@ -88,7 +107,7 @@ public class BasicWebTokenGenerator extends SchemeBasedWebTokenGenerator
|
||||
|
||||
if (HttpUtil.AUTHORIZATION_SCHEME_BASIC.equalsIgnoreCase(scheme))
|
||||
{
|
||||
String token = new String(Base64.decode(authorization.getBytes()));
|
||||
String token = decodeAuthenticationHeader(request, authorization);
|
||||
|
||||
int index = token.indexOf(CREDENTIAL_SEPARATOR);
|
||||
|
||||
@@ -115,4 +134,32 @@ public class BasicWebTokenGenerator extends SchemeBasedWebTokenGenerator
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode base64 of the basic authentication header. The method will use
|
||||
* the charset provided by the {@link UserAgent}, if the
|
||||
* {@link UserAgentParser} is not available the method will be fall back to
|
||||
* ISO-8859-1.
|
||||
*
|
||||
* @param request http request
|
||||
* @param authentication base64 encoded basic authentication string
|
||||
*
|
||||
* @return decoded basic authentication header
|
||||
*
|
||||
* @see <a href="http://goo.gl/tZEBS3">issue 627</a>
|
||||
* @see <a href="http://goo.gl/NhbZ2F">Stackoverflow Basic Authentication</a>
|
||||
*
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
private String decodeAuthenticationHeader(HttpServletRequest request, String authentication)
|
||||
{
|
||||
Charset encoding = DEFAULT_ENCODING;
|
||||
|
||||
if (userAgentParser != null)
|
||||
{
|
||||
encoding = userAgentParser.parse(request).getBasicAuthenticationCharset();
|
||||
}
|
||||
|
||||
return new String(Base64.decode(authentication), encoding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) 2010, Sebastian Sdorra
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from this
|
||||
* software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* http://bitbucket.org/sdorra/scm-manager
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
package sonia.scm.web;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra <s.sdorra@gmail.com>
|
||||
*/
|
||||
@Extension
|
||||
public class BrowserUserAgentProvider implements UserAgentProvider
|
||||
{
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final UserAgent CHROME = UserAgent.builder(
|
||||
"Chrome").basicAuthenticationCharset(
|
||||
Charsets.UTF_8).build();
|
||||
|
||||
/** Field description */
|
||||
private static final String CHROME_PATTERN = "chrome";
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final UserAgent FIREFOX = UserAgent.builder("Firefox").build();
|
||||
|
||||
/** Field description */
|
||||
private static final String FIREFOX_PATTERN = "firefox";
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting
|
||||
static final UserAgent MSIE = UserAgent.builder("Internet Explorer").build();
|
||||
|
||||
/** Field description */
|
||||
private static final String MSIE_PATTERN = "msie";
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting // todo check charset
|
||||
static final UserAgent SAFARI = UserAgent.builder("Safari").build();
|
||||
|
||||
/** Field description */
|
||||
private static final String OPERA_PATTERN = "opera";
|
||||
|
||||
/** Field description */
|
||||
private static final String SAFARI_PATTERN = "safari";
|
||||
|
||||
/** Field description */
|
||||
@VisibleForTesting // todo check charset
|
||||
static final UserAgent OPERA = UserAgent.builder(
|
||||
"Opera").basicAuthenticationCharset(
|
||||
Charsets.UTF_8).build();
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
*
|
||||
* @param userAgentString
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public UserAgent parseUserAgent(String userAgentString)
|
||||
{
|
||||
UserAgent ua = null;
|
||||
|
||||
if (userAgentString.contains(CHROME_PATTERN))
|
||||
{
|
||||
ua = CHROME;
|
||||
}
|
||||
else if (userAgentString.contains(FIREFOX_PATTERN))
|
||||
{
|
||||
ua = FIREFOX;
|
||||
}
|
||||
else if (userAgentString.contains(OPERA_PATTERN))
|
||||
{
|
||||
ua = OPERA;
|
||||
}
|
||||
else if (userAgentString.contains(MSIE_PATTERN))
|
||||
{
|
||||
ua = MSIE;
|
||||
}
|
||||
else if (userAgentString.contains(SAFARI_PATTERN))
|
||||
{
|
||||
ua = SAFARI;
|
||||
}
|
||||
|
||||
return ua;
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,7 @@ import java.io.OutputStream;
|
||||
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletInputStream;
|
||||
@@ -94,15 +95,17 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
||||
* Constructs ...
|
||||
*
|
||||
*
|
||||
* @param executor to handle error stream processing
|
||||
* @param configuration
|
||||
* @param context
|
||||
* @param request
|
||||
* @param response
|
||||
*/
|
||||
public DefaultCGIExecutor(ScmConfiguration configuration,
|
||||
ServletContext context, HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
public DefaultCGIExecutor(ExecutorService executor,
|
||||
ScmConfiguration configuration, ServletContext context,
|
||||
HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
this.executor = executor;
|
||||
this.configuration = configuration;
|
||||
this.context = context;
|
||||
this.request = request;
|
||||
@@ -190,7 +193,7 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
||||
p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), workDirectory);
|
||||
execute(p);
|
||||
}
|
||||
catch (Throwable ex)
|
||||
catch (IOException ex)
|
||||
{
|
||||
getExceptionHandler().handleException(request, response, ex);
|
||||
}
|
||||
@@ -507,7 +510,7 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
||||
*/
|
||||
private void processErrorStreamAsync(final Process process)
|
||||
{
|
||||
new Thread(new Runnable()
|
||||
executor.execute(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
@@ -528,7 +531,7 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
||||
IOUtil.close(errorStream);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -539,6 +542,8 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
||||
*/
|
||||
private void processServletInput(Process process)
|
||||
{
|
||||
logger.trace("process servlet input");
|
||||
|
||||
OutputStream processOS = null;
|
||||
ServletInputStream servletIS = null;
|
||||
|
||||
@@ -637,6 +642,9 @@ public class DefaultCGIExecutor extends AbstractCGIExecutor
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** executor to handle error stream processing */
|
||||
private final ExecutorService executor;
|
||||
|
||||
/** Field description */
|
||||
private ScmConfiguration configuration;
|
||||
|
||||
|
||||
@@ -35,10 +35,15 @@ package sonia.scm.web.cgi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -50,6 +55,21 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class DefaultCGIExecutorFactory implements CGIExecutorFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
*/
|
||||
public DefaultCGIExecutorFactory()
|
||||
{
|
||||
//J-
|
||||
this.executor = Executors.newCachedThreadPool(
|
||||
new ThreadFactoryBuilder().setNameFormat("cgi-pool-%d").build()
|
||||
);
|
||||
//J+
|
||||
}
|
||||
|
||||
//~--- methods --------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Method description
|
||||
*
|
||||
@@ -63,10 +83,15 @@ public class DefaultCGIExecutorFactory implements CGIExecutorFactory
|
||||
*/
|
||||
@Override
|
||||
public CGIExecutor createExecutor(ScmConfiguration configuration,
|
||||
ServletContext context,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
ServletContext context, HttpServletRequest request,
|
||||
HttpServletResponse response)
|
||||
{
|
||||
return new DefaultCGIExecutor(configuration, context, request, response);
|
||||
return new DefaultCGIExecutor(executor, configuration, context, request,
|
||||
response);
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private final ExecutorService executor;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user