/* * 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.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.util.HashMap; import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.gitblit.HtpasswdUserService; import com.gitblit.models.UserModel; import com.gitblit.tests.mock.MemorySettings; import com.gitblit.tests.mock.MockRuntimeManager; import com.gitblit.utils.StringUtils; /** * Test the Htpasswd user service. * */ public class HtpasswdUserServiceTest extends GitblitUnitTest { private static final String RESOURCE_DIR = "src/test/resources/htpasswdUSTest/"; private static final String KEY_SUPPORT_PLAINTEXT_PWD = "realm.htpasswd.supportPlaintextPasswords"; private static final int NUM_USERS_HTPASSWD = 10; private static final MemorySettings MS = new MemorySettings(new HashMap()); private HtpasswdUserService htpwdUserService; private MemorySettings getSettings( String userfile, String groupfile, Boolean overrideLA) { MS.put("realm.htpasswd.backingUserService", RESOURCE_DIR + "users.conf"); MS.put("realm.htpasswd.userfile", (userfile == null) ? (RESOURCE_DIR+"htpasswd") : userfile); MS.put("realm.htpasswd.groupfile", (groupfile == null) ? (RESOURCE_DIR+"htgroup") : groupfile); MS.put("realm.htpasswd.overrideLocalAuthentication", (overrideLA == null) ? "false" : overrideLA.toString()); // Default to keep test the same on all platforms. MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); return MS; } private MemorySettings getSettings() { return getSettings(null, null, null); } private MemorySettings getSettings(boolean overrideLA) { return getSettings(null, null, new Boolean(overrideLA)); } private void setupUS() { htpwdUserService = new HtpasswdUserService(); htpwdUserService.setup(new MockRuntimeManager(getSettings())); } private void setupUS(boolean overrideLA) { htpwdUserService = new HtpasswdUserService(); htpwdUserService.setup(new MockRuntimeManager(getSettings(overrideLA))); } private void copyInFiles() throws IOException { File dir = new File(RESOURCE_DIR); FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String file) { return file.endsWith(".in"); } }; for (File inf : dir.listFiles(filter)) { File dest = new File(inf.getParent(), inf.getName().substring(0, inf.getName().length()-3)); FileUtils.copyFile(inf, dest); } } private void deleteGeneratedFiles() { File dir = new File(RESOURCE_DIR); FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String file) { return !(file.endsWith(".in")); } }; for (File file : dir.listFiles(filter)) { file.delete(); } } @Before public void setup() throws IOException { copyInFiles(); setupUS(); } @After public void tearDown() { deleteGeneratedFiles(); } @Test public void testSetup() throws IOException { assertEquals(NUM_USERS_HTPASSWD, htpwdUserService.getNumberHtpasswdUsers()); } @Test public void testAuthenticate() { MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); UserModel user = htpwdUserService.authenticate("user1", "pass1".toCharArray()); assertNotNull(user); assertEquals("user1", user.username); user = htpwdUserService.authenticate("user2", "pass2".toCharArray()); assertNotNull(user); assertEquals("user2", user.username); // Test different encryptions user = htpwdUserService.authenticate("plain", "passWord".toCharArray()); assertNotNull(user); assertEquals("plain", user.username); MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); user = htpwdUserService.authenticate("crypt", "password".toCharArray()); assertNotNull(user); assertEquals("crypt", user.username); user = htpwdUserService.authenticate("md5", "password".toCharArray()); assertNotNull(user); assertEquals("md5", user.username); user = htpwdUserService.authenticate("sha", "password".toCharArray()); assertNotNull(user); assertEquals("sha", user.username); // Test leading and trailing whitespace user = htpwdUserService.authenticate("trailing", "whitespace".toCharArray()); assertNotNull(user); assertEquals("trailing", user.username); user = htpwdUserService.authenticate("tabbed", "frontAndBack".toCharArray()); assertNotNull(user); assertEquals("tabbed", user.username); user = htpwdUserService.authenticate("leading", "whitespace".toCharArray()); assertNotNull(user); assertEquals("leading", user.username); // Test local account user = htpwdUserService.authenticate("admin", "admin".toCharArray()); assertNotNull(user); assertEquals("admin", user.username); } @Test public void testAttributes() { MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); UserModel user = htpwdUserService.authenticate("user1", "pass1".toCharArray()); assertNotNull(user); assertEquals("El Capitan", user.displayName); assertEquals("cheffe@example.com", user.emailAddress); assertTrue(user.canAdmin); user = htpwdUserService.authenticate("user2", "pass2".toCharArray()); assertNotNull(user); assertEquals("User Two", user.displayName); assertTrue(user.canCreate); assertTrue(user.canFork); user = htpwdUserService.authenticate("admin", "admin".toCharArray()); assertNotNull(user); assertTrue(user.canAdmin); user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); assertNotNull(user); assertEquals("Local User", user.displayName); assertFalse(user.canCreate); assertFalse(user.canFork); assertFalse(user.canAdmin); } @Test public void testAuthenticateDenied() { UserModel user = null; MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); user = htpwdUserService.authenticate("user1", "".toCharArray()); assertNull("User 'user1' falsely authenticated.", user); user = htpwdUserService.authenticate("user1", "pass2".toCharArray()); assertNull("User 'user1' falsely authenticated.", user); user = htpwdUserService.authenticate("user2", "lalala".toCharArray()); assertNull("User 'user2' falsely authenticated.", user); user = htpwdUserService.authenticate("user3", "disabled".toCharArray()); assertNull("User 'user3' falsely authenticated.", user); user = htpwdUserService.authenticate("user4", "disabled".toCharArray()); assertNull("User 'user4' falsely authenticated.", user); user = htpwdUserService.authenticate("plain", "text".toCharArray()); assertNull("User 'plain' falsely authenticated.", user); user = htpwdUserService.authenticate("plain", "password".toCharArray()); assertNull("User 'plain' falsely authenticated.", user); MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); user = htpwdUserService.authenticate("crypt", "".toCharArray()); assertNull("User 'cyrpt' falsely authenticated.", user); user = htpwdUserService.authenticate("crypt", "passwd".toCharArray()); assertNull("User 'crypt' falsely authenticated.", user); user = htpwdUserService.authenticate("md5", "".toCharArray()); assertNull("User 'md5' falsely authenticated.", user); user = htpwdUserService.authenticate("md5", "pwd".toCharArray()); assertNull("User 'md5' falsely authenticated.", user); user = htpwdUserService.authenticate("sha", "".toCharArray()); assertNull("User 'sha' falsely authenticated.", user); user = htpwdUserService.authenticate("sha", "letmein".toCharArray()); assertNull("User 'sha' falsely authenticated.", user); user = htpwdUserService.authenticate(" tabbed", "frontAndBack".toCharArray()); assertNull("User 'tabbed' falsely authenticated.", user); user = htpwdUserService.authenticate(" leading", "whitespace".toCharArray()); assertNull("User 'leading' falsely authenticated.", user); } @Test public void testNewLocalAccount() { UserModel newUser = new UserModel("newlocal"); newUser.displayName = "Local User 2"; newUser.password = StringUtils.MD5_TYPE + StringUtils.getMD5("localPwd2"); assertTrue("Failed to add local account.", htpwdUserService.updateUserModel(newUser)); UserModel localAccount = htpwdUserService.authenticate(newUser.username, "localPwd2".toCharArray()); assertNotNull(localAccount); assertEquals(newUser, localAccount); localAccount = htpwdUserService.authenticate(newUser.username, "localPwd2".toCharArray()); assertNotNull(localAccount); assertEquals(newUser, localAccount); assertTrue("Failed to delete local account.", htpwdUserService.deleteUser(localAccount.username)); assertNull(htpwdUserService.authenticate(newUser.username, "localPwd2".toCharArray())); } @Test public void testCleartextIntrusion() { MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); assertNull(htpwdUserService.authenticate("md5", "$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0".toCharArray())); assertNull(htpwdUserService.authenticate("sha", "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=".toCharArray())); assertNull(htpwdUserService.authenticate("user1", "#externalAccount".toCharArray())); MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); assertNull(htpwdUserService.authenticate("md5", "$apr1$qAGGNfli$sAn14mn.WKId/3EQS7KSX0".toCharArray())); assertNull(htpwdUserService.authenticate("sha", "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=".toCharArray())); assertNull(htpwdUserService.authenticate("user1", "#externalAccount".toCharArray())); } @Test public void testCryptVsPlaintext() { MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false"); assertNull(htpwdUserService.authenticate("crypt", "6TmlbxqZ2kBIA".toCharArray())); assertNotNull(htpwdUserService.authenticate("crypt", "password".toCharArray())); MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true"); assertNotNull(htpwdUserService.authenticate("crypt", "6TmlbxqZ2kBIA".toCharArray())); assertNull(htpwdUserService.authenticate("crypt", "password".toCharArray())); } /* * Test case: User exists in user.conf with a local password and in htpasswd with an external password. * If overrideLocalAuthentication is false, the local account takes precedence and is never updated. */ @Test public void testPreparedAccountPreferLocal() throws IOException { setupUS(false); UserModel user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); assertNotNull(user); assertEquals("staylocal", user.getName()); deleteGeneratedFiles(); copyInFiles(); setupUS(false); user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); assertNotNull(user); assertEquals("staylocal", user.getName()); } /* * Test case: User exists in user.conf with a local password and in htpasswd with an external password. * If overrideLocalAuthentication is true, the external account takes precedence, * the initial local password is never used and discarded. */ @Test public void testPreparedAccountPreferExternal() throws IOException { setupUS(true); UserModel user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); assertNotNull(user); assertEquals("staylocal", user.getName()); deleteGeneratedFiles(); copyInFiles(); setupUS(true); user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); assertNotNull(user); assertEquals("staylocal", user.getName()); // Make sure no authentication by using the string constant for external accounts is possible. user = htpwdUserService.authenticate("leaderred", "#externalAccount".toCharArray()); assertNull(user); } /* * Test case: User exists in user.conf with a local password and in htpasswd with an external password. * If overrideLocalAuthentication is true, the external account takes precedence, * the initial local password is never used and discarded. */ @Test public void testPreparedAccountChangeSetting() throws IOException { getSettings(false); UserModel user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); assertNotNull(user); assertEquals("staylocal", user.getName()); getSettings(true); user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); assertNotNull(user); assertEquals("staylocal", user.getName()); // Make sure no authentication by using the string constant for external accounts is possible. user = htpwdUserService.authenticate("leaderred", "#externalAccount".toCharArray()); assertNull(user); getSettings(false); // The preference is now back to local accounts but since the prepared account got switched // to an external account, it will stay this way. user = htpwdUserService.authenticate("leaderred", "localPassword".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("leaderred", "externalPassword".toCharArray()); assertNotNull(user); assertEquals("leaderred", user.getName()); user = htpwdUserService.authenticate("staylocal", "localUser".toCharArray()); assertNotNull(user); assertEquals("staylocal", user.getName()); // Make sure no authentication by using the string constant for external accounts is possible. user = htpwdUserService.authenticate("leaderred", "#externalAccount".toCharArray()); assertNull(user); } @Test public void testChangeHtpasswdFile() { UserModel user; // User default set up. user = htpwdUserService.authenticate("md5", "password".toCharArray()); assertNotNull(user); assertEquals("md5", user.username); user = htpwdUserService.authenticate("sha", "password".toCharArray()); assertNotNull(user); assertEquals("sha", user.username); user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray()); assertNull(user); // Switch to different htpasswd file. getSettings(RESOURCE_DIR + "htpasswd-user", null, null); user = htpwdUserService.authenticate("md5", "password".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("sha", "password".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray()); assertNotNull(user); assertEquals("blueone", user.username); user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray()); assertNotNull(user); assertEquals("bluetwo", user.username); } @Test public void testChangeHtpasswdFileNotExisting() { UserModel user; // User default set up. user = htpwdUserService.authenticate("md5", "password".toCharArray()); assertNotNull(user); assertEquals("md5", user.username); user = htpwdUserService.authenticate("sha", "password".toCharArray()); assertNotNull(user); assertEquals("sha", user.username); user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray()); assertNull(user); // Switch to different htpasswd file that doesn't exist. // Currently we stop working with old users upon this change. getSettings(RESOURCE_DIR + "no-such-file", null, null); user = htpwdUserService.authenticate("md5", "password".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("sha", "password".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("blueone", "GoBlue!".toCharArray()); assertNull(user); user = htpwdUserService.authenticate("bluetwo", "YayBlue!".toCharArray()); assertNull(user); } }