]> source.dussan.org Git - gitblit.git/commitdiff
Implement user "disabled" flag as an alternative to deleting the account 21/21/1
authorJames Moger <james.moger@gitblit.com>
Tue, 4 Mar 2014 22:29:02 +0000 (17:29 -0500)
committerJames Moger <james.moger@gitblit.com>
Tue, 4 Mar 2014 22:29:02 +0000 (17:29 -0500)
13 files changed:
releases.moxie
src/main/java/com/gitblit/ConfigUserService.java
src/main/java/com/gitblit/client/EditUserDialog.java
src/main/java/com/gitblit/manager/AuthenticationManager.java
src/main/java/com/gitblit/models/UserModel.java
src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
src/main/java/com/gitblit/wicket/pages/EditUserPage.html
src/main/java/com/gitblit/wicket/pages/EditUserPage.java
src/main/java/com/gitblit/wicket/pages/SessionPage.java
src/main/java/com/gitblit/wicket/panels/UsersPanel.java
src/main/resources/gitblit.css
src/test/java/com/gitblit/tests/AuthenticationManagerTest.java [new file with mode: 0644]
src/test/java/com/gitblit/tests/GitBlitSuite.java

index 0f4a75ecfc1d0abddd3a2cc409b6cbd2d99e7dd0..28a62350b7c69148fc6750b6acf3a23e153db677 100644 (file)
@@ -78,6 +78,7 @@ r20: {
        - Added FishEye hook script (pr-137)
        - Added Redmine Fetch hook script (issue-359)
        - Added Subgit hook contributed by TMate Software
+       - Added function to retain a user account but prohibit authentication. This is an alternative to deleting a user account.
     dependencyChanges:
        - updated to Jetty 8.1.13
        - updated to JGit 3.2.0
index a2a3277e9bf38d8f7d16992ae049a2ce54c65158..9b4dd7f11ba16bc054c5c24e42978217426a758b 100644 (file)
@@ -98,6 +98,8 @@ public class ConfigUserService implements IUserService {
 \r
        private static final String ACCOUNTTYPE = "accountType";\r
 \r
+       private static final String DISABLED = "disabled";\r
+\r
        private final File realmFile;\r
 \r
        private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);\r
@@ -701,6 +703,9 @@ public class ConfigUserService implements IUserService {
                        if (!StringUtils.isEmpty(model.countryCode)) {\r
                                config.setString(USER, model.username, COUNTRYCODE, model.countryCode);\r
                        }\r
+                       if (model.disabled) {\r
+                               config.setBoolean(USER, model.username, DISABLED, true);\r
+                       }\r
                        if (model.getPreferences() != null) {\r
                                if (!StringUtils.isEmpty(model.getPreferences().locale)) {\r
                                        config.setString(USER, model.username, LOCALE, model.getPreferences().locale);\r
@@ -868,6 +873,7 @@ public class ConfigUserService implements IUserService {
                                        if (Constants.EXTERNAL_ACCOUNT.equals(user.password) && user.accountType.isLocal()) {\r
                                                user.accountType = AccountType.EXTERNAL;\r
                                        }\r
+                                       user.disabled = config.getBoolean(USER, username, DISABLED, false);\r
                                        user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT);\r
                                        user.organization = config.getString(USER, username, ORGANIZATION);\r
                                        user.locality = config.getString(USER, username, LOCALITY);\r
index ab3ea67e30f27b272e4231d81f0eecadb16ad2cc..676916b2330f94a0e96503c4c4e7f4990a5e0758 100644 (file)
@@ -92,6 +92,8 @@ public class EditUserDialog extends JDialog {
 \r
        private JCheckBox notFederatedCheckbox;\r
 \r
+       private JCheckBox disabledCheckbox;\r
+\r
        private JTextField organizationalUnitField;\r
 \r
        private JTextField organizationField;\r
@@ -153,6 +155,7 @@ public class EditUserDialog extends JDialog {
                notFederatedCheckbox = new JCheckBox(\r
                                Translation.get("gb.excludeFromFederationDescription"),\r
                                anUser.excludeFromFederation);\r
+               disabledCheckbox = new JCheckBox(Translation.get("gb.disableUserDescription"), anUser.disabled);\r
 \r
                organizationalUnitField = new JTextField(anUser.organizationalUnit == null ? "" : anUser.organizationalUnit, 25);\r
                organizationField = new JTextField(anUser.organization == null ? "" : anUser.organization, 25);\r
@@ -176,6 +179,7 @@ public class EditUserDialog extends JDialog {
                fieldsPanel.add(newFieldPanel(Translation.get("gb.canCreate"), canCreateCheckbox));\r
                fieldsPanel.add(newFieldPanel(Translation.get("gb.excludeFromFederation"),\r
                                notFederatedCheckbox));\r
+               fieldsPanel.add(newFieldPanel(Translation.get("gb.disableUser"), disabledCheckbox));\r
 \r
                JPanel attributesPanel = new JPanel(new GridLayout(0, 1, 5, 2));\r
                attributesPanel.add(newFieldPanel(Translation.get("gb.organizationalUnit") + " (OU)", organizationalUnitField));\r
@@ -355,6 +359,7 @@ public class EditUserDialog extends JDialog {
                user.canFork = canForkCheckbox.isSelected();\r
                user.canCreate = canCreateCheckbox.isSelected();\r
                user.excludeFromFederation = notFederatedCheckbox.isSelected();\r
+               user.disabled = disabledCheckbox.isSelected();\r
 \r
                user.organizationalUnit = organizationalUnitField.getText().trim();\r
                user.organization = organizationField.getText().trim();\r
index 4897514299bb4913fadf58976c11d412fb39345b..ad4a9851adaccde3ffe4ce8f8fdca155951ffd40 100644 (file)
@@ -198,7 +198,7 @@ public class AuthenticationManager implements IAuthenticationManager {
                                                flagWicketSession(AuthenticationType.CONTAINER);
                                                logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
                                                                user.username, httpRequest.getRemoteAddr()));
-                                               return user;
+                                               return validateAuthentication(user, AuthenticationType.CONTAINER);
                                        } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false)
                                                        && !internalAccount) {
                                                // auto-create user from an authenticated container principal
@@ -210,7 +210,7 @@ public class AuthenticationManager implements IAuthenticationManager {
                                                flagWicketSession(AuthenticationType.CONTAINER);
                                                logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}",
                                                                user.username, httpRequest.getRemoteAddr()));
-                                               return user;
+                                               return validateAuthentication(user, AuthenticationType.CONTAINER);
                                        } else if (!internalAccount) {
                                                logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}",
                                                                principal.getName(), httpRequest.getRemoteAddr()));
