merge with branch 1.x

This commit is contained in:
Sebastian Sdorra
2014-04-28 13:48:11 +02:00
17 changed files with 338 additions and 59 deletions

12
pom.xml
View File

@@ -443,23 +443,25 @@
<powermock.version>1.5.3</powermock.version>
<!-- logging libraries -->
<slf4j.version>1.7.6</slf4j.version>
<logback.version>1.1.1</logback.version>
<slf4j.version>1.7.7</slf4j.version>
<logback.version>1.1.2</logback.version>
<servlet.version>2.5</servlet.version>
<guice.version>3.0</guice.version>
<jersey.version>1.18.1</jersey.version>
<freemarker.version>2.3.20</freemarker.version>
<jetty.version>7.6.14.v20131031</jetty.version>
<!-- event bus -->
<legman.version>1.2.0</legman.version>
<!-- webserver -->
<jetty.version>7.6.15.v20140411</jetty.version>
<!-- security libraries -->
<shiro.version>1.2.3</shiro.version>
<!-- repostitory libraries -->
<jgit.version>3.3.0.201403021825-r</jgit.version>
<svnkit.version>1.8.4-scm2</svnkit.version>
<jgit.version>3.3.2.201404171909-r</jgit.version>
<svnkit.version>1.8.5-scm1</svnkit.version>
<!-- util libraries -->
<guava.version>16.0.1</guava.version>

View File

@@ -359,6 +359,19 @@ public class ScmConfiguration
return forceBaseUrl;
}
/**
* Returns true if the login attempt limit is enabled.
*
*
* @return true if login attempt limit is enabled
*
* @since 1.37
*/
public boolean isLoginAttemptLimitEnabled()
{
return loginAttemptLimit > 0;
}
/**
* Returns true if failed authenticators are skipped.
*

View File

@@ -33,6 +33,10 @@
package sonia.scm.util;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings;
//~--- JDK imports ------------------------------------------------------------
import java.math.BigInteger;
@@ -352,15 +356,14 @@ public final class Util
}
/**
* Method description
*
*
* @param value
* Returns an emtpy string, if the object is null. Otherwise the result of
* the toString method of the object is returned is returned.
*
* @param value object
*
* @since 1.13
*
* @return
* @return string value or empty string
*/
public static String nonNull(Object value)
{
@@ -369,6 +372,23 @@ public final class Util
: "";
}
/**
* Returns an emtpy string, if the string is null. Otherwise the string
* is returned. The method is available to fix a possible linkage error which
* was introduced with version 1.14. Please have a look at:
* https://bitbucket.org/sdorra/scm-manager/issue/569/active-directory-plugin-not-working-in
*
* @param value string value
*
* @return string value or empty string
*
* @since 1.38
*/
public static String nonNull(String value)
{
return Strings.nullToEmpty(value);
}
/**
* Method description
*

View File

@@ -91,7 +91,6 @@ public class GitBasicAuthenticationFilter extends BasicAuthenticationFilter
HttpServletResponse response)
throws IOException
{
System.out.println(ClientMessages.get(request).failedAuthentication());
if (GitUtil.isGitClient(request))
{
GitSmartHttpTools.sendError(request, response,

View File

@@ -87,7 +87,8 @@ public class HgBasicAuthenticationFilter extends BasicAuthenticationFilter
HttpServletResponse response)
throws IOException
{
if (HgUtil.isHgClient(request))
if (HgUtil.isHgClient(request)
&& (configuration.isLoginAttemptLimitEnabled()))
{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}

View File

@@ -30,18 +30,23 @@
*/
package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Closeables;
import sonia.scm.repository.Repository;
import sonia.scm.repository.SvnRepositoryHandler;
import sonia.scm.repository.SvnUtil;
import sonia.scm.repository.api.Command;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.Set;
/**
@@ -53,8 +58,8 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
/** Field description */
public static final Set<Command> COMMANDS = ImmutableSet.of(Command.BLAME,
Command.BROWSE, Command.CAT,
Command.DIFF, Command.LOG);
Command.BROWSE, Command.CAT,
Command.DIFF, Command.LOG);
//~--- constructors ---------------------------------------------------------
@@ -66,12 +71,26 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
* @param repository
*/
SvnRepositoryServiceProvider(SvnRepositoryHandler handler,
Repository repository)
Repository repository)
{
this.repository = repository;
this.context = new SvnContext(handler.getDirectory(repository));
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @throws IOException
*/
@Override
public void close() throws IOException
{
Closeables.close(context, true);
}
//~--- get methods ----------------------------------------------------------
/**
@@ -149,8 +168,8 @@ public class SvnRepositoryServiceProvider extends RepositoryServiceProvider
//~--- fields ---------------------------------------------------------------
/** Field description */
private SvnContext context;
private final SvnContext context;
/** Field description */
private Repository repository;
private final Repository repository;
}

