@@ -115,7 +115,7 @@ public class Constants { | |||
* Enumeration representing the types of federation requests. | |||
*/ | |||
public static enum FederationRequest { | |||
PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_SETTINGS, STATUS; | |||
POKE, PROPOSAL, PULL_REPOSITORIES, PULL_USERS, PULL_SETTINGS, STATUS; | |||
public static FederationRequest fromName(String name) { | |||
for (FederationRequest type : values()) { | |||
@@ -181,4 +181,16 @@ public class Constants { | |||
} | |||
} | |||
/** | |||
* Enumeration representing the possible results of federation proposal | |||
* requests. | |||
*/ | |||
public static enum FederationProposalResult { | |||
ERROR, FEDERATION_DISABLED, MISSING_DATA, NO_PROPOSALS, NO_POKE, ACCEPTED; | |||
@Override | |||
public String toString() { | |||
return name(); | |||
} | |||
} | |||
} |
@@ -35,6 +35,7 @@ import com.gitblit.models.FederationModel; | |||
import com.gitblit.models.FederationProposal; | |||
import com.gitblit.models.RepositoryModel; | |||
import com.gitblit.models.UserModel; | |||
import com.gitblit.utils.FederationUtils; | |||
import com.gitblit.utils.HttpUtils; | |||
import com.gitblit.utils.StringUtils; | |||
import com.gitblit.utils.TimeUtils; | |||
@@ -110,6 +111,16 @@ public class FederationServlet extends HttpServlet { | |||
private void processRequest(javax.servlet.http.HttpServletRequest request, | |||
javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, | |||
java.io.IOException { | |||
FederationRequest reqType = FederationRequest.fromName(request.getParameter("req")); | |||
logger.info(MessageFormat.format("Federation {0} request from {1}", reqType, | |||
request.getRemoteAddr())); | |||
if (FederationRequest.POKE.equals(reqType)) { | |||
// Gitblit always responds to POKE requests to verify a connection | |||
logger.info("Received federation POKE from " + request.getRemoteAddr()); | |||
return; | |||
} | |||
if (!GitBlit.getBoolean(Keys.git.enableGitServlet, true)) { | |||
logger.warn(Keys.git.enableGitServlet + " must be set TRUE for federation requests."); | |||
response.sendError(HttpServletResponse.SC_FORBIDDEN); | |||
@@ -124,11 +135,6 @@ public class FederationServlet extends HttpServlet { | |||
return; | |||
} | |||
String token = request.getParameter("token"); | |||
FederationRequest reqType = FederationRequest.fromName(request.getParameter("req")); | |||
logger.info(MessageFormat.format("Federation {0} request from {1}", reqType, | |||
request.getRemoteAddr())); | |||
if (FederationRequest.PROPOSAL.equals(reqType)) { | |||
// Receive a gitblit federation proposal | |||
BufferedReader reader = request.getReader(); | |||
@@ -159,6 +165,20 @@ public class FederationServlet extends HttpServlet { | |||
return; | |||
} | |||
// poke the origin Gitblit instance that is proposing federation | |||
boolean poked = false; | |||
try { | |||
poked = FederationUtils.poke(proposal.url); | |||
} catch (Exception e) { | |||
logger.error("Failed to poke origin", e); | |||
} | |||
if (!poked) { | |||
logger.error(MessageFormat.format("Failed to send federation poke to {0}", | |||
proposal.url)); | |||
response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE); | |||
return; | |||
} | |||
String url = HttpUtils.getGitblitURL(request); | |||
GitBlit.self().submitFederationProposal(proposal, url); | |||
logger.info(MessageFormat.format( | |||
@@ -207,6 +227,7 @@ public class FederationServlet extends HttpServlet { | |||
} | |||
// Determine the federation tokens for this gitblit instance | |||
String token = request.getParameter("token"); | |||
List<String> tokens = GitBlit.self().getFederationTokens(); | |||
if (!tokens.contains(token)) { | |||
logger.warn(MessageFormat.format( |
@@ -971,7 +971,8 @@ public class GitBlit implements ServletContextListener { | |||
* @param proposal | |||
* the proposal | |||
* @param gitblitUrl | |||
* the url of your gitblit instance | |||
* the url of your gitblit instance to send an email to | |||
* administrators | |||
* @return true if the proposal was submitted | |||
*/ | |||
public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) { |
@@ -45,6 +45,7 @@ import javax.servlet.http.HttpServletResponse; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import com.gitblit.Constants.FederationProposalResult; | |||
import com.gitblit.Constants.FederationRequest; | |||
import com.gitblit.FederationServlet; | |||
import com.gitblit.IStoredSettings; | |||
@@ -82,7 +83,7 @@ public class FederationUtils { | |||
private static final SSLContext SSL_CONTEXT; | |||
private static final DummyHostnameVerifier HOSTNAME_VERIFIER; | |||
private static final Logger LOGGER = LoggerFactory.getLogger(FederationUtils.class); | |||
static { | |||
@@ -181,6 +182,26 @@ public class FederationUtils { | |||
return federationRegistrations; | |||
} | |||
/** | |||
* Sends a federation poke to the Gitblit instance at remoteUrl. Pokes are | |||
* sent by an pulling Gitblit instance to an origin Gitblit instance as part | |||
* of the proposal process. This is to ensure that the pulling Gitblit | |||
* instance has an IP route to the origin instance. | |||
* | |||
* @param remoteUrl | |||
* the remote Gitblit instance to send a federation proposal to | |||
* @param proposal | |||
* a complete federation proposal | |||
* @return true if there is a route to the remoteUrl | |||
*/ | |||
public static boolean poke(String remoteUrl) throws Exception { | |||
String url = FederationServlet.asFederationLink(remoteUrl, null, FederationRequest.POKE); | |||
Gson gson = new Gson(); | |||
String json = gson.toJson("POKE"); | |||
int status = writeJson(url, json); | |||
return status == HttpServletResponse.SC_OK; | |||
} | |||
/** | |||
* Sends a federation proposal to the Gitblit instance at remoteUrl | |||
* | |||
@@ -188,15 +209,34 @@ public class FederationUtils { | |||
* the remote Gitblit instance to send a federation proposal to | |||
* @param proposal | |||
* a complete federation proposal | |||
* @return true if the proposal was received | |||
* @return the federation proposal result code | |||
*/ | |||
public static boolean propose(String remoteUrl, FederationProposal proposal) throws Exception { | |||
public static FederationProposalResult propose(String remoteUrl, FederationProposal proposal) | |||
throws Exception { | |||
String url = FederationServlet | |||
.asFederationLink(remoteUrl, null, FederationRequest.PROPOSAL); | |||
Gson gson = new GsonBuilder().setPrettyPrinting().create(); | |||
String json = gson.toJson(proposal); | |||
int status = writeJson(url, json); | |||
return status == HttpServletResponse.SC_OK; | |||
switch (status) { | |||
case HttpServletResponse.SC_FORBIDDEN: | |||
// remote Gitblit Federation disabled | |||
return FederationProposalResult.FEDERATION_DISABLED; | |||
case HttpServletResponse.SC_BAD_REQUEST: | |||
// remote Gitblit did not receive any JSON data | |||
return FederationProposalResult.MISSING_DATA; | |||
case HttpServletResponse.SC_METHOD_NOT_ALLOWED: | |||
// remote Gitblit not accepting proposals | |||
return FederationProposalResult.NO_PROPOSALS; | |||
case HttpServletResponse.SC_NOT_ACCEPTABLE: | |||
// remote Gitblit failed to poke this Gitblit instance | |||
return FederationProposalResult.NO_POKE; | |||
case HttpServletResponse.SC_OK: | |||
// received | |||
return FederationProposalResult.ACCEPTED; | |||
default: | |||
return FederationProposalResult.ERROR; | |||
} | |||
} | |||
/** |
@@ -26,6 +26,7 @@ import org.apache.wicket.markup.html.form.Form; | |||
import org.apache.wicket.markup.html.form.TextField; | |||
import org.apache.wicket.model.CompoundPropertyModel; | |||
import com.gitblit.Constants.FederationProposalResult; | |||
import com.gitblit.GitBlit; | |||
import com.gitblit.models.FederationProposal; | |||
import com.gitblit.models.RepositoryModel; | |||
@@ -78,17 +79,44 @@ public class SendProposalPage extends BasePage { | |||
error("Please enter a destination url for your proposal!"); | |||
return; | |||
} | |||
// build new proposal | |||
FederationProposal proposal = GitBlit.self().createFederationProposal(myUrl, token); | |||
proposal.url = myUrl; | |||
proposal.message = message; | |||
try { | |||
if (FederationUtils.propose(destinationUrl, proposal)) { | |||
info(MessageFormat.format("Proposal successfully received by {0}.", destinationUrl)); | |||
FederationProposalResult res = FederationUtils | |||
.propose(destinationUrl, proposal); | |||
switch (res) { | |||
case ACCEPTED: | |||
info(MessageFormat.format("Proposal successfully received by {0}.", | |||
destinationUrl)); | |||
setResponsePage(RepositoriesPage.class); | |||
} else { | |||
error(MessageFormat.format("Sorry, {0} rejected your proposal.", destinationUrl)); | |||
break; | |||
case NO_POKE: | |||
error(MessageFormat.format( | |||
"Sorry, {0} could not find a Gitblit instance at {1}.", | |||
destinationUrl, myUrl)); | |||
break; | |||
case NO_PROPOSALS: | |||
error(MessageFormat.format( | |||
"Sorry, {0} is not accepting proposals at this time.", | |||
destinationUrl)); | |||
break; | |||
case FEDERATION_DISABLED: | |||
error(MessageFormat | |||
.format("Sorry, {0} is not configured to federate with any Gitblit instances.", | |||
destinationUrl)); | |||
break; | |||
case MISSING_DATA: | |||
error(MessageFormat.format("Sorry, {0} did not receive any proposal data!", | |||
destinationUrl)); | |||
break; | |||
case ERROR: | |||
error(MessageFormat.format( | |||
"Sorry, {0} reports that an unexpected error occurred!", | |||
destinationUrl)); | |||
break; | |||
} | |||
} catch (Exception e) { | |||
if (!StringUtils.isEmpty(e.getMessage())) { |
@@ -24,6 +24,7 @@ import java.util.concurrent.Executors; | |||
import junit.framework.TestCase; | |||
import com.gitblit.Constants.AccessRestrictionType; | |||
import com.gitblit.Constants.FederationProposalResult; | |||
import com.gitblit.Constants.FederationRequest; | |||
import com.gitblit.Constants.FederationToken; | |||
import com.gitblit.FederationServlet; | |||
@@ -91,14 +92,15 @@ public class FederationTests extends TestCase { | |||
"testtoken", repositories); | |||
// propose federation | |||
assertTrue("proposal refused", | |||
FederationUtils.propose("http://localhost:" + port, proposal)); | |||
assertEquals("proposal refused", | |||
FederationUtils.propose("http://localhost:" + port, proposal), | |||
FederationProposalResult.NO_PROPOSALS); | |||
} | |||
public void testPullRepositories() throws Exception { | |||
try { | |||
String url = FederationServlet.asFederationLink("http://localhost:" + port, "testtoken", | |||
FederationRequest.PULL_REPOSITORIES); | |||
String url = FederationServlet.asFederationLink("http://localhost:" + port, | |||
"testtoken", FederationRequest.PULL_REPOSITORIES); | |||
String json = FederationUtils.readJson(url); | |||
} catch (IOException e) { | |||
if (!e.getMessage().contains("403")) { |