@@ -231,7 +231,7 @@ public class AuthenticationManager implements IAuthenticationManager {
                                flagWicketSession(AuthenticationType.CERTIFICATE);
                                logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
                                                user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
-                               return user;
+                               return validateAuthentication(user, AuthenticationType.CERTIFICATE);
                        } else {
                                logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}",
                                                model.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
@@ -253,7 +253,7 @@ public class AuthenticationManager implements IAuthenticationManager {
                                flagWicketSession(AuthenticationType.COOKIE);
                                logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}",
                                        user.username, httpRequest.getRemoteAddr()));
-                               return user;
+                               return validateAuthentication(user, AuthenticationType.COOKIE);
                        }
                }
 
@@ -275,7 +275,7 @@ public class AuthenticationManager implements IAuthenticationManager {
                                        flagWicketSession(AuthenticationType.CREDENTIALS);
                                        logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
                                                        user.username, httpRequest.getRemoteAddr()));
-                                       return user;
+                                       return validateAuthentication(user, AuthenticationType.CREDENTIALS);
                                } else {
                                        logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}",
                                                        username, httpRequest.getRemoteAddr()));
@@ -285,6 +285,27 @@ public class AuthenticationManager implements IAuthenticationManager {
                return null;
        }
 
+       /**
+        * This method allows the authentication manager to reject authentication
+        * attempts.  It is called after the username/secret have been verified to
+        * ensure that the authentication technique has been logged.
+        *
+        * @param user
+        * @return
+        */
+       protected UserModel validateAuthentication(UserModel user, AuthenticationType type) {
+               if (user == null) {
+                       return null;
+               }
+               if (user.disabled) {
+                       // user has been disabled
+                       logger.warn("Rejected {} authentication attempt by disabled account \"{}\"",
+                                       type, user.username);
+                       return null;
+               }
+               return user;
+       }
+
        protected void flagWicketSession(AuthenticationType authenticationType) {
                RequestCycle requestCycle = RequestCycle.get();
                if (requestCycle != null) {
@@ -338,7 +359,7 @@ public class AuthenticationManager implements IAuthenticationManager {
                                // plain-text password
                                returnedUser = user;
                        }
-                       return returnedUser;
+                       return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS);
                }
 
                // try registered external authentication providers
@@ -349,12 +370,12 @@ public class AuthenticationManager implements IAuthenticationManager {
                                        if (user != null) {
                                                // user authenticated
                                                user.accountType = provider.getAccountType();
-                                               return user;
+                                               return validateAuthentication(user, AuthenticationType.CREDENTIALS);
                                        }
                                }
                        }
                }
