merge with branch 1.x

This commit is contained in:
Sebastian Sdorra
2017-01-12 19:50:39 +01:00
250 changed files with 16399 additions and 1573 deletions

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);

View File

@@ -1111,7 +1111,7 @@ public class RepositoryResource
*/
private String getContentDispositionName(String name)
{
return "attachment; filename=\"".concat(name).concat("\"");
return HttpUtil.createContentDispositionAttachmentHeader(name);
}
/**

View 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();
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -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);

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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
{

View 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.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;
}
}

View File

@@ -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("&");
}
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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();
}
});
}
}

View 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());
}
}

View 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);
}
}
}

View File

@@ -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 */

View 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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
{

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}