View File

@@ -170,7 +170,9 @@ public abstract class RepositoryManagerTestBase
Repository repository = createTestRepository();
repository.setArchived(true);
delete(createRepositoryManager(true), repository);
RepositoryManager drm = createRepositoryManager(true);
drm.init(contextProvider);
delete(drm, repository);
}
/**
@@ -255,6 +257,7 @@ public abstract class RepositoryManagerTestBase
public void testListener() throws RepositoryException, IOException
{
RepositoryManager repoManager = createRepositoryManager(false);
repoManager.init(contextProvider);
TestListener listener = new TestListener();
ScmEventBus.getInstance().register(listener);

View File

@@ -205,6 +205,14 @@
<version>${guava.version}</version>
</dependency>
<!-- fix version conflict -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.6</version>
</dependency>
<!-- template engine -->
<dependency>
@@ -477,7 +485,7 @@
</systemProperty>
<systemProperty>
<name>scm.stage</name>
<value>${scm.stage}</value>
<value>production</value>
</systemProperty>
<systemProperty>
<name>java.awt.headless</name>

View File

@@ -196,9 +196,10 @@ public class ScmContextListener extends GuiceServletContextListener
moduleList.addAll(pluginLoader.getInjectionModules());
moduleList.addAll(overrides.getModules());
SCMContextProvider ctx = SCMContext.getContext();
return Guice.createInjector(ctx.getStage().getInjectionStage(), moduleList);
// TODO: fix cyclic dependencies in production environment
// SCMContextProvider ctx = SCMContext.getContext();
// return Guice.createInjector(ctx.getStage().getInjectionStage(), moduleList);
return Guice.createInjector(moduleList);
}
/**

View File

@@ -202,7 +202,7 @@ public class ScmServletModule extends JerseyServletModule
PATTERN_STYLESHEET, "*.json", "*.xml", "*.txt" };
/** Field description */
private static Logger logger =
private static final Logger logger =
LoggerFactory.getLogger(ScmServletModule.class);
//~--- constructors ---------------------------------------------------------

View File