-               return user;
+               return validateAuthentication(user, AuthenticationType.CREDENTIALS);
        }
 
        /**
index 63208f359a4b140fc77a94fc8f4dde2b416b0306..fbf311a5bb086fdbd7cf907e32d2b9de76e4f5d8 100644 (file)
@@ -67,6 +67,7 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
        public boolean canFork;\r
        public boolean canCreate;\r
        public boolean excludeFromFederation;\r
+       public boolean disabled;\r
        // retained for backwards-compatibility with RPC clients\r
        @Deprecated\r
        public final Set<String> repositories = new HashSet<String>();\r
index 86dd585f978170d57d5e0952c7416c95be2e9fd3..49a57e9cfa07ba3c12e448a18ceed7aa712be424 100644 (file)
@@ -648,4 +648,6 @@ gb.looksGood = looks good
 gb.approve = approve
 gb.hasNotReviewed = has not reviewed
 gb.about = about
-gb.ticketN = ticket #{0}
\ No newline at end of file
+gb.ticketN = ticket #{0}
+gb.disableUser = disable user
+gb.disableUserDescription = prevent this account from authenticating
\ No newline at end of file
index e78e44eed2de012f438c3bfe987e648b28ce0f99..3bccdd590369f10c3662163597f646e547903fde 100644 (file)
                                <tr><th><wicket:message key="gb.confirmPassword"></wicket:message></th><td class="edit"><input type="password" wicket:id="confirmPassword" size="30" tabindex="3" /></td></tr>\r
                                <tr><th><wicket:message key="gb.displayName"></wicket:message></th><td class="edit"><input type="text" wicket:id="displayName" size="30" tabindex="4" /></td></tr>\r
                                <tr><th><wicket:message key="gb.emailAddress"></wicket:message></th><td class="edit"><input type="text" wicket:id="emailAddress" size="30" tabindex="5" /></td></tr>\r
-                               <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canAdminDescription"></wicket:message></span></label></td></tr>                            \r
-                               <tr><th><wicket:message key="gb.canCreate"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canCreate" tabindex="7" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canCreateDescription"></wicket:message></span></label></td></tr>                         \r
-                               <tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canForkDescription"></wicket:message></span></label></td></tr>                               \r
+                               <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canAdminDescription"></wicket:message></span></label></td></tr>\r
+                               <tr><th><wicket:message key="gb.canCreate"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canCreate" tabindex="7" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canCreateDescription"></wicket:message></span></label></td></tr>\r
+                               <tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="8" /> &nbsp;<span class="help-inline"><wicket:message key="gb.canForkDescription"></wicket:message></span></label></td></tr>\r
                                <tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="9" /> &nbsp;<span class="help-inline"><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></span></label></td></tr>                             \r
+                               <tr><th><wicket:message key="gb.disableUser"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="disabled" tabindex="10" /> &nbsp;<span class="help-inline"><wicket:message key="gb.disableUserDescription"></wicket:message></span></label></td></tr>\r
                        </tbody>\r
                </table>\r
                </div>\r
index 15c35fa6aee9441d546a873c866d37fd7cb490c1..b9a848052ba2657f25a4424bb63f5aa1d7350468 100644 (file)
@@ -243,6 +243,8 @@ public class EditUserPage extends RootSubPage {
                form.add(new CheckBox("canFork").setEnabled(app().settings().getBoolean(Keys.web.allowForking, true)));\r
                form.add(new CheckBox("canCreate"));\r
                form.add(new CheckBox("excludeFromFederation"));\r
+               form.add(new CheckBox("disabled"));\r
+\r
                form.add(new RegistrantPermissionsPanel("repositories", RegistrantType.REPOSITORY, repos, permissions, getAccessPermissions()));\r
                form.add(teams.setEnabled(editTeams));\r
 \r
index 8065c5aa72af5c7e420116b4480daab106704bf5..909342aeef84367074f884d24145416732f78b9e 100644 (file)
@@ -56,6 +56,16 @@ public abstract class SessionPage extends WebPage {
                        // any changes to permissions or roles (issue-186)\r
                        UserModel user = app().users().getUserModel(session.getUser().username);\r
 \r
+                       if (user.disabled) {\r
+                               // user was disabled during session\r
+                               HttpServletResponse response = ((WebResponse) getRequestCycle().getResponse())\r
+                                               .getHttpServletResponse();\r
+                               app().authentication().logout(response, user);\r
+                               session.setUser(null);\r
+                               session.invalidateNow();\r
+                               return;\r
+                       }\r
+\r
                        // validate cookie during session (issue-361)\r
                        if (user != null && app().settings().getBoolean(Keys.web.allowCookieAuthentication, true)) {\r
                                HttpServletRequest request = ((WebRequest) getRequestCycle().getRequest())\r
index ed990c8937a150c8af1582f746346fe58d7c8188..5d6265525430cf1de642ac927302f1444ace5ce9 100644 (file)
@@ -57,7 +57,8 @@ public class UsersPanel extends BasePanel {
                        @Override\r
                        public void populateItem(final Item<UserModel> item) {\r
                                final UserModel entry = item.getModelObject();\r
-                               LinkPanel editLink = new LinkPanel("username", "list", entry.username,\r
+                               String css = "list" + (entry.disabled ? "-strikethrough":"");\r
+                               LinkPanel editLink = new LinkPanel("username", css, entry.username,\r
                                                EditUserPage.class, WicketUtils.newUsernameParameter(entry.username));\r
                                WicketUtils.setHtmlTooltip(editLink, getString("gb.edit") + " " + entry.getDisplayName());\r
                                item.add(editLink);\r
@@ -65,7 +66,7 @@ public class UsersPanel extends BasePanel {
                                if (StringUtils.isEmpty(entry.displayName)) {\r
                                        item.add(new Label("displayName").setVisible(false));\r
                                } else {\r
-                                       editLink = new LinkPanel("displayName", "list", entry.getDisplayName(),\r
+                                       editLink = new LinkPanel("displayName", css, entry.getDisplayName(),\r
                                                EditUserPage.class, WicketUtils.newUsernameParameter(entry.username));\r
                                        WicketUtils.setHtmlTooltip(editLink, getString("gb.edit") + " " + entry.getDisplayName());\r
                                        item.add(editLink);\r
@@ -74,7 +75,7 @@ public class UsersPanel extends BasePanel {
                                if (StringUtils.isEmpty(entry.emailAddress)) {\r
                                        item.add(new Label("emailAddress").setVisible(false));\r
                                } else {\r
-                                       editLink = new LinkPanel("emailAddress", "list", entry.emailAddress,\r
+                                       editLink = new LinkPanel("emailAddress", css, entry.emailAddress,\r
                                                EditUserPage.class, WicketUtils.newUsernameParameter(entry.username));\r
                                        WicketUtils.setHtmlTooltip(editLink, getString("gb.edit") + " " + entry.getDisplayName());\r
                                        item.add(editLink);\r
index 789477096f9d87f0c20afcef20dc4509f06309aa..748a3198b41e79e1c73c8382ec5cbe02f9597583 100644 (file)
@@ -55,7 +55,7 @@ a.bugtraq {
        white-space: nowrap;
        vertical-align: baseline;
 }
-
+\r
 [class^="icon-"], [class*=" icon-"] i {\r
        /* override for a links that look like bootstrap buttons */\r
        vertical-align: text-bottom;\r
