James Moger
2014-05-07 ae6db865961bca99f13cf6f5658c8265765d7b48
src/main/java/com/gitblit/manager/GitblitManager.java
@@ -27,28 +27,59 @@
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.FetchCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.RefSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.fortsoft.pf4j.PluginState;
import ro.fortsoft.pf4j.PluginWrapper;
import ro.fortsoft.pf4j.Version;
import com.gitblit.Constants;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.Constants.AccessRestrictionType;
import com.gitblit.Constants.FederationRequest;
import com.gitblit.Constants.FederationToken;
import com.gitblit.GitBlitException;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
import com.gitblit.models.FederationSet;
import com.gitblit.models.ForkModel;
import com.gitblit.models.GitClientApplication;
import com.gitblit.models.Mailing;
import com.gitblit.models.Metric;
import com.gitblit.models.PluginRegistry.InstallState;
import com.gitblit.models.PluginRegistry.PluginRegistration;
import com.gitblit.models.PluginRegistry.PluginRelease;
import com.gitblit.models.ProjectModel;
import com.gitblit.models.RegistrantAccessPermission;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.RepositoryUrl;
import com.gitblit.models.SearchResult;
import com.gitblit.models.ServerSettings;
import com.gitblit.models.ServerStatus;
import com.gitblit.models.SettingModel;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.tickets.ITicketService;
import com.gitblit.transport.ssh.IPublicKeyManager;
import com.gitblit.transport.ssh.SshKey;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.HttpUtils;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.JsonUtils;
import com.gitblit.utils.ObjectCache;
import com.gitblit.utils.StringUtils;
@@ -57,29 +88,65 @@
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
public class GitblitManager implements IGitblitManager {
/**
 * GitblitManager is an aggregate interface delegate.  It implements all the manager
 * interfaces and delegates most methods calls to the proper manager implementation.
 * It's primary purpose is to provide complete management control to the git
 * upload and receive pack functions.
 *
 * GitblitManager also implements several integration methods when it is required
 * to manipulate several manages for one operation.
 *
 * @author James Moger
 *
 */
public class GitblitManager implements IGitblit {
   private final Logger logger = LoggerFactory.getLogger(getClass());
   protected final Logger logger = LoggerFactory.getLogger(getClass());
   private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
   protected final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
   private final IStoredSettings settings;
   protected final IStoredSettings settings;
   private final IRuntimeManager runtimeManager;
   protected final IRuntimeManager runtimeManager;
   private final IUserManager userManager;
   protected final IPluginManager pluginManager;
   private final IRepositoryManager repositoryManager;
   protected final INotificationManager notificationManager;
   protected final IUserManager userManager;
   protected final IAuthenticationManager authenticationManager;
   protected final IPublicKeyManager publicKeyManager;
   protected final IRepositoryManager repositoryManager;
   protected final IProjectManager projectManager;
   protected final IFederationManager federationManager;
   public GitblitManager(
         IRuntimeManager runtimeManager,
         IPluginManager pluginManager,
         INotificationManager notificationManager,
         IUserManager userManager,
         IRepositoryManager repositoryManager) {
         IAuthenticationManager authenticationManager,
         IPublicKeyManager publicKeyManager,
         IRepositoryManager repositoryManager,
         IProjectManager projectManager,
         IFederationManager federationManager) {
      this.settings = runtimeManager.getSettings();
      this.runtimeManager = runtimeManager;
      this.pluginManager = pluginManager;
      this.notificationManager = notificationManager;
      this.userManager = userManager;
      this.authenticationManager = authenticationManager;
      this.publicKeyManager = publicKeyManager;
      this.repositoryManager = repositoryManager;
      this.projectManager = projectManager;
      this.federationManager = federationManager;
   }
   @Override
@@ -93,6 +160,318 @@
      return this;
   }
   /*
    * IGITBLIT
    */
   /**
    * Creates a personal fork of the specified repository. The clone is view
    * restricted by default and the owner of the source repository is given
    * access to the clone.
    *
    * @param repository
    * @param user
    * @return the repository model of the fork, if successful
    * @throws GitBlitException
    */
   @Override
   public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
      String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
      String fromUrl = MessageFormat.format("file://{0}/{1}", repositoryManager.getRepositoriesFolder().getAbsolutePath(), repository.name);
      // clone the repository
      try {
         Repository canonical = getRepository(repository.name);
         File folder = new File(repositoryManager.getRepositoriesFolder(), cloneName);
         CloneCommand clone = new CloneCommand();
         clone.setBare(true);
         // fetch branches with exclusions
         Collection<Ref> branches = canonical.getRefDatabase().getRefs(Constants.R_HEADS).values();
         List<String> branchesToClone = new ArrayList<String>();
         for (Ref branch : branches) {
            String name = branch.getName();
            if (name.startsWith(Constants.R_TICKET)) {
               // exclude ticket branches
               continue;
            }
            branchesToClone.add(name);
         }
         clone.setBranchesToClone(branchesToClone);
         clone.setURI(fromUrl);
         clone.setDirectory(folder);
         Git git = clone.call();
         // fetch tags
         FetchCommand fetch  = git.fetch();
         fetch.setRefSpecs(new RefSpec("+refs/tags/*:refs/tags/*"));
         fetch.call();
         git.getRepository().close();
      } catch (Exception e) {
         throw new GitBlitException(e);
      }
      // create a Gitblit repository model for the clone
      RepositoryModel cloneModel = repository.cloneAs(cloneName);
      // owner has REWIND/RW+ permissions
      cloneModel.addOwner(user.username);
      repositoryManager.updateRepositoryModel(cloneName, cloneModel, false);
      // add the owner of the source repository to the clone's access list
      if (!ArrayUtils.isEmpty(repository.owners)) {
         for (String owner : repository.owners) {
            UserModel originOwner = userManager.getUserModel(owner);
            if (originOwner != null && !originOwner.canClone(cloneModel)) {
               // origin owner can't yet clone fork, grant explicit clone access
               originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
               reviseUser(originOwner.username, originOwner);
            }
         }
      }
      // grant origin's user list clone permission to fork
      List<String> users = repositoryManager.getRepositoryUsers(repository);
      List<UserModel> cloneUsers = new ArrayList<UserModel>();
      for (String name : users) {
         if (!name.equalsIgnoreCase(user.username)) {
            UserModel cloneUser = userManager.getUserModel(name);
            if (cloneUser.canClone(repository) && !cloneUser.canClone(cloneModel)) {
               // origin user can't yet clone fork, grant explicit clone access
               cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
            }
            cloneUsers.add(cloneUser);
         }
      }
      userManager.updateUserModels(cloneUsers);
      // grant origin's team list clone permission to fork
      List<String> teams = repositoryManager.getRepositoryTeams(repository);
      List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
      for (String name : teams) {
         TeamModel cloneTeam = userManager.getTeamModel(name);
         if (cloneTeam.canClone(repository) && !cloneTeam.canClone(cloneModel)) {
            // origin team can't yet clone fork, grant explicit clone access
            cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
         }
         cloneTeams.add(cloneTeam);
      }
      userManager.updateTeamModels(cloneTeams);
      // add this clone to the cached model
      repositoryManager.addToCachedRepositoryList(cloneModel);
      return cloneModel;
   }
   /**
    * Adds a TeamModel object.
    *
    * @param team
    */
   @Override
   public void addTeam(TeamModel team) throws GitBlitException {
      if (!userManager.updateTeamModel(team)) {
         throw new GitBlitException("Failed to add team!");
      }
   }
   /**
    * Updates the TeamModel object for the specified name.
    *
    * @param teamname
    * @param team
    */
   @Override
   public void reviseTeam(String teamname, TeamModel team) throws GitBlitException {
      if (!teamname.equalsIgnoreCase(team.name)) {
         if (userManager.getTeamModel(team.name) != null) {
            throw new GitBlitException(MessageFormat.format(
                  "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
                  team.name));
         }
      }
      if (!userManager.updateTeamModel(teamname, team)) {
         throw new GitBlitException("Failed to update team!");
      }
   }
   /**
    * Adds a user object.
    *
    * @param user
    * @throws GitBlitException
    */
   @Override
   public void addUser(UserModel user) throws GitBlitException {
      if (!userManager.updateUserModel(user)) {
         throw new GitBlitException("Failed to add user!");
      }
   }
   /**
    * Updates a user object keyed by username. This method allows
    * for renaming a user.
    *
    * @param username
    * @param user
    * @throws GitBlitException
    */
   @Override
   public void reviseUser(String username, UserModel user) throws GitBlitException {
      if (!username.equalsIgnoreCase(user.username)) {
         if (userManager.getUserModel(user.username) != null) {
            throw new GitBlitException(MessageFormat.format(
                  "Failed to rename ''{0}'' because ''{1}'' already exists.", username,
                  user.username));
         }
         // rename repositories and owner fields for all repositories
         for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) {
            if (model.isUsersPersonalRepository(username)) {
               // personal repository
               model.addOwner(user.username);
               String oldRepositoryName = model.name;
               model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length());
               model.projectPath = user.getPersonalPath();
               repositoryManager.updateRepositoryModel(oldRepositoryName, model, false);
            } else if (model.isOwner(username)) {
               // common/shared repo
               model.addOwner(user.username);
               repositoryManager.updateRepositoryModel(model.name, model, false);
            }
         }
         // rename the user's ssh public keystore
         getPublicKeyManager().renameUser(username, user.username);
      }
      if (!userManager.updateUserModel(username, user)) {
         throw new GitBlitException("Failed to update user!");
      }
   }
   /**
    * Returns a list of repository URLs and the user access permission.
    *
    * @param request
    * @param user
    * @param repository
    * @return a list of repository urls
    */
   @Override
   public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
      if (user == null) {
         user = UserModel.ANONYMOUS;
      }
      String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
      List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
      // http/https url
      if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
         AccessPermission permission = user.getRepositoryPermission(repository).permission;
         if (permission.exceeds(AccessPermission.NONE)) {
            list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
         }
      }
      // add all other urls
      // {0} = repository
      // {1} = username
      for (String url : settings.getStrings(Keys.web.otherUrls)) {
         if (url.contains("{1}")) {
            // external url requires username, only add url IF we have one
            if (!StringUtils.isEmpty(username)) {
               list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
            }
         } else {
            // external url does not require username
            list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
         }
      }
      return list;
   }
   protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
      String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null);
      if (StringUtils.isEmpty(gitblitUrl)) {
         gitblitUrl = HttpUtils.getGitblitURL(request);
      }
      StringBuilder sb = new StringBuilder();
      sb.append(gitblitUrl);
      sb.append(Constants.R_PATH);
      sb.append(repository.name);
      // inject username into repository url if authentication is required
      if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
            && !StringUtils.isEmpty(username)) {
         sb.insert(sb.indexOf("://") + 3, username + "@");
      }
      return sb.toString();
   }
   /**
    * Returns the list of custom client applications to be used for the
    * repository url panel;
    *
    * @return a collection of client applications
    */
   @Override
   public Collection<GitClientApplication> getClientApplications() {
      // prefer user definitions, if they exist
      File userDefs = new File(runtimeManager.getBaseFolder(), "clientapps.json");
      if (userDefs.exists()) {
         Date lastModified = new Date(userDefs.lastModified());
         if (clientApplications.hasCurrent("user", lastModified)) {
            return clientApplications.getObject("user");
         } else {
            // (re)load user definitions
            try {
               InputStream is = new FileInputStream(userDefs);
               Collection<GitClientApplication> clients = readClientApplications(is);
               is.close();
               if (clients != null) {
                  clientApplications.updateObject("user", lastModified, clients);
                  return clients;
               }
            } catch (IOException e) {
               logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
            }
         }
      }
      // no user definitions, use system definitions
      if (!clientApplications.hasCurrent("system", new Date(0))) {
         try {
            InputStream is = GitblitManager.class.getResourceAsStream("/clientapps.json");
            Collection<GitClientApplication> clients = readClientApplications(is);
            is.close();
            if (clients != null) {
               clientApplications.updateObject("system", new Date(0), clients);
            }
         } catch (IOException e) {
            logger.error("Failed to deserialize clientapps.json resource!", e);
         }
      }
      return clientApplications.getObject("system");
   }
   private Collection<GitClientApplication> readClientApplications(InputStream is) {
      try {
         Type type = new TypeToken<Collection<GitClientApplication>>() {
         }.getType();
         InputStreamReader reader = new InputStreamReader(is);
         Gson gson = JsonUtils.gson();
         Collection<GitClientApplication> links = gson.fromJson(reader, type);
         return links;
      } catch (JsonIOException e) {
         logger.error("Error deserializing client applications!", e);
      } catch (JsonSyntaxException e) {
         logger.error("Error deserializing client applications!", e);
      }
      return null;
   }
   /**
    * Parse the properties file and aggregate all the comments by the setting
    * key. A setting model tracks the current value, the default value, the
@@ -101,18 +480,11 @@
    * @return Map<String, SettingModel>
    */
   private void loadSettingModels(ServerSettings settingsModel) {
      // this entire "supports" concept will go away with user service refactoring
      UserModel externalUser = new UserModel(Constants.EXTERNAL_ACCOUNT);
      externalUser.password = Constants.EXTERNAL_ACCOUNT;
      settingsModel.supportsCredentialChanges = userManager.supportsCredentialChanges(externalUser);
      settingsModel.supportsDisplayNameChanges = userManager.supportsDisplayNameChanges(externalUser);
      settingsModel.supportsEmailAddressChanges = userManager.supportsEmailAddressChanges(externalUser);
      settingsModel.supportsTeamMembershipChanges = userManager.supportsTeamMembershipChanges(externalUser);
      try {
         // Read bundled Gitblit properties to extract setting descriptions.
         // This copy is pristine and only used for populating the setting
         // models map.
         InputStream is = getClass().getResourceAsStream("/reference.properties");
         InputStream is = GitblitManager.class.getResourceAsStream("/reference.properties");
         BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
         StringBuilder description = new StringBuilder();
         SettingModel setting = new SettingModel();
@@ -164,300 +536,768 @@
   }
   /**
    * Returns a list of repository URLs and the user access permission.
    * Throws an exception if trying to get a ticket service.
    *
    * @param request
    * @param user
    * @param repository
    * @return a list of repository urls
    */
   @Override
   public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
   public ITicketService getTicketService() {
      throw new RuntimeException("This class does not have a ticket service!");
   }
   @Override
   public IPublicKeyManager getPublicKeyManager() {
      return publicKeyManager;
   }
   /*
    * ISTOREDSETTINGS
    *
    * these methods are necessary for (nearly) seamless Groovy hook operation
    * after the massive refactor.
    */
   public boolean getBoolean(String key, boolean defaultValue) {
      return runtimeManager.getSettings().getBoolean(key, defaultValue);
   }
   public String getString(String key, String defaultValue) {
      return runtimeManager.getSettings().getString(key, defaultValue);
   }
   public int getInteger(String key, int defaultValue) {
      return runtimeManager.getSettings().getInteger(key, defaultValue);
   }
   public List<String> getStrings(String key) {
      return runtimeManager.getSettings().getStrings(key);
   }
   /*
    * RUNTIME MANAGER
    */
   @Override
   public File getBaseFolder() {
      return runtimeManager.getBaseFolder();
   }
   @Override
   public void setBaseFolder(File folder) {
      runtimeManager.setBaseFolder(folder);
   }
   @Override
   public Date getBootDate() {
      return runtimeManager.getBootDate();
   }
   @Override
   public ServerSettings getSettingsModel() {
      return runtimeManager.getSettingsModel();
   }
   @Override
   public boolean isServingRepositories() {
      return runtimeManager.isServingRepositories();
   }
   @Override
   public TimeZone getTimezone() {
      return runtimeManager.getTimezone();
   }
   @Override
   public Locale getLocale() {
      return runtimeManager.getLocale();
   }
   @Override
   public boolean isDebugMode() {
      return runtimeManager.isDebugMode();
   }
   @Override
   public File getFileOrFolder(String key, String defaultFileOrFolder) {
      return runtimeManager.getFileOrFolder(key, defaultFileOrFolder);
   }
   @Override
   public File getFileOrFolder(String fileOrFolder) {
      return runtimeManager.getFileOrFolder(fileOrFolder);
   }
   @Override
   public IStoredSettings getSettings() {
      return runtimeManager.getSettings();
   }
   @Override
   public boolean updateSettings(Map<String, String> updatedSettings) {
      return runtimeManager.updateSettings(updatedSettings);
   }
   @Override
   public ServerStatus getStatus() {
      return runtimeManager.getStatus();
   }
   /*
    * NOTIFICATION MANAGER
    */
   @Override
   public void sendMailToAdministrators(String subject, String message) {
      notificationManager.sendMailToAdministrators(subject, message);
   }
   @Override
   public void sendMail(String subject, String message, Collection<String> toAddresses) {
      notificationManager.sendMail(subject, message, toAddresses);
   }
   @Override
   public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
      notificationManager.sendHtmlMail(subject, message, toAddresses);
   }
   @Override
   public void send(Mailing mail) {
      notificationManager.send(mail);
   }
   /*
    * SESSION MANAGER
    */
   @Override
   public UserModel authenticate(String username, char[] password) {
      return authenticationManager.authenticate(username, password);
   }
   @Override
   public UserModel authenticate(HttpServletRequest httpRequest) {
      UserModel user = authenticationManager.authenticate(httpRequest, false);
      if (user == null) {
         user = UserModel.ANONYMOUS;
         user = federationManager.authenticate(httpRequest);
      }
      String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
      List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
      // http/https url
      if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
         AccessPermission permission = user.getRepositoryPermission(repository).permission;
         if (permission.exceeds(AccessPermission.NONE)) {
            list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
         }
      }
      // git daemon url
      String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
      if (!StringUtils.isEmpty(gitDaemonUrl)) {
         AccessPermission permission = getGitDaemonAccessPermission(user, repository);
         if (permission.exceeds(AccessPermission.NONE)) {
            list.add(new RepositoryUrl(gitDaemonUrl, permission));
         }
      }
      // add all other urls
      // {0} = repository
      // {1} = username
      for (String url : settings.getStrings(Keys.web.otherUrls)) {
         if (url.contains("{1}")) {
            // external url requires username, only add url IF we have one
            if (!StringUtils.isEmpty(username)) {
               list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
            }
         } else {
            // external url does not require username
            list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
         }
      }
      return list;
      return user;
   }
   protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
      StringBuilder sb = new StringBuilder();
      sb.append(HttpUtils.getGitblitURL(request));
      sb.append(Constants.GIT_PATH);
      sb.append(repository.name);
      // inject username into repository url if authentication is required
      if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
            && !StringUtils.isEmpty(username)) {
         sb.insert(sb.indexOf("://") + 3, username + "@");
      }
      return sb.toString();
   }
   protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
