From 7847af6e63e7adef6ec8d99a1809e91472d2bc2d Mon Sep 17 00:00:00 2001 From: James Moger <james.moger@gitblit.com> Date: Thu, 05 Sep 2013 09:14:22 -0400 Subject: [PATCH] Restore blockpush and localclone hooks to binaries (issue-303) --- src/main/java/com/gitblit/GitBlit.java | 351 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 files changed, 259 insertions(+), 92 deletions(-) diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java index ceb40c1..111e595 100644 --- a/src/main/java/com/gitblit/GitBlit.java +++ b/src/main/java/com/gitblit/GitBlit.java @@ -84,6 +84,7 @@ import com.gitblit.Constants.AccessPermission; import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.Constants.AccountType; import com.gitblit.Constants.AuthenticationType; import com.gitblit.Constants.AuthorizationControl; import com.gitblit.Constants.FederationRequest; @@ -102,6 +103,7 @@ import com.gitblit.models.GitClientApplication; import com.gitblit.models.Metric; import com.gitblit.models.ProjectModel; +import com.gitblit.models.RefModel; import com.gitblit.models.RegistrantAccessPermission; import com.gitblit.models.RepositoryModel; import com.gitblit.models.RepositoryUrl; @@ -114,11 +116,13 @@ import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.Base64; import com.gitblit.utils.ByteFormat; +import com.gitblit.utils.CommitCache; import com.gitblit.utils.ContainerUtils; import com.gitblit.utils.DeepCopier; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.JGitUtils; +import com.gitblit.utils.JGitUtils.LastChange; import com.gitblit.utils.JsonUtils; import com.gitblit.utils.MetricUtils; import com.gitblit.utils.ObjectCache; @@ -228,6 +232,33 @@ } return gitblit; } + + /** + * Returns the boot date of the Gitblit server. + * + * @return the boot date of Gitblit + */ + public static Date getBootDate() { + return self().serverStatus.bootDate; + } + + /** + * Returns the most recent change date of any repository served by Gitblit. + * + * @return a date + */ + public static Date getLastActivityDate() { + Date date = null; + for (String name : self().getRepositoryList()) { + Repository r = self().getRepository(name); + Date lastChange = JGitUtils.getLastChange(r).when; + r.close(); + if (lastChange != null && (date == null || lastChange.after(date))) { + date = lastChange; + } + } + return date; + } /** * Determine if this is the GO variant of Gitblit. @@ -273,6 +304,15 @@ self().timezone = TimeZone.getTimeZone(tzid); } return self().timezone; + } + + /** + * Returns the active settings. + * + * @return the active settings + */ + public static IStoredSettings getSettings() { + return self().settings; } /** @@ -509,7 +549,7 @@ if (user == null) { user = UserModel.ANONYMOUS; } - String username = UserModel.ANONYMOUS.equals(user) ? "" : user.username; + String username = encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username); List<RepositoryUrl> list = new ArrayList<RepositoryUrl>(); // http/https url @@ -681,7 +721,15 @@ * @return true if the user service supports credential changes */ public boolean supportsCredentialChanges(UserModel user) { - return (user != null && user.isLocalAccount()) || userService.supportsCredentialChanges(); + if (user == null) { + return false; + } else if (AccountType.LOCAL.equals(user.accountType)) { + // local account, we can change credentials + return true; + } else { + // external account, ask user service + return userService.supportsCredentialChanges(); + } } /** @@ -715,6 +763,18 @@ } /** + * Returns true if the username represents an internal account + * + * @param username + * @return true if the specified username represents an internal account + */ + protected boolean isInternalAccount(String username) { + return !StringUtils.isEmpty(username) + && (username.equalsIgnoreCase(Constants.FEDERATION_USER) + || username.equalsIgnoreCase(UserModel.ANONYMOUS.username)); + } + + /** * Authenticate a user based on a username and password. * * @see IUserService.authenticate(String, char[]) @@ -727,6 +787,7 @@ // can not authenticate empty username return null; } + String usernameDecoded = decodeUsername(username); String pw = new String(password); if (StringUtils.isEmpty(pw)) { // can not authenticate empty password @@ -735,13 +796,10 @@ // check to see if this is the federation user if (canFederate()) { - if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) { + if (usernameDecoded.equalsIgnoreCase(Constants.FEDERATION_USER)) { List<String> tokens = getFederationTokens(); if (tokens.contains(pw)) { - // the federation user is an administrator - UserModel federationUser = new UserModel(Constants.FEDERATION_USER); - federationUser.canAdmin = true; - return federationUser; + return getFederationUser(); } } } @@ -750,7 +808,7 @@ if (userService == null) { return null; } - return userService.authenticate(username, password); + return userService.authenticate(usernameDecoded, password); } /** @@ -827,14 +885,27 @@ Principal principal = httpRequest.getUserPrincipal(); if (principal != null) { String username = principal.getName(); - if (StringUtils.isEmpty(username)) { + if (!StringUtils.isEmpty(username)) { + boolean internalAccount = isInternalAccount(username); UserModel user = getUserModel(username); if (user != null) { + // existing user flagWicketSession(AuthenticationType.CONTAINER); logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}", user.username, httpRequest.getRemoteAddr())); return user; - } else { + } else if (settings.getBoolean(Keys.realm.container.autoCreateAccounts, false) + && !internalAccount) { + // auto-create user from an authenticated container principal + user = new UserModel(username.toLowerCase()); + user.displayName = username; + user.password = Constants.EXTERNAL_ACCOUNT; + userService.updateUserModel(user); + flagWicketSession(AuthenticationType.CONTAINER); + logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}", + user.username, httpRequest.getRemoteAddr())); + return user; + } else if (!internalAccount) { logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}", principal.getName(), httpRequest.getRemoteAddr())); } @@ -910,7 +981,10 @@ if (userService == null) { return; } - if (userService.supportsCookies()) { + GitBlitWebSession session = GitBlitWebSession.get(); + boolean standardLogin = session.authenticationType.isStandard(); + + if (userService.supportsCookies() && standardLogin) { Cookie userCookie; if (user == null) { // clear cookie for logout @@ -945,6 +1019,26 @@ } /** + * Encode the username for user in an url. + * + * @param name + * @return the encoded name + */ + protected String encodeUsername(String name) { + return name.replace("@", "%40").replace(" ", "%20").replace("\\", "%5C"); + } + + /** + * Decode a username from an encoded url. + * + * @param name + * @return the decoded name + */ + protected String decodeUsername(String name) { + return name.replace("%40", "@").replace("%20", " ").replace("%5C", "\\"); + } + + /** * Returns the list of all users available to the login service. * * @see IUserService.getAllUsernames() @@ -977,7 +1071,15 @@ if (StringUtils.isEmpty(username)) { return false; } - return userService.deleteUser(username); + String usernameDecoded = decodeUsername(username); + return userService.deleteUser(usernameDecoded); + } + + protected UserModel getFederationUser() { + // the federation user is an administrator + UserModel federationUser = new UserModel(Constants.FEDERATION_USER); + federationUser.canAdmin = true; + return federationUser; } /** @@ -991,7 +1093,8 @@ if (StringUtils.isEmpty(username)) { return null; } - UserModel user = userService.getUserModel(username); + String usernameDecoded = decodeUsername(username); + UserModel user = userService.getUserModel(usernameDecoded); return user; } @@ -1394,22 +1497,13 @@ } else { // we are caching this list String msg = "{0} repositories identified in {1} msecs"; - - // optionally (re)calculate repository sizes if (getBoolean(Keys.web.showRepositorySizes, true)) { - ByteFormat byteFormat = new ByteFormat(); + // optionally (re)calculate repository sizes msg = "{0} repositories identified with calculated folder sizes in {1} msecs"; - for (String repository : repositories) { - RepositoryModel model = getRepositoryModel(repository); - if (!model.skipSizeCalculation) { - model.size = byteFormat.format(calculateSize(model)); - } - } - } else { - // update cache - for (String repository : repositories) { - getRepositoryModel(repository); - } + } + + for (String repository : repositories) { + getRepositoryModel(repository); } // rebuild fork networks @@ -1428,7 +1522,10 @@ } // return sorted copy of cached list - List<String> list = new ArrayList<String>(repositoryListCache.keySet()); + List<String> list = new ArrayList<String>(); + for (RepositoryModel model : repositoryListCache.values()) { + list.add(model.name); + } StringUtils.sortRepositorynames(list); return list; } @@ -1451,6 +1548,10 @@ * @return repository or null */ public Repository getRepository(String repositoryName, boolean logError) { + // Decode url-encoded repository name (issue-278) + // http://stackoverflow.com/questions/17183110 + repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~"); + if (isCollectingGarbage(repositoryName)) { logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName)); return null; @@ -1497,23 +1598,6 @@ } } } - if (getBoolean(Keys.web.showRepositorySizes, true)) { - int repoCount = 0; - long startTime = System.currentTimeMillis(); - ByteFormat byteFormat = new ByteFormat(); - for (RepositoryModel model : repositories) { - if (!model.skipSizeCalculation) { - repoCount++; - model.size = byteFormat.format(calculateSize(model)); - } - } - long duration = System.currentTimeMillis() - startTime; - if (duration > 250) { - // only log calcualtion time if > 250 msecs - logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs", - repoCount, duration)); - } - } long duration = System.currentTimeMillis() - methodStart; logger.info(MessageFormat.format("{0} repository models loaded for {1} in {2} msecs", repositories.size(), user == null ? "anonymous" : user.username, duration)); @@ -1550,13 +1634,17 @@ * @return repository model or null */ public RepositoryModel getRepositoryModel(String repositoryName) { + // Decode url-encoded repository name (issue-278) + // http://stackoverflow.com/questions/17183110 + repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~"); + if (!repositoryListCache.containsKey(repositoryName)) { RepositoryModel model = loadRepositoryModel(repositoryName); if (model == null) { return null; } addToCachedRepositoryList(model); - return model; + return DeepCopier.copy(model); } // cached model @@ -1592,11 +1680,7 @@ model.hasCommits = JGitUtils.hasCommits(r); } - model.lastChange = JGitUtils.getLastChange(r); - if (!model.skipSizeCalculation) { - ByteFormat byteFormat = new ByteFormat(); - model.size = byteFormat.format(calculateSize(model)); - } + updateLastChangeFields(r, model); } r.close(); @@ -1618,6 +1702,30 @@ } } return count; + } + + private void reloadProjectMarkdown(ProjectModel project) { + // project markdown + File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd"); + if (pmkd.exists()) { + Date lm = new Date(pmkd.lastModified()); + if (!projectMarkdownCache.hasCurrent(project.name, lm)) { + String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n"); + projectMarkdownCache.updateObject(project.name, lm, mkd); + } + project.projectMarkdown = projectMarkdownCache.getObject(project.name); + } + + // project repositories markdown + File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd"); + if (rmkd.exists()) { + Date lm = new Date(rmkd.lastModified()); + if (!projectRepositoriesMarkdownCache.hasCurrent(project.name, lm)) { + String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n"); + projectRepositoriesMarkdownCache.updateObject(project.name, lm, mkd); + } + project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(project.name); + } } @@ -1654,27 +1762,7 @@ project.title = projectConfigs.getString("project", name, "title"); project.description = projectConfigs.getString("project", name, "description"); - // project markdown - File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/project.mkd"); - if (pmkd.exists()) { - Date lm = new Date(pmkd.lastModified()); - if (!projectMarkdownCache.hasCurrent(name, lm)) { - String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n"); - projectMarkdownCache.updateObject(name, lm, mkd); - } - project.projectMarkdown = projectMarkdownCache.getObject(name); - } - - // project repositories markdown - File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/repositories.mkd"); - if (rmkd.exists()) { - Date lm = new Date(rmkd.lastModified()); - if (!projectRepositoriesMarkdownCache.hasCurrent(name, lm)) { - String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n"); - projectRepositoriesMarkdownCache.updateObject(name, lm, mkd); - } - project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(name); - } + reloadProjectMarkdown(project); configs.put(name.toLowerCase(), project); } @@ -1796,6 +1884,8 @@ // no repositories == no project return null; } + + reloadProjectMarkdown(project); return project; } @@ -1889,15 +1979,22 @@ // is symlinked. Use the provided repository name. model.name = repositoryName; } - model.hasCommits = JGitUtils.hasCommits(r); - model.lastChange = JGitUtils.getLastChange(r); model.projectPath = StringUtils.getFirstPathElement(repositoryName); StoredConfig config = r.getConfig(); boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url")); if (config != null) { + // Initialize description from description file + if (getConfig(config,"description", null) == null) { + File descFile = new File(r.getDirectory(), "description"); + if (descFile.exists()) { + config.setString(Constants.CONFIG_GITBLIT, null, "description", + com.gitblit.utils.FileUtils.readContent(descFile, System.getProperty("line.separator"))); + } + } model.description = getConfig(config, "description", ""); + model.originRepository = getConfig(config, "originRepository", null); model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", ""))); model.useTickets = getConfig(config, "useTickets", false); model.useDocs = getConfig(config, "useDocs", false); @@ -1939,6 +2036,8 @@ Constants.CONFIG_GITBLIT, null, "mailingList"))); model.indexedBranches = new ArrayList<String>(Arrays.asList(config.getStringList( Constants.CONFIG_GITBLIT, null, "indexBranch"))); + model.metricAuthorExclusions = new ArrayList<String>(Arrays.asList(config.getStringList( + Constants.CONFIG_GITBLIT, null, "metricAuthorExclusions"))); // Custom defined properties model.customFields = new LinkedHashMap<String, String>(); @@ -1949,9 +2048,11 @@ model.HEAD = JGitUtils.getHEADRef(r); model.availableRefs = JGitUtils.getAvailableHeadTargets(r); model.sparkleshareId = JGitUtils.getSparkleshareId(r); + model.hasCommits = JGitUtils.hasCommits(r); + updateLastChangeFields(r, model); r.close(); - if (model.origin != null && model.origin.startsWith("file://")) { + if (StringUtils.isEmpty(model.originRepository) && model.origin != null && model.origin.startsWith("file://")) { // repository was cloned locally... perhaps as a fork try { File folder = new File(new URI(model.origin)); @@ -1961,6 +2062,9 @@ File repoFolder = new File(getRepositoriesFolder(), originRepo); if (repoFolder.exists()) { model.originRepository = originRepo.toLowerCase(); + + // persist the fork origin + updateConfiguration(r, model); } } } catch (URISyntaxException e) { @@ -2141,21 +2245,31 @@ } /** - * Returns the size in bytes of the repository. Gitblit caches the - * repository sizes to reduce the performance penalty of recursive - * calculation. The cache is updated if the repository has been changed - * since the last calculation. + * Updates the last changed fields and optionally calculates the size of the + * repository. Gitblit caches the repository sizes to reduce the performance + * penalty of recursive calculation. The cache is updated if the repository + * has been changed since the last calculation. * * @param model - * @return size in bytes + * @return size in bytes of the repository */ - public long calculateSize(RepositoryModel model) { - if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) { - return repositorySizeCache.getObject(model.name); + public long updateLastChangeFields(Repository r, RepositoryModel model) { + LastChange lc = JGitUtils.getLastChange(r); + model.lastChange = lc.when; + model.lastChangeAuthor = lc.who; + + if (!getBoolean(Keys.web.showRepositorySizes, true) || model.skipSizeCalculation) { + model.size = null; + return 0L; } - File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED); - long size = com.gitblit.utils.FileUtils.folderSize(gitDir); - repositorySizeCache.updateObject(model.name, model.lastChange, size); + if (!repositorySizeCache.hasCurrent(model.name, model.lastChange)) { + File gitDir = r.getDirectory(); + long sz = com.gitblit.utils.FileUtils.folderSize(gitDir); + repositorySizeCache.updateObject(model.name, model.lastChange, sz); + } + long size = repositorySizeCache.getObject(model.name); + ByteFormat byteFormat = new ByteFormat(); + model.size = byteFormat.format(size); return size; } @@ -2360,6 +2474,7 @@ String origin = config.getString("remote", "origin", "url"); origin = origin.replace(repositoryName, repository.name); config.setString("remote", "origin", "url", origin); + config.setString(Constants.CONFIG_GITBLIT, null, "originRepository", repository.name); config.save(); } catch (Exception e) { logger.error("Failed to update repository fork config for " + fork, e); @@ -2368,11 +2483,12 @@ } } - // remove this repository from any origin model's fork list + // update this repository's origin's fork list if (!StringUtils.isEmpty(repository.originRepository)) { RepositoryModel origin = repositoryListCache.get(repository.originRepository); if (origin != null && !ArrayUtils.isEmpty(origin.forks)) { origin.forks.remove(repositoryName); + origin.forks.add(repository.name); } } @@ -2389,6 +2505,15 @@ // update settings if (r != null) { updateConfiguration(r, repository); + // Update the description file + File descFile = new File(r.getDirectory(), "description"); + if (repository.description != null) + { + com.gitblit.utils.FileUtils.writeContent(descFile, repository.description); + } + else if (descFile.exists() && !descFile.isDirectory()) { + descFile.delete(); + } // only update symbolic head if it changes String currentRef = JGitUtils.getHEADRef(r); if (!StringUtils.isEmpty(repository.HEAD) && !repository.HEAD.equals(currentRef)) { @@ -2421,6 +2546,7 @@ public void updateConfiguration(Repository r, RepositoryModel repository) { StoredConfig config = r.getConfig(); config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description); + config.setString(Constants.CONFIG_GITBLIT, null, "originRepository", repository.originRepository); config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners)); config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets); config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs); @@ -2465,6 +2591,7 @@ updateList(config, "postReceiveScript", repository.postReceiveScripts); updateList(config, "mailingList", repository.mailingLists); updateList(config, "indexBranch", repository.indexedBranches); + updateList(config, "metricAuthorExclusions", repository.metricAuthorExclusions); // User Defined Properties if (repository.customFields != null) { @@ -2871,8 +2998,7 @@ String cloneUrl = sb.toString(); // Retrieve all available repositories - UserModel user = new UserModel(Constants.FEDERATION_USER); - user.canAdmin = true; + UserModel user = getFederationUser(); List<RepositoryModel> list = getRepositoryModels(user); // create the [cloneurl, repositoryModel] map @@ -3006,7 +3132,9 @@ if (repository != null) { for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) { TeamModel team = userService.getTeamModel(teamname); - scripts.addAll(team.preReceiveScripts); + if (!ArrayUtils.isEmpty(team.preReceiveScripts)) { + scripts.addAll(team.preReceiveScripts); + } } } return new ArrayList<String>(scripts); @@ -3056,7 +3184,9 @@ if (repository != null) { for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) { TeamModel team = userService.getTeamModel(teamname); - scripts.addAll(team.postReceiveScripts); + if (!ArrayUtils.isEmpty(team.postReceiveScripts)) { + scripts.addAll(team.postReceiveScripts); + } } } return new ArrayList<String>(scripts); @@ -3349,7 +3479,8 @@ configureJGit(); configureFanout(); configureGitDaemon(); - + configureCommitCache(); + ContainerUtils.CVE_2007_0450.test(); } @@ -3459,6 +3590,42 @@ } } + protected void configureCommitCache() { + int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14); + if (daysToCache <= 0) { + logger.info("commit cache disabled"); + } else { + long start = System.nanoTime(); + long repoCount = 0; + long commitCount = 0; + logger.info(MessageFormat.format("preparing {0} day commit cache. please wait...", daysToCache)); + CommitCache.instance().setCacheDays(daysToCache); + Date cutoff = CommitCache.instance().getCutoffDate(); + for (String repositoryName : getRepositoryList()) { + RepositoryModel model = getRepositoryModel(repositoryName); + if (model != null && model.hasCommits && model.lastChange.after(cutoff)) { + repoCount++; + Repository repository = getRepository(repositoryName); + for (RefModel ref : JGitUtils.getLocalBranches(repository, true, -1)) { + if (!ref.getDate().after(cutoff)) { + // branch not recently updated + continue; + } + List<?> commits = CommitCache.instance().getCommits(repositoryName, repository, ref.getName()); + if (commits.size() > 0) { + logger.info(MessageFormat.format(" cached {0} commits for {1}:{2}", + commits.size(), repositoryName, ref.getName())); + commitCount += commits.size(); + } + } + repository.close(); + } + } + logger.info(MessageFormat.format("built {0} day commit cache of {1} commits across {2} repositories in {3} msecs", + daysToCache, commitCount, repoCount, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start))); + } + } + protected final Logger getLogger() { return logger; } -- Gitblit v1.9.1