summaryrefslogtreecommitdiffstats
path: root/src/com/gitblit/GitBlit.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/gitblit/GitBlit.java')
-rw-r--r--src/com/gitblit/GitBlit.java447
1 files changed, 432 insertions, 15 deletions
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index c54fbe14..7c499698 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -16,6 +16,7 @@
package com.gitblit;
import java.io.File;
+import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.text.MessageFormat;
@@ -25,8 +26,14 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import javax.mail.Message;
+import javax.mail.MessagingException;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.Cookie;
@@ -46,10 +53,17 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.UserModel;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
/**
* GitBlit is the servlet context listener singleton that acts as the core for
@@ -73,6 +87,13 @@ public class GitBlit implements ServletContextListener {
private final Logger logger = LoggerFactory.getLogger(GitBlit.class);
+ private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
+
+ private final List<FederationModel> federationRegistrations = Collections
+ .synchronizedList(new ArrayList<FederationModel>());
+
+ private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
+
private RepositoryResolver<Void> repositoryResolver;
private File repositoriesFolder;
@@ -83,6 +104,8 @@ public class GitBlit implements ServletContextListener {
private IStoredSettings settings;
+ private MailExecutor mailExecutor;
+
public GitBlit() {
if (gitblit == null) {
// set the static singleton reference
@@ -103,6 +126,15 @@ public class GitBlit implements ServletContextListener {
}
/**
+ * Determine if this is the GO variant of Gitblit.
+ *
+ * @return true if this is the GO variant of Gitblit.
+ */
+ public static boolean isGO() {
+ return self().settings instanceof FileSettings;
+ }
+
+ /**
* Returns the boolean value for the specified key. If the key does not
* exist or the value for the key can not be interpreted as a boolean, the
* defaultValue is returned.
@@ -226,6 +258,30 @@ public class GitBlit implements ServletContextListener {
* @return a user object or null
*/
public UserModel authenticate(String username, char[] password) {
+ if (StringUtils.isEmpty(username)) {
+ // can not authenticate empty username
+ return null;
+ }
+ String pw = new String(password);
+ if (StringUtils.isEmpty(pw)) {
+ // can not authenticate empty password
+ return null;
+ }
+
+ // check to see if this is the federation user
+ if (canFederate()) {
+ if (username.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;
+ }
+ }
+ }
+
+ // delegate authentication to the user service
if (userService == null) {
return null;
}
@@ -463,6 +519,10 @@ public class GitBlit implements ServletContextListener {
model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
model.isFrozen = getConfig(config, "isFrozen", false);
model.showReadme = getConfig(config, "showReadme", false);
+ model.federationStrategy = FederationStrategy.fromName(getConfig(config,
+ "federationStrategy", null));
+ model.isFederated = getConfig(config, "isFederated", false);
+ model.origin = config.getString("remote", "origin", "url");
}
r.close();
return model;
@@ -614,26 +674,40 @@ public class GitBlit implements ServletContextListener {
// update settings
if (r != null) {
- StoredConfig config = JGitUtils.readConfig(r);
- config.setString("gitblit", null, "description", repository.description);
- config.setString("gitblit", null, "owner", repository.owner);
- config.setBoolean("gitblit", null, "useTickets", repository.useTickets);
- config.setBoolean("gitblit", null, "useDocs", repository.useDocs);
- config.setString("gitblit", null, "accessRestriction",
- repository.accessRestriction.name());
- config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
- config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
- config.setBoolean("gitblit", null, "showReadme", repository.showReadme);
- try {
- config.save();
- } catch (IOException e) {
- logger.error("Failed to save repository config!", e);
- }
+ updateConfiguration(r, repository);
r.close();
}
}
/**
+ * Updates the Gitblit configuration for the specified repository.
+ *
+ * @param r
+ * the Git repository
+ * @param repository
+ * the Gitblit repository model
+ */
+ public void updateConfiguration(Repository r, RepositoryModel repository) {
+ StoredConfig config = JGitUtils.readConfig(r);
+ config.setString("gitblit", null, "description", repository.description);
+ config.setString("gitblit", null, "owner", repository.owner);
+ config.setBoolean("gitblit", null, "useTickets", repository.useTickets);
+ config.setBoolean("gitblit", null, "useDocs", repository.useDocs);
+ config.setString("gitblit", null, "accessRestriction", repository.accessRestriction.name());
+ config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches);
+ config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen);
+ config.setBoolean("gitblit", null, "showReadme", repository.showReadme);
+ config.setString("gitblit", null, "federationStrategy",
+ repository.federationStrategy.name());
+ config.setBoolean("gitblit", null, "isFederated", repository.isFederated);
+ try {
+ config.save();
+ } catch (IOException e) {
+ logger.error("Failed to save repository config!", e);
+ }
+ }
+
+ /**
* Deletes the repository from the file system and removes the repository
* permission from all repository users.
*
@@ -711,6 +785,341 @@ public class GitBlit implements ServletContextListener {
}
/**
+ * Returns Gitblit's scheduled executor service for scheduling tasks.
+ *
+ * @return scheduledExecutor
+ */
+ public ScheduledExecutorService executor() {
+ return scheduledExecutor;
+ }
+
+ public static boolean canFederate() {
+ String uuid = getString(Keys.federation.uuid, "");
+ return !StringUtils.isEmpty(uuid);
+ }
+
+ /**
+ * Configures this Gitblit instance to pull any registered federated gitblit
+ * instances.
+ */
+ private void configureFederation() {
+ boolean validUuid = true;
+ String uuid = settings.getString(Keys.federation.uuid, "");
+ if (StringUtils.isEmpty(uuid)) {
+ logger.warn("Federation UUID is blank! This server can not be PULLED from.");
+ validUuid = false;
+ }
+ if (validUuid) {
+ for (FederationToken tokenType : FederationToken.values()) {
+ logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
+ getFederationToken(tokenType)));
+ }
+ }
+
+ // Schedule the federation executor
+ List<FederationModel> registrations = getFederationRegistrations();
+ if (registrations.size() > 0) {
+ scheduledExecutor.schedule(new FederationPullExecutor(registrations), 1,
+ TimeUnit.MINUTES);
+ }
+ }
+
+ /**
+ * Returns the list of federated gitblit instances that this instance will
+ * try to pull.
+ *
+ * @return list of registered gitblit instances
+ */
+ public List<FederationModel> getFederationRegistrations() {
+ if (federationRegistrations.isEmpty()) {
+ List<String> keys = settings.getAllKeys(Keys.federation._ROOT);
+ keys.remove(Keys.federation.name);
+ keys.remove(Keys.federation.uuid);
+ keys.remove(Keys.federation.allowProposals);
+ keys.remove(Keys.federation.proposalsFolder);
+ keys.remove(Keys.federation.defaultFrequency);
+ Collections.sort(keys);
+ Map<String, FederationModel> federatedModels = new HashMap<String, FederationModel>();
+ for (String key : keys) {
+ String value = key.substring(Keys.federation._ROOT.length() + 1);
+ List<String> values = StringUtils.getStringsFromValue(value, "\\.");
+ String server = values.get(0);
+ if (!federatedModels.containsKey(server)) {
+ federatedModels.put(server, new FederationModel(server));
+ }
+ String setting = values.get(1);
+ if (setting.equals("url")) {
+ // url of the remote Gitblit instance
+ federatedModels.get(server).url = settings.getString(key, "");
+ } else if (setting.equals("token")) {
+ // token for the remote Gitblit instance
+ federatedModels.get(server).token = settings.getString(key, "");
+ } else if (setting.equals("frequency")) {
+ // frequency of the pull operation
+ federatedModels.get(server).frequency = settings.getString(key, "");
+ } else if (setting.equals("folder")) {
+ // destination folder of the pull operation
+ federatedModels.get(server).folder = settings.getString(key, "");
+ } else if (setting.equals("mergeAccounts")) {
+ // merge remote accounts into local accounts
+ federatedModels.get(server).mergeAccounts = settings.getBoolean(key, false);
+ } else if (setting.equals("sendStatus")) {
+ // send a status acknowledgment to source Gitblit instance
+ // at end of git pull
+ federatedModels.get(server).sendStatus = settings.getBoolean(key, false);
+ } else if (setting.equals("notifyOnError")) {
+ // notify administrators on federation pull failures
+ federatedModels.get(server).notifyOnError = settings.getBoolean(key, false);
+ } else if (setting.equals("exclude")) {
+ // excluded repositories
+ federatedModels.get(server).exclusions = settings.getStrings(key);
+ } else if (setting.equals("include")) {
+ // included repositories
+ federatedModels.get(server).inclusions = settings.getStrings(key);
+ }
+ }
+
+ // verify that registrations have a url and a token
+ for (FederationModel model : federatedModels.values()) {
+ if (StringUtils.isEmpty(model.url)) {
+ logger.warn(MessageFormat.format(
+ "Dropping federation registration {0}. Missing url.", model.name));
+ continue;
+ }
+ if (StringUtils.isEmpty(model.token)) {
+ logger.warn(MessageFormat.format(
+ "Dropping federation registration {0}. Missing token.", model.name));
+ continue;
+ }
+ // set default frequency if unspecified
+ if (StringUtils.isEmpty(model.frequency)) {
+ model.frequency = settings.getString(Keys.federation.defaultFrequency,
+ "60 mins");
+ }
+ federationRegistrations.add(model);
+ }
+ }
+ return federationRegistrations;
+ }
+
+ /**
+ * Retrieve the specified federation registration.
+ *
+ * @param name
+ * the name of the registration
+ * @return a federation registration
+ */
+ public FederationModel getFederationRegistration(String url, String name) {
+ // check registrations
+ for (FederationModel r : getFederationRegistrations()) {
+ if (r.name.equals(name) && r.url.equals(url)) {
+ return r;
+ }
+ }
+
+ // check the results
+ for (FederationModel r : getFederationResultRegistrations()) {
+ if (r.name.equals(name) && r.url.equals(url)) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the list of possible federation tokens for this Gitblit instance.
+ *
+ * @return list of federation tokens
+ */
+ public List<String> getFederationTokens() {
+ List<String> tokens = new ArrayList<String>();
+ for (FederationToken type : FederationToken.values()) {
+ tokens.add(getFederationToken(type));
+ }
+ return tokens;
+ }
+
+ /**
+ * Returns the specified federation token for this Gitblit instance.
+ *
+ * @param type
+ * @return a federation token
+ */
+ public String getFederationToken(FederationToken type) {
+ String uuid = settings.getString(Keys.federation.uuid, "");
+ return StringUtils.getSHA1(uuid + "-" + type.name());
+ }
+
+ /**
+ * Compares the provided token with this Gitblit instance's tokens and
+ * determines if the requested permission may be granted to the token.
+ *
+ * @param req
+ * @param token
+ * @return true if the request can be executed
+ */
+ public boolean validateFederationRequest(FederationRequest req, String token) {
+ String all = getFederationToken(FederationToken.ALL);
+ String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
+ String jur = getFederationToken(FederationToken.REPOSITORIES);
+ switch (req) {
+ case PULL_REPOSITORIES:
+ return token.equals(all) || token.equals(unr) || token.equals(jur);
+ case PULL_USERS:
+ return token.equals(all) || token.equals(unr);
+ case PULL_SETTINGS:
+ return token.equals(all);
+ }
+ return false;
+ }
+
+ /**
+ * Acknowledge and cache the status of a remote Gitblit instance.
+ *
+ * @param identification
+ * the identification of the pulling Gitblit instance
+ * @param registration
+ * the registration from the pulling Gitblit instance
+ * @return true if acknowledged
+ */
+ public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
+ // reset the url to the identification of the pulling Gitblit instance
+ registration.url = identification;
+ String id = identification;
+ if (!StringUtils.isEmpty(registration.folder)) {
+ id += "-" + registration.folder;
+ }
+ federationPullResults.put(id, registration);
+ return true;
+ }
+
+ /**
+ * Returns the list of registration results.
+ *
+ * @return the list of registration results
+ */
+ public List<FederationModel> getFederationResultRegistrations() {
+ return new ArrayList<FederationModel>(federationPullResults.values());
+ }
+
+ /**
+ * Submit a federation proposal. The proposal is cached locally and the
+ * Gitblit administrator(s) are notified via email.
+ *
+ * @param proposal
+ * the proposal
+ * @param gitblitUrl
+ * the url of your gitblit instance
+ * @return true if the proposal was submitted
+ */
+ public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
+ // convert proposal to json
+ Gson gson = new GsonBuilder().setPrettyPrinting().create();
+ String json = gson.toJson(proposal);
+
+ try {
+ // make the proposals folder
+ File proposalsFolder = new File(getString(Keys.federation.proposalsFolder, "proposals")
+ .trim());
+ proposalsFolder.mkdirs();
+
+ // cache json to a file
+ File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
+ com.gitblit.utils.FileUtils.writeContent(file, json);
+ } catch (Exception e) {
+ logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
+ }
+
+ // send an email, if possible
+ try {
+ Message message = mailExecutor.createMessageForAdministrators();
+ if (message != null) {
+ message.setSubject("Federation proposal from " + proposal.url);
+ message.setText("Please review the proposal @ " + gitblitUrl + "/proposal/"
+ + proposal.token);
+ mailExecutor.queue(message);
+ }
+ } catch (Throwable t) {
+ logger.error("Failed to notify administrators of proposal", t);
+ }
+ return true;
+ }
+
+ /**
+ * Returns the list of pending federation proposals
+ *
+ * @return list of federation proposals
+ */
+ public List<FederationProposal> getPendingFederationProposals() {
+ List<FederationProposal> list = new ArrayList<FederationProposal>();
+ File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
+ if (folder.exists()) {
+ File[] files = folder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.isFile()
+ && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
+ }
+ });
+ Gson gson = new Gson();
+ for (File file : files) {
+ String json = com.gitblit.utils.FileUtils.readContent(file, null);
+ FederationProposal proposal = gson.fromJson(json, FederationProposal.class);
+ list.add(proposal);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Returns the proposal identified by the supplied token.
+ *
+ * @param token
+ * @return the specified proposal or null
+ */
+ public FederationProposal getPendingFederationProposal(String token) {
+ List<FederationProposal> list = getPendingFederationProposals();
+ for (FederationProposal proposal : list) {
+ if (proposal.token.equals(token)) {
+ return proposal;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Deletes a pending federation proposal.
+ *
+ * @param a
+ * proposal
+ * @return true if the proposal was deleted
+ */
+ public boolean deletePendingFederationProposal(FederationProposal proposal) {
+ File folder = new File(getString(Keys.federation.proposalsFolder, "proposals").trim());
+ File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
+ return file.delete();
+ }
+
+ /**
+ * Notify the administrators by email.
+ *
+ * @param subject
+ * @param message
+ */
+ public void notifyAdministrators(String subject, String message) {
+ try {
+ Message mail = mailExecutor.createMessageForAdministrators();
+ if (mail != null) {
+ mail.setSubject(subject);
+ mail.setText(message);
+ mailExecutor.queue(mail);
+ }
+ } catch (MessagingException e) {
+ logger.error("Messaging error", e);
+ }
+ }
+
+ /**
* Configure the Gitblit singleton with the specified settings source. This
* source may be file settings (Gitblit GO) or may be web.xml settings
* (Gitblit WAR).
@@ -746,6 +1155,13 @@ public class GitBlit implements ServletContextListener {
loginService = new FileUserService(realmFile);
}
setUserService(loginService);
+ configureFederation();
+ mailExecutor = new MailExecutor(settings);
+ if (mailExecutor.isReady()) {
+ scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
+ } else {
+ logger.warn("Mail server is not properly configured. Mail services disabled.");
+ }
}
/**
@@ -770,5 +1186,6 @@ public class GitBlit implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent contextEvent) {
logger.info("Gitblit context destroyed by servlet container.");
+ scheduledExecutor.shutdownNow();
}
}