@@ -993,6 +993,11 @@ a.list {
        color: inherit;\r
 }\r
 \r
+a.list-strikethrough {\r
+       text-decoration: line-through;\r
+       color: inherit;\r
+}\r
+\r
 a.list.subject {\r
        font-weight: bold;\r
 }\r
diff --git a/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java b/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java
new file mode 100644 (file)
index 0000000..84a2b74
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.tests;
+
+import java.util.HashMap;
+
+import org.junit.Test;
+
+import com.gitblit.manager.AuthenticationManager;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.manager.UserManager;
+import com.gitblit.models.UserModel;
+import com.gitblit.tests.mock.MemorySettings;
+
+/**
+ * Class for testing local authentication.
+ *
+ * @author James Moger
+ *
+ */
+public class AuthenticationManagerTest extends GitblitUnitTest {
+
+       IUserManager users;
+
+    MemorySettings getSettings() {
+       return new MemorySettings(new HashMap<String, Object>());
+    }
+
+    IAuthenticationManager newAuthenticationManager() {
+       RuntimeManager runtime = new RuntimeManager(getSettings(), GitBlitSuite.BASEFOLDER).start();
+       users = new UserManager(runtime).start();
+       AuthenticationManager auth = new AuthenticationManager(runtime, users).start();
+       return auth;
+    }
+
+    @Test
+    public void testAuthenticate() throws Exception {
+       IAuthenticationManager auth = newAuthenticationManager();
+
+       UserModel user = new UserModel("sunnyjim");
+               user.password = "password";
+               users.updateUserModel(user);
+
+               assertNotNull(auth.authenticate(user.username, user.password.toCharArray()));
+               user.disabled = true;
+
+               users.updateUserModel(user);
+               assertNull(auth.authenticate(user.username, user.password.toCharArray()));
+               users.deleteUserModel(user);
+    }
+}
index cba575d768bf525067f0a0a50c4988421c51ea52..c015c847e1b7009f5a846c7c784b1e0d0b0496c4 100644 (file)
@@ -64,7 +64,7 @@ import com.gitblit.utils.JGitUtils;
                GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class,\r
                FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class,\r
                ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
-               BranchTicketServiceTest.class, RedisTicketServiceTest.class  })
+               BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class })
 public class GitBlitSuite {\r
 \r
        public static final File BASEFOLDER = new File("data");\r