diff --git a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java index d65589db82..580c666ed0 100644 --- a/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java +++ b/scm-core/src/main/java/sonia/scm/config/ScmConfiguration.java @@ -233,17 +233,6 @@ public class ScmConfiguration return baseUrl; } - /** - * Returns the realm description. - * - * - * @return realm description - */ - public String getRealmDescription() - { - return realmDescription; - } - /** * Returns the date format for the user interface. This format is a * JavaScript date format, from the library moment.js. @@ -375,6 +364,17 @@ public class ScmConfiguration return proxyUser; } + /** + * Returns the realm description. + * + * + * @return realm description + */ + public String getRealmDescription() + { + return realmDescription; + } + /** * Returns the servername of the SCM-Manager host. * @@ -482,6 +482,19 @@ public class ScmConfiguration return forceBaseUrl; } + /** + * Method description + * + * + * @return + * + * @since 1.36 + */ + public boolean isSkipFailedAuthenticators() + { + return skipFailedAuthenticators; + } + //~--- set methods ---------------------------------------------------------- /** @@ -529,17 +542,6 @@ public class ScmConfiguration this.baseUrl = baseUrl; } - /** - * Sets the realm description. - * - * - * @param realmDescription - */ - public void setRealmDescription(String realmDescription) - { - this.realmDescription = realmDescription; - } - /** * Sets the date format for the ui. * @@ -733,6 +735,17 @@ public class ScmConfiguration this.proxyUser = proxyUser; } + /** + * Sets the realm description. + * + * + * @param realmDescription + */ + public void setRealmDescription(String realmDescription) + { + this.realmDescription = realmDescription; + } + /** * Method description * @@ -745,6 +758,19 @@ public class ScmConfiguration this.servername = servername; } + /** + * Method description + * + * + * @param skipFailedAuthenticators + * + * @since 1.36 + */ + public void setSkipFailedAuthenticators(boolean skipFailedAuthenticators) + { + this.skipFailedAuthenticators = skipFailedAuthenticators; + } + /** * Method description * @@ -790,21 +816,6 @@ public class ScmConfiguration @XmlElement(name = "login-attempt-limit") private int loginAttemptLimit = -1; - /** - * Login attempt timeout. - * - * @since 1.34 - */ - @XmlElement(name = "login-attempt-limit-timeout") - private long loginAttemptLimitTimeout = TimeUnit.MINUTES.toSeconds(5l); - - /** Field description */ - private boolean enableProxy = false; - - /** Field description */ - @XmlElement(name = "plugin-url") - private String pluginUrl = DEFAULT_PLUGINURL; - /** glob patterns for urls which are excluded from proxy */ @XmlElement(name = "proxy-excludes") @XmlJavaTypeAdapter(XmlSetStringAdapter.class) @@ -825,10 +836,33 @@ public class ScmConfiguration /** @deprecated use {@link #baseUrl} */ private String servername = "localhost"; + /** + * Skip failed authenticators. + * + * @since 1.36 + */ + @XmlElement(name = "skip-failed-authenticators") + private boolean skipFailedAuthenticators = false; + + /** Field description */ + @XmlElement(name = "plugin-url") + private String pluginUrl = DEFAULT_PLUGINURL; + + /** + * Login attempt timeout. + * + * @since 1.34 + */ + @XmlElement(name = "login-attempt-limit-timeout") + private long loginAttemptLimitTimeout = TimeUnit.MINUTES.toSeconds(5l); + /** @deprecated use {@link #baseUrl} and {@link #forceBaseUrl} */ @Deprecated private boolean enableSSL = false; + /** Field description */ + private boolean enableProxy = false; + /** @deprecated use {@link #baseUrl} */ @Deprecated private boolean enablePortForward = false; @@ -837,6 +871,13 @@ public class ScmConfiguration @Deprecated private int sslPort = 8181; + /** + * + * Authentication realm for basic authentication. + * + */ + private String realmDescription = HttpUtil.AUTHENTICATION_REALM; + /** Configuration change listeners */ @XmlTransient private Set listeners = @@ -848,13 +889,6 @@ public class ScmConfiguration /** Field description */ private boolean disableGroupingGrid = false; - /** - * - * Authentication realm for basic authentication. - * - */ - private String realmDescription = HttpUtil.AUTHENTICATION_REALM; - /** * JavaScript date format from moment.js * @see http://momentjs.com/docs/#/parsing/ diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java b/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java index e7ef7c43af..f768a4d044 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java @@ -47,6 +47,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.SCMContextProvider; import sonia.scm.cache.Cache; import sonia.scm.cache.CacheManager; +import sonia.scm.config.ScmConfiguration; import sonia.scm.security.EncryptionHandler; import sonia.scm.user.User; import sonia.scm.user.UserManager; @@ -87,21 +88,24 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager * * * + * + * @param configuration * @param userManager * @param authenticationHandlerSet * @param encryptionHandler * @param cacheManager - * @param authenticationListenerProvider * @param authenticationListeners */ @Inject - public ChainAuthenticatonManager(UserManager userManager, + public ChainAuthenticatonManager(ScmConfiguration configuration, + UserManager userManager, Set authenticationHandlerSet, EncryptionHandler encryptionHandler, CacheManager cacheManager, Set authenticationListeners) { AssertUtil.assertIsNotEmpty(authenticationHandlerSet); AssertUtil.assertIsNotNull(cacheManager); + this.configuration = configuration; this.authenticationHandlers = sort(userManager, authenticationHandlerSet); this.encryptionHandler = encryptionHandler; this.cache = cacheManager.getCache(String.class, @@ -200,6 +204,22 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager } } + /** + * Method description + * + * + * @param result + * + * @return + */ + boolean stopChain(AuthenticationResult result) + { + return (result != null) && (result.getState() != null) + && (result.getState().isSuccessfully() + || ((result.getState() == AuthenticationState.FAILED) + &&!configuration.isSkipFailedAuthenticators())); + } + /** * Method description * @@ -240,9 +260,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager authenticator.getClass().getName(), result); } - if ((result != null) && (result.getState() != null) - && (result.getState().isSuccessfully() - || (result.getState() == AuthenticationState.FAILED))) + if (stopChain(result)) { if (result.getState().isSuccessfully() && (result.getUser() != null)) { @@ -378,11 +396,14 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager //~--- fields --------------------------------------------------------------- /** Field description */ - private List authenticationHandlers; + private final List authenticationHandlers; /** Field description */ - private Cache cache; + private final Cache cache; /** Field description */ - private EncryptionHandler encryptionHandler; + private final ScmConfiguration configuration; + + /** Field description */ + private final EncryptionHandler encryptionHandler; } diff --git a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js index 9f80b5eed8..29efa3fa00 100644 --- a/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js +++ b/scm-webapp/src/main/webapp/resources/js/config/sonia.config.scmconfigpanel.js @@ -51,6 +51,7 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ errorSubmitMsgText: 'Could not submit config.', // TODO i18n + skipFailedAuthenticatorsText: 'Skip failed authenticators', loginAttemptLimitText: 'Login Attempt Limit', loginAttemptLimitTimeoutText: 'Login Attempt Limit Timeout', @@ -85,6 +86,8 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ adminUsersHelpText: 'Comma seperated list of users with admin permissions.', // TODO i18n + skipFailedAuthenticatorsHelpText: 'Do not stop the authentication chain, \n\ + if an authenticator finds the user but fails to authenticate the user.', loginAttemptLimitHelpText: 'Maximum allowed login attempts. Use -1 to disable the login attempt limit.', loginAttemptLimitTimeoutHelpText: 'Timeout in seconds for users which are temporary disabled,\ because of too many failed login attempts.', @@ -157,6 +160,12 @@ Sonia.config.ScmConfigPanel = Ext.extend(Sonia.config.ConfigPanel,{ name: 'anonymousAccessEnabled', inputValue: 'true', helpText: this.allowAnonymousAccessHelpText + },{ + xtype: 'checkbox', + fieldLabel: this.skipFailedAuthenticatorsText, + name: 'skip-failed-authenticators', + inputValue: 'true', + helpText: this.skipFailedAuthenticatorsHelpText },{ xtype: 'numberfield', fieldLabel: this.loginAttemptLimitText, diff --git a/scm-webapp/src/main/webapp/resources/js/i18n/de.js b/scm-webapp/src/main/webapp/resources/js/i18n/de.js index 40b6b6dca6..6c8e00f8e4 100644 --- a/scm-webapp/src/main/webapp/resources/js/i18n/de.js +++ b/scm-webapp/src/main/webapp/resources/js/i18n/de.js @@ -40,7 +40,7 @@ if (Ext.form.VTypes){ passwordText: 'Die Passwörter stimmen nicht überein!', nameTest: 'Der Name ist invalid.', usernameText: 'Der Benutzername ist invalid.', - repositoryNameText: 'Der Name des Repositorys ist ungültig.', + repositoryNameText: 'Der Name des Repositorys ist ungültig.' }); } @@ -349,6 +349,10 @@ if (Sonia.config.ScmConfigPanel){ adminGroupsHelpText: 'Komma getrennte Liste von Gruppen mit Administrationsrechten.', adminUsersHelpText: 'Komma getrennte Liste von Benutzern mit Administrationsrechten.', + skipFailedAuthenticatorsText: 'Überspringe fehlgeschlagene Authentifizierer', + skipFailedAuthenticatorsHelpText: 'Setzt die Authentifizierungs-Kette fort,\n\ + auch wenn ein ein Authentifizierer einen Benutzer gefunden hat,\n\ + diesen aber nicht Authentifizieren kann.', loginAttemptLimitText: 'Login Attempt Limit', loginAttemptLimitTimeoutText: 'Login Attempt Limit Timeout', loginAttemptLimitHelpText: 'Maximale Anzahl gescheiterte Loginversuche. Der Wert -1 deaktiviert die Begrenzung.', diff --git a/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java b/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java index d35068db82..ab4e68e256 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java @@ -42,6 +42,7 @@ import org.junit.Test; import sonia.scm.AbstractTestBase; import sonia.scm.SCMContextProvider; import sonia.scm.cache.MapCacheManager; +import sonia.scm.config.ScmConfiguration; import sonia.scm.security.MessageDigestEncryptionHandler; import sonia.scm.user.User; import sonia.scm.user.UserManager; @@ -137,7 +138,7 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase SingleUserAuthenticaionHandler a2 = new SingleUserAuthenticaionHandler("a2", trillian); - manager = createManager("a2", a1, a2); + manager = createManager("a2", false, a1, a2); AuthenticationResult result = manager.authenticate(request, response, trillian.getName(), "trillian123"); @@ -147,6 +148,24 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase assertEquals("a2", result.getUser().getType()); } + /** + * Method description + * + */ + @Test + public void testStopChain() + { + ChainAuthenticatonManager cam = createManager("", false); + + assertTrue(cam.stopChain(new AuthenticationResult(perfect))); + assertTrue(cam.stopChain(AuthenticationResult.FAILED)); + assertFalse(cam.stopChain(AuthenticationResult.NOT_FOUND)); + cam = createManager("", true); + assertTrue(cam.stopChain(new AuthenticationResult(perfect))); + assertFalse(cam.stopChain(AuthenticationResult.FAILED)); + assertFalse(cam.stopChain(AuthenticationResult.NOT_FOUND)); + } + /** * Method description * @@ -199,7 +218,7 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase trillian = UserTestData.createTrillian(); trillian.setPassword("trillian123"); - return createManager("", + return createManager("", false, new SingleUserAuthenticaionHandler("perfectsType", perfect), new SingleUserAuthenticaionHandler("trilliansType", trillian)); } @@ -209,20 +228,33 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase * * * @param defaultType + * @param skipFailedAuthenticators * @param handlers * * @return */ private ChainAuthenticatonManager createManager(String defaultType, - AuthenticationHandler... handlers) + boolean skipFailedAuthenticators, AuthenticationHandler... handlers) { + if ( handlers == null || handlers.length == 0 ){ + //J- + handlers = new AuthenticationHandler[]{ + new SingleUserAuthenticaionHandler("perfectsType", perfect), + new SingleUserAuthenticaionHandler("trilliansType", trillian) + }; + //J+ + } + ScmConfiguration configuration = new ScmConfiguration(); + + configuration.setSkipFailedAuthenticators(skipFailedAuthenticators); + Set handlerSet = ImmutableSet.copyOf(handlers); UserManager userManager = mock(UserManager.class); when(userManager.getDefaultType()).thenReturn(defaultType); - manager = new ChainAuthenticatonManager(userManager, handlerSet, - new MessageDigestEncryptionHandler(), new MapCacheManager(), + manager = new ChainAuthenticatonManager(configuration, userManager, + handlerSet, new MessageDigestEncryptionHandler(), new MapCacheManager(), Collections.EMPTY_SET); manager.init(contextProvider); @@ -328,10 +360,10 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase //~--- fields ------------------------------------------------------------- /** Field description */ - private String type; + private final String type; /** Field description */ - private User user; + private final User user; }