//      if (gitDaemon != null) {
//         String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
//         if (bindInterface.equals("localhost")
//               && (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) {
//            // git daemon is bound to localhost and the request is from elsewhere
//            return null;
//         }
//         if (user.canClone(repository)) {
//            String servername = request.getServerName();
//            String url = gitDaemon.formatUrl(servername, repository.name);
//            return url;
//         }
//      }
      return null;
   }
   protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
//      if (gitDaemon != null && user.canClone(repository)) {
//         AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
//         if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
//            if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
//               // can not authenticate clone via anonymous git protocol
//               gitDaemonPermission = AccessPermission.NONE;
//            } else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
//               // can not authenticate push via anonymous git protocol
//               gitDaemonPermission = AccessPermission.CLONE;
//            } else {
//               // normal user permission
//            }
//         }
//         return gitDaemonPermission;
//      }
      return AccessPermission.NONE;
   }
   /**
    * Returns the list of custom client applications to be used for the
    * repository url panel;
    *
    * @return a collection of client applications
    */
   @Override
   public Collection<GitClientApplication> getClientApplications() {
      // prefer user definitions, if they exist
      File userDefs = new File(runtimeManager.getBaseFolder(), "clientapps.json");
      if (userDefs.exists()) {
         Date lastModified = new Date(userDefs.lastModified());
         if (clientApplications.hasCurrent("user", lastModified)) {
            return clientApplications.getObject("user");
         } else {
            // (re)load user definitions
            try {
               InputStream is = new FileInputStream(userDefs);
               Collection<GitClientApplication> clients = readClientApplications(is);
               is.close();
               if (clients != null) {
                  clientApplications.updateObject("user", lastModified, clients);
                  return clients;
               }
            } catch (IOException e) {
               logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
            }
         }
      }
      // no user definitions, use system definitions
      if (!clientApplications.hasCurrent("system", new Date(0))) {
         try {
            InputStream is = getClass().getResourceAsStream("/clientapps.json");
            Collection<GitClientApplication> clients = readClientApplications(is);
            is.close();
            if (clients != null) {
               clientApplications.updateObject("system", new Date(0), clients);
            }
         } catch (IOException e) {
            logger.error("Failed to deserialize clientapps.json resource!", e);
         }
      }
      return clientApplications.getObject("system");
   public UserModel authenticate(String username, SshKey key) {
      return authenticationManager.authenticate(username, key);
   }
   private Collection<GitClientApplication> readClientApplications(InputStream is) {
      try {
         Type type = new TypeToken<Collection<GitClientApplication>>() {
         }.getType();
         InputStreamReader reader = new InputStreamReader(is);
         Gson gson = JsonUtils.gson();
         Collection<GitClientApplication> links = gson.fromJson(reader, type);
         return links;
      } catch (JsonIOException e) {
         logger.error("Error deserializing client applications!", e);
      } catch (JsonSyntaxException e) {
         logger.error("Error deserializing client applications!", e);
      }
      return null;
   }
   /**
    * Creates a personal fork of the specified repository. The clone is view
    * restricted by default and the owner of the source repository is given
    * access to the clone.
    *
    * @param repository
    * @param user
    * @return the repository model of the fork, if successful
    * @throws GitBlitException
    */
   @Override
   public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
      String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
      String fromUrl = MessageFormat.format("file://{0}/{1}", repositoryManager.getRepositoriesFolder().getAbsolutePath(), repository.name);
      // clone the repository
      try {
         JGitUtils.cloneRepository(repositoryManager.getRepositoriesFolder(), cloneName, fromUrl, true, null);
      } catch (Exception e) {
         throw new GitBlitException(e);
   public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
      UserModel user = authenticationManager.authenticate(httpRequest, requiresCertificate);
      if (user == null) {
         user = federationManager.authenticate(httpRequest);
      }
      // create a Gitblit repository model for the clone
      RepositoryModel cloneModel = repository.cloneAs(cloneName);
      // owner has REWIND/RW+ permissions
      cloneModel.addOwner(user.username);
      repositoryManager.updateRepositoryModel(cloneName, cloneModel, false);
      // add the owner of the source repository to the clone's access list
      if (!ArrayUtils.isEmpty(repository.owners)) {
         for (String owner : repository.owners) {
            UserModel originOwner = userManager.getUserModel(owner);
            if (originOwner != null) {
               originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
               updateUserModel(originOwner.username, originOwner, false);
            }
         }
      }
      // grant origin's user list clone permission to fork
      List<String> users = repositoryManager.getRepositoryUsers(repository);
      List<UserModel> cloneUsers = new ArrayList<UserModel>();
      for (String name : users) {
         if (!name.equalsIgnoreCase(user.username)) {
            UserModel cloneUser = userManager.getUserModel(name);
            if (cloneUser.canClone(repository)) {
               // origin user can clone origin, grant clone access to fork
               cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
            }
            cloneUsers.add(cloneUser);
         }
      }
      userManager.updateUserModels(cloneUsers);
      // grant origin's team list clone permission to fork
      List<String> teams = repositoryManager.getRepositoryTeams(repository);
      List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
      for (String name : teams) {
         TeamModel cloneTeam = userManager.getTeamModel(name);
         if (cloneTeam.canClone(repository)) {
            // origin team can clone origin, grant clone access to fork
            cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
         }
         cloneTeams.add(cloneTeam);
      }
      userManager.updateTeamModels(cloneTeams);
      // add this clone to the cached model
      repositoryManager.addToCachedRepositoryList(cloneModel);
      return cloneModel;
      return user;
   }
   /**
    * Adds/updates a complete user object keyed by username. This method allows
    * for renaming a user.
    *
    * @see IUserService.updateUserModel(String, UserModel)
    * @param username
    * @param user
    * @param isCreate
    * @throws GitBlitException
    */
   @Override
   public void updateUserModel(String username, UserModel user, boolean isCreate)
         throws GitBlitException {
      if (!username.equalsIgnoreCase(user.username)) {
         if (userManager.getUserModel(user.username) != null) {
            throw new GitBlitException(MessageFormat.format(
                  "Failed to rename ''{0}'' because ''{1}'' already exists.", username,
                  user.username));
         }
         // rename repositories and owner fields for all repositories
         for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) {
            if (model.isUsersPersonalRepository(username)) {
               // personal repository
               model.addOwner(user.username);
               String oldRepositoryName = model.name;
               model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length());
               model.projectPath = user.getPersonalPath();
               repositoryManager.updateRepositoryModel(oldRepositoryName, model, false);
            } else if (model.isOwner(username)) {
               // common/shared repo
               model.addOwner(user.username);
               repositoryManager.updateRepositoryModel(model.name, model, false);
            }
         }
      }
      if (!userManager.updateUserModel(username, user)) {
         throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
      }
   public String getCookie(HttpServletRequest request) {
      return authenticationManager.getCookie(request);
   }
   /**
    * Updates the TeamModel object for the specified name.
    *
    * @param teamname
    * @param team
    * @param isCreate
    */
   @Override
   public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
         throws GitBlitException {
      if (!teamname.equalsIgnoreCase(team.name)) {
         if (userManager.getTeamModel(team.name) != null) {
            throw new GitBlitException(MessageFormat.format(
                  "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
                  team.name));
         }
      }
      if (!userManager.updateTeamModel(teamname, team)) {
         throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
      }
   public void setCookie(HttpServletResponse response, UserModel user) {
      authenticationManager.setCookie(response, user);
   }
   @Override
   public void logout(HttpServletResponse response, UserModel user) {
      authenticationManager.logout(response, user);
   }
   @Override
   public boolean supportsCredentialChanges(UserModel user) {
      return authenticationManager.supportsCredentialChanges(user);
   }
   @Override
   public boolean supportsDisplayNameChanges(UserModel user) {
      return authenticationManager.supportsDisplayNameChanges(user);
   }
   @Override
   public boolean supportsEmailAddressChanges(UserModel user) {
      return authenticationManager.supportsEmailAddressChanges(user);
   }
   @Override
   public boolean supportsTeamMembershipChanges(UserModel user) {
      return authenticationManager.supportsTeamMembershipChanges(user);
   }
   @Override
   public boolean supportsTeamMembershipChanges(TeamModel team) {
      return authenticationManager.supportsTeamMembershipChanges(team);
   }
   /*
    * USER MANAGER
    */
   @Override
   public void setup(IRuntimeManager runtimeManager) {
   }
   @Override
   public boolean isInternalAccount(String username) {
      return userManager.isInternalAccount(username);
   }
   @Override
   public List<String> getAllUsernames() {
      return userManager.getAllUsernames();
   }
   @Override
   public List<UserModel> getAllUsers() {
      return userManager.getAllUsers();
   }
   @Override
   public boolean deleteUser(String username) {
      return userManager.deleteUser(username);
   }
   @Override
   public UserModel getUserModel(String username) {
      return userManager.getUserModel(username);
   }
   @Override
   public List<TeamModel> getAllTeams() {
      return userManager.getAllTeams();
   }
   @Override
   public TeamModel getTeamModel(String teamname) {
      return userManager.getTeamModel(teamname);
   }
   @Override
   public String getCookie(UserModel model) {
      return userManager.getCookie(model);
   }
   @Override
   public UserModel getUserModel(char[] cookie) {
      return userManager.getUserModel(cookie);
   }
   @Override
   public boolean updateUserModel(UserModel model) {
      return userManager.updateUserModel(model);
   }
   @Override
   public boolean updateUserModels(Collection<UserModel> models) {
      return userManager.updateUserModels(models);
   }
   @Override
   public boolean updateUserModel(String username, UserModel model) {
      return userManager.updateUserModel(username, model);
   }
   @Override
   public boolean deleteUserModel(UserModel model) {
      return userManager.deleteUserModel(model);
   }
   @Override
   public List<String> getAllTeamNames() {
      return userManager.getAllTeamNames();
   }
   @Override
   public List<String> getTeamNamesForRepositoryRole(String role) {
      return userManager.getTeamNamesForRepositoryRole(role);
   }
   @Override
   public boolean updateTeamModel(TeamModel model) {
      return userManager.updateTeamModel(model);
   }
   @Override
   public boolean updateTeamModels(Collection<TeamModel> models) {
      return userManager.updateTeamModels(models);
   }
   @Override
   public boolean updateTeamModel(String teamname, TeamModel model) {
      return userManager.updateTeamModel(teamname, model);
   }
   @Override
   public boolean deleteTeamModel(TeamModel model) {
      return userManager.deleteTeamModel(model);
   }
   @Override
   public List<String> getUsernamesForRepositoryRole(String role) {
      return userManager.getUsernamesForRepositoryRole(role);
   }
   @Override
   public boolean renameRepositoryRole(String oldRole, String newRole) {
      return userManager.renameRepositoryRole(oldRole, newRole);
   }
   @Override
   public boolean deleteRepositoryRole(String role) {
      return userManager.deleteRepositoryRole(role);
   }
   @Override
   public boolean deleteTeam(String teamname) {
      return userManager.deleteTeam(teamname);
   }
   /*
    * REPOSITORY MANAGER
    */
   @Override
   public Date getLastActivityDate() {
      return repositoryManager.getLastActivityDate();
   }
   @Override
   public File getRepositoriesFolder() {
      return repositoryManager.getRepositoriesFolder();
   }
   @Override
   public File getHooksFolder() {
      return repositoryManager.getHooksFolder();
   }
   @Override
   public File getGrapesFolder() {
      return repositoryManager.getGrapesFolder();
   }
   @Override
   public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
      return repositoryManager.getUserAccessPermissions(user);
   }
   @Override
   public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
      return repositoryManager.getUserAccessPermissions(repository);
   }
   @Override
   public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
      return repositoryManager.setUserAccessPermissions(repository, permissions);
   }
   @Override
   public List<String> getRepositoryUsers(RepositoryModel repository) {
      return repositoryManager.getRepositoryUsers(repository);
   }
   @Override
   public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
      return repositoryManager.getTeamAccessPermissions(repository);
   }
   @Override
   public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
      return repositoryManager.setTeamAccessPermissions(repository, permissions);
   }
   @Override
   public List<String> getRepositoryTeams(RepositoryModel repository) {
      return repositoryManager.getRepositoryTeams(repository);
   }
   @Override
   public void addToCachedRepositoryList(RepositoryModel model) {
      repositoryManager.addToCachedRepositoryList(model);
   }
   @Override
   public void resetRepositoryListCache() {
      repositoryManager.resetRepositoryListCache();
   }
   @Override
   public void resetRepositoryCache(String repositoryName) {
      repositoryManager.resetRepositoryCache(repositoryName);
   }
   @Override
   public List<String> getRepositoryList() {
      return repositoryManager.getRepositoryList();
   }
   @Override
   public Repository getRepository(String repositoryName) {
      return repositoryManager.getRepository(repositoryName);
   }
   @Override
   public Repository getRepository(String repositoryName, boolean logError) {
      return repositoryManager.getRepository(repositoryName, logError);
   }
   @Override
   public List<RepositoryModel> getRepositoryModels(UserModel user) {
      return repositoryManager.getRepositoryModels(user);
   }
   @Override
   public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
      return repositoryManager.getRepositoryModel(repositoryName);
   }
   @Override
   public RepositoryModel getRepositoryModel(String repositoryName) {
      return repositoryManager.getRepositoryModel(repositoryName);
   }
   @Override
   public long getStarCount(RepositoryModel repository) {
      return repositoryManager.getStarCount(repository);
   }
   @Override
   public boolean hasRepository(String repositoryName) {
      return repositoryManager.hasRepository(repositoryName);
   }
   @Override
   public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
      return repositoryManager.hasRepository(repositoryName, caseSensitiveCheck);
   }
   @Override
   public boolean hasFork(String username, String origin) {
      return repositoryManager.hasFork(username, origin);
   }
   @Override
   public String getFork(String username, String origin) {
      return repositoryManager.getFork(username, origin);
   }
   @Override
   public ForkModel getForkNetwork(String repository) {
      return repositoryManager.getForkNetwork(repository);
   }
   @Override
   public long updateLastChangeFields(Repository r, RepositoryModel model) {
      return repositoryManager.updateLastChangeFields(r, model);
   }
   @Override
   public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
      return repositoryManager.getRepositoryDefaultMetrics(model, repository);
   }
   @Override
   public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
         boolean isCreate) throws GitBlitException {
      repositoryManager.updateRepositoryModel(repositoryName, repository, isCreate);
   }
   @Override
   public void updateConfiguration(Repository r, RepositoryModel repository) {
      repositoryManager.updateConfiguration(r, repository);
   }
   @Override
   public boolean deleteRepositoryModel(RepositoryModel model) {
      return repositoryManager.deleteRepositoryModel(model);
   }
   @Override
   public boolean deleteRepository(String repositoryName) {
      return repositoryManager.deleteRepository(repositoryName);
   }
   @Override
   public List<String> getAllScripts() {
      return repositoryManager.getAllScripts();
   }
   @Override
   public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) {
      return repositoryManager.getPreReceiveScriptsInherited(repository);
   }
   @Override
   public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) {
      return repositoryManager.getPreReceiveScriptsUnused(repository);
   }
   @Override
   public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) {
      return repositoryManager.getPostReceiveScriptsInherited(repository);
   }
   @Override
   public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) {
      return repositoryManager.getPostReceiveScriptsUnused(repository);
   }
   @Override
   public List<SearchResult> search(String query, int page, int pageSize, List<String> repositories) {
      return repositoryManager.search(query, page, pageSize, repositories);
   }
   @Override
   public boolean isCollectingGarbage() {
      return repositoryManager.isCollectingGarbage();
   }
   @Override
   public boolean isCollectingGarbage(String repositoryName) {
      return repositoryManager.isCollectingGarbage(repositoryName);
   }
   /*
    * PROJECT MANAGER
    */
   @Override
   public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
      return projectManager.getProjectModels(user, includeUsers);
   }
   @Override
   public ProjectModel getProjectModel(String name, UserModel user) {
      return projectManager.getProjectModel(name, user);
   }
   @Override
   public ProjectModel getProjectModel(String name) {
      return projectManager.getProjectModel(name);
   }
   @Override
   public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
      return projectManager.getProjectModels(repositoryModels, includeUsers);
   }
   /*
    * FEDERATION MANAGER
    */
   @Override
   public File getProposalsFolder() {
      return federationManager.getProposalsFolder();
   }
   @Override
   public boolean canFederate() {
      return federationManager.canFederate();
   }
   @Override
   public UserModel getFederationUser() {
      return federationManager.getFederationUser();
   }
   @Override
   public List<FederationModel> getFederationRegistrations() {
      return federationManager.getFederationRegistrations();
   }
   @Override
   public FederationModel getFederationRegistration(String url, String name) {
      return federationManager.getFederationRegistration(url, name);
   }
   @Override
   public List<FederationSet> getFederationSets(String gitblitUrl) {
      return federationManager.getFederationSets(gitblitUrl);
   }
   @Override
   public List<String> getFederationTokens() {
      return federationManager.getFederationTokens();
   }
   @Override
   public String getFederationToken(FederationToken type) {
      return federationManager.getFederationToken(type);
   }
   @Override
   public String getFederationToken(String value) {
      return federationManager.getFederationToken(value);
   }
   @Override
   public boolean validateFederationRequest(FederationRequest req, String token) {
      return federationManager.validateFederationRequest(req, token);
   }
   @Override
   public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
      return federationManager.acknowledgeFederationStatus(identification, registration);
   }
   @Override
   public List<FederationModel> getFederationResultRegistrations() {
      return federationManager.getFederationResultRegistrations();
   }
   @Override
   public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
      return federationManager.submitFederationProposal(proposal, gitblitUrl);
   }
   @Override
   public List<FederationProposal> getPendingFederationProposals() {
      return federationManager.getPendingFederationProposals();
   }
   @Override
   public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
      return federationManager.getRepositories(gitblitUrl, token);
   }
   @Override
   public FederationProposal createFederationProposal(String gitblitUrl, String token) {
      return federationManager.createFederationProposal(gitblitUrl, token);
   }
   @Override
   public FederationProposal getPendingFederationProposal(String token) {
      return federationManager.getPendingFederationProposal(token);
   }
   @Override
   public boolean deletePendingFederationProposal(FederationProposal proposal) {
      return federationManager.deletePendingFederationProposal(proposal);
   }
   @Override
   public void closeAll() {
      repositoryManager.closeAll();
   }
   @Override
   public void close(String repository) {
      repositoryManager.close(repository);
   }
   @Override
   public boolean isIdle(Repository repository) {
      return repositoryManager.isIdle(repository);
   }
   /*
    * PLUGIN MANAGER
    */
   @Override
   public Version getSystemVersion() {
      return pluginManager.getSystemVersion();
   }
   @Override
   public void startPlugins() {
      pluginManager.startPlugins();
   }
   @Override
   public void stopPlugins() {
      pluginManager.stopPlugins();
   }
   @Override
   public List<PluginWrapper> getPlugins() {
      return pluginManager.getPlugins();
   }
   @Override
   public PluginWrapper getPlugin(String pluginId) {
      return pluginManager.getPlugin(pluginId);
   }
   @Override
   public List<Class<?>> getExtensionClasses(String pluginId) {
      return pluginManager.getExtensionClasses(pluginId);
   }
   @Override
   public <T> List<T> getExtensions(Class<T> clazz) {
      return pluginManager.getExtensions(clazz);
   }
   @Override
   public PluginWrapper whichPlugin(Class<?> clazz) {
      return pluginManager.whichPlugin(clazz);
   }
   @Override
   public PluginState startPlugin(String pluginId) {
      return pluginManager.startPlugin(pluginId);
   }
   @Override
   public PluginState stopPlugin(String pluginId) {
      return pluginManager.stopPlugin(pluginId);
   }
   @Override
   public boolean disablePlugin(String pluginId) {
      return pluginManager.disablePlugin(pluginId);
   }
   @Override
   public boolean enablePlugin(String pluginId) {
      return pluginManager.enablePlugin(pluginId);
   }
   @Override
   public boolean uninstallPlugin(String pluginId) {
      return pluginManager.uninstallPlugin(pluginId);
   }
   @Override
   public boolean refreshRegistry(boolean verifyChecksum) {
      return pluginManager.refreshRegistry(verifyChecksum);
   }
   @Override
   public boolean installPlugin(String url, boolean verifyChecksum) throws IOException {
      return pluginManager.installPlugin(url, verifyChecksum);
   }
   @Override
   public boolean upgradePlugin(String pluginId, String url, boolean verifyChecksum) throws IOException {
      return pluginManager.upgradePlugin(pluginId, url, verifyChecksum);
   }
   @Override
   public List<PluginRegistration> getRegisteredPlugins() {
      return pluginManager.getRegisteredPlugins();
   }
   @Override
   public List<PluginRegistration> getRegisteredPlugins(InstallState state) {
      return pluginManager.getRegisteredPlugins(state);
   }
   @Override
   public PluginRegistration lookupPlugin(String pluginId) {
      return pluginManager.lookupPlugin(pluginId);
   }
   @Override
   public PluginRelease lookupRelease(String pluginId, String version) {
      return pluginManager.lookupRelease(pluginId, version);
   }
}