@@ -0,0 +1,190 @@
/**
* 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.repository;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.eventbus.Subscribe;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.EagerSingleton;
import sonia.scm.plugin.Extension;
import sonia.scm.web.security.AdministrationContext;
import sonia.scm.web.security.PrivilegedAction;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
/**
*
* @author Sebastian Sdorra
* @since 1.37
*/
@Extension
@EagerSingleton
public final class LastModifiedUpdateListener
{
/**
* the logger for LastModifiedUpdateListener
*/
private static final Logger logger =
LoggerFactory.getLogger(LastModifiedUpdateListener.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param adminContext
* @param repositoryManager
*/
@Inject
public LastModifiedUpdateListener(AdministrationContext adminContext,
RepositoryManager repositoryManager)
{
this.adminContext = adminContext;
this.repositoryManager = repositoryManager;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param event
*/
@Subscribe
public void onPostReceive(PostReceiveRepositoryHookEvent event)
{
final Repository repository = event.getRepository();
if (repository != null)
{
//J-
adminContext.runAsAdmin(
new LastModifiedPrivilegedAction(repositoryManager, repository)
);
//J+
}
else
{
logger.warn("recevied hook without repository");
}
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 14/04/20
* @author Enter your name here...
*/
static class LastModifiedPrivilegedAction implements PrivilegedAction
{
/**
* Constructs ...
*
*
* @param repositoryManager
* @param repository
*/
public LastModifiedPrivilegedAction(RepositoryManager repositoryManager,
Repository repository)
{
this.repositoryManager = repositoryManager;
this.repository = repository;
}
//~--- methods ------------------------------------------------------------
/**
* Method description
*
*/
@Override
public void run()
{
Repository dbr = repositoryManager.get(repository.getId());
if (dbr != null)
{
logger.info("update last modified date of repository {}", dbr.getId());
dbr.setLastModified(System.currentTimeMillis());
try
{
repositoryManager.modify(dbr);
}
catch (RepositoryException ex)
{
logger.error("could not modify repository", ex);
}
catch (IOException ex)
{
logger.error("could not modify repository", ex);
}
}
else
{
logger.error("could not find repository with id {}",
repository.getId());
}
}
//~--- fields -------------------------------------------------------------
/** Field description */
private final Repository repository;
/** Field description */
private final RepositoryManager repositoryManager;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final AdministrationContext adminContext;
/** Field description */
private final RepositoryManager repositoryManager;
}

View File

@@ -86,8 +86,6 @@ public class DefaultAdministrationContext implements AdministrationContext
*
*
* @param injector
* @param userSessionProvider
* @param contextHolder
* @param securityManager
*/
@Inject
@@ -178,6 +176,22 @@ public class DefaultAdministrationContext implements AdministrationContext
return collection;
}
/**
* Method description
*
*
* @return
*/
private Subject createAdminSubject()
{
//J-
return new Subject.Builder(securityManager)
.authenticated(true)
.principals(principalCollection)
.buildSubject();
//J+
}
/**
* Method description
*
@@ -195,12 +209,7 @@ public class DefaultAdministrationContext implements AdministrationContext
{
SecurityUtils.setSecurityManager(securityManager);
//J-
Subject subject = new Subject.Builder(securityManager)
.authenticated(true)
.principals(principalCollection)
.buildSubject();
//J+
Subject subject = createAdminSubject();
ThreadState state = new SubjectThreadState(subject);
state.bind();
@@ -240,7 +249,7 @@ public class DefaultAdministrationContext implements AdministrationContext
if (logger.isInfoEnabled())
{
String username = null;
String username;
if (subject.hasRole(Role.USER))
{
@@ -255,7 +264,12 @@ public class DefaultAdministrationContext implements AdministrationContext
action.getClass().getName());
}
subject.runAs(principalCollection);
Subject adminSubject = createAdminSubject();
// do not use runas, because we want only execute this action in this
// thread as administrator. Runas could affect other threads
ThreadContext.bind(adminSubject);
try
{
@@ -263,32 +277,20 @@ public class DefaultAdministrationContext implements AdministrationContext
}
finally
{
PrincipalCollection collection = subject.releaseRunAs();
if (logger.isDebugEnabled())
{
logger.debug("release runas for user {}/{}",
principal, collection.getPrimaryPrincipal());
}
if (!subject.getPrincipal().equals(principal))
{
logger.error("release runas failed, {} is not equal with {}, logout.",
subject.getPrincipal(), principal);
subject.logout();
}
logger.debug("release administration context for user {}/{}", principal,
subject.getPrincipal());
ThreadContext.bind(subject);
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private Injector injector;
private final Injector injector;
/** Field description */
private final org.apache.shiro.mgt.SecurityManager securityManager;
/** Field description */
private PrincipalCollection principalCollection;
/** Field description */
private org.apache.shiro.mgt.SecurityManager securityManager;
}

View File

@@ -46,7 +46,7 @@
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>

View File

@@ -61,14 +61,14 @@
<append>true</append>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>

View File

@@ -163,6 +163,7 @@ if ( Sonia.repository.Grid ){
colContactText: 'Kontakt',
colDescriptionText: 'Beschreibung',
colCreationDateText: 'Erstellungsdatum',
colLastModifiedText: 'Zuletzt geändert',
colUrlText: 'Url',
colArchiveText: 'Archiv',
emptyText: 'Es wurde kein Repository konfiguriert',

View File

@@ -38,6 +38,7 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
colContactText: 'Contact',
colDescriptionText: 'Description',
colCreationDateText: 'Creation date',
colLastModifiedText: 'Last modified',
colUrlText: 'Url',
colArchiveText: 'Archive',
emptyText: 'No repository is configured',
@@ -90,6 +91,8 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
name: 'description'
},{
name: 'creationDate'
},{
name: 'lastModified'
},{
name: 'public'
},{
@@ -159,6 +162,12 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
header: this.colCreationDateText,
dataIndex: 'creationDate',
renderer: Ext.util.Format.formatTimestamp
},{
id: 'lastModified',
header: this.colLastModifiedText,
dataIndex: 'lastModified',
renderer: Ext.util.Format.formatTimestamp,
hidden: true
},{
id: 'Url',
header: this.colUrlText,

View File

@@ -575,8 +575,19 @@ Sonia.scm.Main = Ext.extend(Ext.util.Observable, {
Ext.onReady(function(){
function isLocalStorageAvailable(){
var mod = '__scm-manager';
try {
localStorage.setItem(mod, mod);
localStorage.removeItem(mod);
return true;
} catch(e) {
return false;
}
}
var stateProvider;
if ( typeof(Storage) !== "undefined" ){
if (isLocalStorageAvailable()){
if (debug){
console.debug('use localStore to save application state');
}