From dd9ae71bc1cb13b90dcc8d9689550eb7dfe7d035 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Sun, 25 Sep 2011 09:45:45 -0400
Subject: [PATCH] Revised federation proposal mechanism. Added SendProposalPage.

---
 src/com/gitblit/wicket/pages/FederationRegistrationPage.html |    3 
 src/com/gitblit/FederationServlet.java                       |   88 ++--------
 src/com/gitblit/wicket/panels/FederationTokensPanel.java     |   24 --
 src/com/gitblit/models/FederationProposal.java               |    5 
 src/com/gitblit/wicket/pages/FederationRegistrationPage.java |    7 
 src/com/gitblit/wicket/pages/ReviewProposalPage.java         |    7 
 src/com/gitblit/wicket/pages/SendProposalPage.java           |  127 +++++++++++++++
 src/com/gitblit/wicket/panels/FederationProposalsPanel.java  |    6 
 src/com/gitblit/utils/FederationUtils.java                   |   28 +--
 src/com/gitblit/GitBlit.java                                 |   80 ++++++++++
 src/com/gitblit/wicket/GitBlitWebApp.properties              |    6 
 src/com/gitblit/wicket/pages/SendProposalPage.html           |   30 +++
 src/com/gitblit/wicket/pages/ReviewProposalPage.html         |    7 
 tests/com/gitblit/tests/FederationTests.java                 |   10 
 src/com/gitblit/wicket/GitBlitWebApp.java                    |    4 
 15 files changed, 305 insertions(+), 127 deletions(-)

diff --git a/src/com/gitblit/FederationServlet.java b/src/com/gitblit/FederationServlet.java
index a84e90c..708ca92 100644
--- a/src/com/gitblit/FederationServlet.java
+++ b/src/com/gitblit/FederationServlet.java
@@ -35,7 +35,6 @@
 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;
@@ -68,7 +67,7 @@
 	 * @param req
 	 *            the pull type request
 	 */
-	public static String asPullLink(String sourceURL, String token, FederationRequest req) {
+	public static String asFederationLink(String sourceURL, String token, FederationRequest req) {
 		return asFederationLink(sourceURL, null, token, req, null);
 	}
 
@@ -95,7 +94,7 @@
 			req = FederationRequest.PULL_REPOSITORIES;
 		}
 		return remoteURL + Constants.FEDERATION_PATH + "?req=" + req.name().toLowerCase()
-				+ "&token=" + token
+				+ (token == null ? "" : ("&token=" + token))
 				+ (tokenType == null ? "" : ("&tokenType=" + tokenType.name().toLowerCase()))
 				+ (myURL == null ? "" : ("&url=" + StringUtils.encodeURL(myURL)));
 	}
@@ -132,16 +131,6 @@
 
 		if (FederationRequest.PROPOSAL.equals(reqType)) {
 			// Receive a gitblit federation proposal
-			String url = StringUtils.decodeFromHtml(request.getParameter("url"));
-			FederationToken tokenType = FederationToken.fromName(request.getParameter("tokenType"));
-
-			if (!GitBlit.getBoolean(Keys.federation.allowProposals, false)) {
-				logger.error(MessageFormat.format("Rejected {0} federation proposal from {1}",
-						tokenType.name(), url));
-				response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
-				return;
-			}
-
 			BufferedReader reader = request.getReader();
 			StringBuilder json = new StringBuilder();
 			String line = null;
@@ -150,28 +139,32 @@
 			}
 			reader.close();
 
-			// check to see if we have repository data
+			// check to see if we have proposal data
 			if (json.length() == 0) {
-				logger.error(MessageFormat.format(
-						"Failed to receive proposed repositories list from {0}", url));
+				logger.error(MessageFormat.format("Failed to receive proposal data from {0}",
+						request.getRemoteAddr()));
 				response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
 				return;
 			}
 
-			// deserialize the repository data
+			// deserialize the proposal
 			Gson gson = new Gson();
-			Map<String, RepositoryModel> repositories = gson.fromJson(json.toString(),
-					FederationUtils.REPOSITORIES_TYPE);
+			FederationProposal proposal = gson.fromJson(json.toString(), FederationProposal.class);
 
-			// submit a proposal
-			FederationProposal proposal = new FederationProposal(url, tokenType, token,
-					repositories);
+			// reject proposal, if not receipt prohibited
+			if (!GitBlit.getBoolean(Keys.federation.allowProposals, false)) {
+				logger.error(MessageFormat.format("Rejected {0} federation proposal from {1}",
+						proposal.tokenType.name(), proposal.url));
+				response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+				return;
+			}
+
 			String hosturl = HttpUtils.getHostURL(request);
 			String gitblitUrl = hosturl + request.getContextPath();
 			GitBlit.self().submitFederationProposal(proposal, gitblitUrl);
 			logger.info(MessageFormat.format(
 					"Submitted {0} federation proposal to pull {1} repositories from {2}",
-					tokenType.name(), repositories.size(), url));
+					proposal.tokenType.name(), proposal.repositories.size(), proposal.url));
 			response.setStatus(HttpServletResponse.SC_OK);
 			return;
 		}
@@ -225,53 +218,8 @@
 
 		Object result = null;
 		if (FederationRequest.PULL_REPOSITORIES.equals(reqType)) {
-			// build a reverse-lookup for token->federation set name
-			Map<String, String> federationSets = new HashMap<String, String>();
-			for (String set : GitBlit.getStrings(Keys.federation.sets)) {
-				federationSets.put(GitBlit.self().getFederationToken(set), set);
-			}
-
-			// Determine the Gitblit clone url
-			StringBuilder sb = new StringBuilder();
-			sb.append(HttpUtils.getHostURL(request));
-			sb.append(Constants.GIT_PATH);
-			sb.append("{0}");
-			String cloneUrl = sb.toString();
-
-			// Retrieve all available repositories
-			UserModel user = new UserModel(Constants.FEDERATION_USER);
-			user.canAdmin = true;
-			List<RepositoryModel> list = GitBlit.self().getRepositoryModels(user);
-
-			// create the [cloneurl, repositoryModel] map
-			Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
-			for (RepositoryModel model : list) {
-				// by default, setup the url for THIS repository
-				String url = MessageFormat.format(cloneUrl, model.name);
-				switch (model.federationStrategy) {
-				case EXCLUDE:
-					// skip this repository
-					continue;
-				case FEDERATE_ORIGIN:
-					// federate the origin, if it is defined
-					if (!StringUtils.isEmpty(model.origin)) {
-						url = model.origin;
-					}
-					break;
-				}
-
-				if (federationSets.containsKey(token)) {
-					// include repositories only for federation set
-					String set = federationSets.get(token);
-					if (model.federationSets.contains(set)) {
-						repositories.put(url, model);
-					}
-				} else {
-					// standard federation token for ALL
-					repositories.put(url, model);
-				}
-			}
-			result = repositories;
+			String gitblitUrl = HttpUtils.getHostURL(request);
+			result = GitBlit.self().getRepositories(gitblitUrl, token);
 		} else {
 			if (FederationRequest.PULL_SETTINGS.equals(reqType)) {
 				// pull settings
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 3e974b3..f3ad363 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -1102,6 +1102,86 @@
 	}
 
 	/**
+	 * Get repositories for the specified token.
+	 * 
+	 * @param gitblitUrl
+	 *            the base url of this gitblit instance
+	 * @param token
+	 *            the federation token
+	 * @return a map of <cloneurl, RepositoryModel>
+	 */
+	public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
+		Map<String, String> federationSets = new HashMap<String, String>();
+		for (String set : getStrings(Keys.federation.sets)) {
+			federationSets.put(getFederationToken(set), set);
+		}
+
+		// Determine the Gitblit clone url
+		StringBuilder sb = new StringBuilder();
+		sb.append(gitblitUrl);
+		sb.append(Constants.GIT_PATH);
+		sb.append("{0}");
+		String cloneUrl = sb.toString();
+
+		// Retrieve all available repositories
+		UserModel user = new UserModel(Constants.FEDERATION_USER);
+		user.canAdmin = true;
+		List<RepositoryModel> list = getRepositoryModels(user);
+
+		// create the [cloneurl, repositoryModel] map
+		Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
+		for (RepositoryModel model : list) {
+			// by default, setup the url for THIS repository
+			String url = MessageFormat.format(cloneUrl, model.name);
+			switch (model.federationStrategy) {
+			case EXCLUDE:
+				// skip this repository
+				continue;
+			case FEDERATE_ORIGIN:
+				// federate the origin, if it is defined
+				if (!StringUtils.isEmpty(model.origin)) {
+					url = model.origin;
+				}
+				break;
+			}
+
+			if (federationSets.containsKey(token)) {
+				// include repositories only for federation set
+				String set = federationSets.get(token);
+				if (model.federationSets.contains(set)) {
+					repositories.put(url, model);
+				}
+			} else {
+				// standard federation token for ALL
+				repositories.put(url, model);
+			}
+		}
+		return repositories;
+	}
+
+	/**
+	 * Creates a proposal from the token.
+	 * 
+	 * @param gitblitUrl
+	 *            the url of this Gitblit instance
+	 * @param token
+	 * @return a potential proposal
+	 */
+	public FederationProposal createFederationProposal(String gitblitUrl, String token) {
+		FederationToken tokenType = FederationToken.REPOSITORIES;
+		for (FederationToken type : FederationToken.values()) {
+			if (token.equals(getFederationToken(type))) {
+				tokenType = type;
+				break;
+			}
+		}
+		Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
+		FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
+				repositories);
+		return proposal;
+	}
+
+	/**
 	 * Returns the proposal identified by the supplied token.
 	 * 
 	 * @param token
diff --git a/src/com/gitblit/models/FederationProposal.java b/src/com/gitblit/models/FederationProposal.java
index 03b31ce..5cf9182 100644
--- a/src/com/gitblit/models/FederationProposal.java
+++ b/src/com/gitblit/models/FederationProposal.java
@@ -37,6 +37,8 @@
 	public FederationToken tokenType;
 
 	public String token;
+	
+	public String message;
 
 	public Map<String, RepositoryModel> repositories;
 
@@ -59,6 +61,7 @@
 		this.url = url;
 		this.tokenType = tokenType;
 		this.token = token;
+		this.message = "";
 		this.repositories = repositories;
 		try {
 			// determine server name and set that as the proposal name
@@ -66,7 +69,7 @@
 			if (name.contains("/")) {
 				name = name.substring(0, name.indexOf('/'));
 			}
-			name = name.replace(".", "");
+			name = name.replace(".", "").replace(";", "").replace(":", "").replace("-", "");
 		} catch (Exception e) {
 			name = Long.toHexString(System.currentTimeMillis());
 		}
diff --git a/src/com/gitblit/utils/FederationUtils.java b/src/com/gitblit/utils/FederationUtils.java
index 129fe42..4cb6881 100644
--- a/src/com/gitblit/utils/FederationUtils.java
+++ b/src/com/gitblit/utils/FederationUtils.java
@@ -27,7 +27,6 @@
 import java.security.cert.CertificateException;
 import java.security.cert.X509Certificate;
 import java.util.Collection;
-import java.util.Date;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -41,9 +40,9 @@
 import javax.servlet.http.HttpServletResponse;
 
 import com.gitblit.Constants.FederationRequest;
-import com.gitblit.Constants.FederationToken;
 import com.gitblit.FederationServlet;
 import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.google.gson.Gson;
@@ -94,22 +93,15 @@
 	 * 
 	 * @param remoteUrl
 	 *            the remote Gitblit instance to send a federation proposal to
-	 * @param tokenType
-	 *            type of the provided federation token
-	 * @param myToken
-	 *            my federation token
-	 * @param myUrl
-	 *            my Gitblit url
-	 * @param myRepositories
-	 *            the repositories I want to share keyed by their clone url
+	 * @param proposal
+	 *            a complete federation proposal
 	 * @return true if the proposal was received
 	 */
-	public static boolean propose(String remoteUrl, FederationToken tokenType, String myToken,
-			String myUrl, Map<String, RepositoryModel> myRepositories) throws Exception {
-		String url = FederationServlet.asFederationLink(remoteUrl, tokenType, myToken,
-				FederationRequest.PROPOSAL, myUrl);
+	public static boolean 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(myRepositories);
+		String json = gson.toJson(proposal);
 		int status = writeJson(url, json);
 		return status == HttpServletResponse.SC_OK;
 	}
@@ -126,7 +118,7 @@
 	 */
 	public static Map<String, RepositoryModel> getRepositories(FederationModel registration,
 			boolean checkExclusions) throws Exception {
-		String url = FederationServlet.asPullLink(registration.url, registration.token,
+		String url = FederationServlet.asFederationLink(registration.url, registration.token,
 				FederationRequest.PULL_REPOSITORIES);
 		Map<String, RepositoryModel> models = readGson(url, REPOSITORIES_TYPE);
 		if (checkExclusions) {
@@ -149,7 +141,7 @@
 	 * @throws Exception
 	 */
 	public static Collection<UserModel> getUsers(FederationModel registration) throws Exception {
-		String url = FederationServlet.asPullLink(registration.url, registration.token,
+		String url = FederationServlet.asFederationLink(registration.url, registration.token,
 				FederationRequest.PULL_USERS);
 		Collection<UserModel> models = readGson(url, USERS_TYPE);
 		return models;
@@ -164,7 +156,7 @@
 	 * @throws Exception
 	 */
 	public static Map<String, String> getSettings(FederationModel registration) throws Exception {
-		String url = FederationServlet.asPullLink(registration.url, registration.token,
+		String url = FederationServlet.asFederationLink(registration.url, registration.token,
 				FederationRequest.PULL_SETTINGS);
 		Map<String, String> settings = readGson(url, SETTINGS_TYPE);
 		return settings;
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.java b/src/com/gitblit/wicket/GitBlitWebApp.java
index 3e7c209..8c41df0 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.java
+++ b/src/com/gitblit/wicket/GitBlitWebApp.java
@@ -32,7 +32,7 @@
 import com.gitblit.wicket.pages.CommitDiffPage;
 import com.gitblit.wicket.pages.CommitPage;
 import com.gitblit.wicket.pages.DocsPage;
-import com.gitblit.wicket.pages.FederationProposalPage;
+import com.gitblit.wicket.pages.ReviewProposalPage;
 import com.gitblit.wicket.pages.FederationRegistrationPage;
 import com.gitblit.wicket.pages.HistoryPage;
 import com.gitblit.wicket.pages.LogPage;
@@ -98,7 +98,7 @@
 		mount("/markdown", MarkdownPage.class, "r", "h", "f");
 
 		// federation urls
-		mount("/proposal", FederationProposalPage.class, "t");
+		mount("/proposal", ReviewProposalPage.class, "t");
 		mount("/registration", FederationRegistrationPage.class, "u", "n");
 
 		// setup login/logout urls, if we are using authentication
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index 72d9d36..f279612 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -134,4 +134,8 @@
 gb.federationStrategy = federation strategy
 gb.federationRegistration = federation registration
 gb.federationResults = federation pull results
-gb.federationSets = federation sets
\ No newline at end of file
+gb.federationSets = federation sets
+gb.message = message
+gb.myUrlDescription = the publicly accessible url for your Gitblit instance
+gb.destinationUrl = send to
+gb.destinationUrlDescription = the url of the Gitblit instance to send your proposal
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/FederationRegistrationPage.html b/src/com/gitblit/wicket/pages/FederationRegistrationPage.html
index fe8f7f2..c7c5bde 100644
--- a/src/com/gitblit/wicket/pages/FederationRegistrationPage.html
+++ b/src/com/gitblit/wicket/pages/FederationRegistrationPage.html
@@ -13,8 +13,7 @@
 
 	<!-- registration info -->
 	<table class="plain">
-		<tr><th><wicket:message key="gb.url">url</wicket:message></th><td><span wicket:id="url">[url]</span></td></tr>
-		<tr><th></th><td><img style="border:0px;vertical-align:middle;" wicket:id="typeIcon" /> <span wicket:id="typeName">[url]</span></td></tr>
+		<tr><th><wicket:message key="gb.url">url</wicket:message></th><td><img style="border:0px;vertical-align:middle;" wicket:id="typeIcon" /> <span wicket:id="url">[url]</span></td></tr>
 		<tr><th><wicket:message key="gb.token">token</wicket:message></th><td><span class="sha1" wicket:id="token">[token]</span></td></tr>
 		<tr><th><wicket:message key="gb.folder">folder</wicket:message></th><td><span wicket:id="folder">[folder]</span></td></tr>
 		<tr><th><wicket:message key="gb.frequency">frequency</wicket:message></th><td><span wicket:id="frequency">[frequency]</span></td></tr>
diff --git a/src/com/gitblit/wicket/pages/FederationRegistrationPage.java b/src/com/gitblit/wicket/pages/FederationRegistrationPage.java
index 65c4d01..00cc2eb 100644
--- a/src/com/gitblit/wicket/pages/FederationRegistrationPage.java
+++ b/src/com/gitblit/wicket/pages/FederationRegistrationPage.java
@@ -36,8 +36,6 @@
 	public FederationRegistrationPage(PageParameters params) {
 		super(params);
 
-		setupPage("", getString("gb.registrations"));
-
 		final boolean showAdmin;
 		if (GitBlit.getBoolean(Keys.web.authenticateAdminPages, true)) {
 			boolean allowAdmin = GitBlit.getBoolean(Keys.web.allowAdministration, false);
@@ -55,10 +53,11 @@
 			error("Could not find federation registration!", true);
 		}
 
+		setupPage("", registration.isResultData() ? getString("gb.federationResults")
+				: getString("gb.federationRegistration"));
+
 		add(new Label("url", registration.url));
 		add(WicketUtils.getRegistrationImage("typeIcon", registration, this));
-		add(new Label("typeName", registration.isResultData() ? getString("gb.federationResults")
-				: getString("gb.federationRegistration")));
 		add(new Label("frequency", registration.frequency));
 		add(new Label("folder", registration.folder));
 		add(new Label("token", showAdmin ? registration.token : "--"));
diff --git a/src/com/gitblit/wicket/pages/FederationProposalPage.html b/src/com/gitblit/wicket/pages/ReviewProposalPage.html
similarity index 90%
rename from src/com/gitblit/wicket/pages/FederationProposalPage.html
rename to src/com/gitblit/wicket/pages/ReviewProposalPage.html
index 160ca3f..d4244ea 100644
--- a/src/com/gitblit/wicket/pages/FederationProposalPage.html
+++ b/src/com/gitblit/wicket/pages/ReviewProposalPage.html
@@ -13,10 +13,11 @@
 
 	<!-- proposal info -->
 	<table class="plain">
-		<tr><th><wicket:message key="gb.url">url</wicket:message></th><td><span wicket:id="url">[url]</span></td></tr>
-		<tr><th><wicket:message key="gb.token">token</wicket:message></th><td><span class="sha1" wicket:id="token">[token]</span></td></tr>
-		<tr><th><wicket:message key="gb.type">type</wicket:message></th><td><span wicket:id="tokenType">[token type]</span></td></tr>
 		<tr><th><wicket:message key="gb.received">received</wicket:message></th><td><span wicket:id="received">[received]</span></td></tr>
+		<tr><th><wicket:message key="gb.url">url</wicket:message></th><td><span wicket:id="url">[url]</span></td></tr>
+		<tr><th><wicket:message key="gb.message">message</wicket:message></th><td><span wicket:id="message">[message]</span></td></tr>
+		<tr><th><wicket:message key="gb.type">type</wicket:message></th><td><span wicket:id="tokenType">[token type]</span></td></tr>
+		<tr><th><wicket:message key="gb.token">token</wicket:message></th><td><span class="sha1" wicket:id="token">[token]</span></td></tr>
 		<tr><th valign="top"><wicket:message key="gb.proposal">proposal</wicket:message></th><td><span class="sha1" wicket:id="definition">[definition]</span></td></tr>
 	</table>
 		
diff --git a/src/com/gitblit/wicket/pages/FederationProposalPage.java b/src/com/gitblit/wicket/pages/ReviewProposalPage.java
similarity index 95%
rename from src/com/gitblit/wicket/pages/FederationProposalPage.java
rename to src/com/gitblit/wicket/pages/ReviewProposalPage.java
index c4b1347..0fc1de3 100644
--- a/src/com/gitblit/wicket/pages/FederationProposalPage.java
+++ b/src/com/gitblit/wicket/pages/ReviewProposalPage.java
@@ -33,13 +33,13 @@
 import com.gitblit.wicket.panels.RepositoriesPanel;
 
 @RequiresAdminRole
-public class FederationProposalPage extends BasePage {
+public class ReviewProposalPage extends BasePage {
 
 	private final String PROPS_PATTERN = "{0} = {1}\n";
 
 	private final String WEBXML_PATTERN = "\n<context-param>\n\t<param-name>{0}</param-name>\n\t<param-value>{1}</param-value>\n</context-param>\n";
 
-	public FederationProposalPage(PageParameters params) {
+	public ReviewProposalPage(PageParameters params) {
 		super(params);
 
 		setupPage("", getString("gb.proposals"));
@@ -53,9 +53,10 @@
 		}
 
 		add(new Label("url", proposal.url));
+		add(new Label("message", proposal.message));
 		add(WicketUtils.createTimestampLabel("received", proposal.received, getTimeZone()));
-		add(new Label("tokenType", proposal.tokenType.name()));
 		add(new Label("token", proposal.token));
+		add(new Label("tokenType", proposal.tokenType.name()));
 
 		boolean go = true;
 		String p;
diff --git a/src/com/gitblit/wicket/pages/SendProposalPage.html b/src/com/gitblit/wicket/pages/SendProposalPage.html
new file mode 100644
index 0000000..90fe0a2
--- /dev/null
+++ b/src/com/gitblit/wicket/pages/SendProposalPage.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<wicket:extend>
+<body onload="document.getElementById('myUrl').focus();">
+
+
+	<div style="padding-top:20px"></div>
+
+	<div style="text-align:center;" wicket:id="feedback">[Feedback Panel]</div>	
+
+	<!-- proposal info -->
+	<form wicket:id="editForm">
+	<table class="plain">
+		<tr><th><wicket:message key="gb.url">url</wicket:message></th><td class="edit"><input type="text" wicket:id="myUrl" id="myUrl" size="60" />  &nbsp;<i><wicket:message key="gb.myUrlDescription"></wicket:message></i></td></tr>
+		<tr><th><wicket:message key="gb.destinationUrl">destination url</wicket:message></th><td class="edit"><input type="text" wicket:id="destinationUrl" size="60" /> &nbsp;<i><wicket:message key="gb.destinationUrlDescription"></wicket:message></i></td></tr>
+		<tr><th valign="top"><wicket:message key="gb.message">message</wicket:message></th><td class="edit"><input type="text" wicket:id="message" size="80" /></td></tr>
+		<tr><th><wicket:message key="gb.type">type</wicket:message></th><td><span wicket:id="tokenType">[token type]</span></td></tr>
+		<tr><th><wicket:message key="gb.token">token</wicket:message></th><td><span class="sha1" wicket:id="token">[token]</span></td></tr>
+		<tr><th></th><td class="editButton"><input type="submit" value="propose" wicket:message="value:gb.sendProposal" wicket:id="save" /> <input type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /></td></tr>
+	</table>
+	</form>
+	
+	<div style="padding-top:10px;" wicket:id="repositories"></div>
+</body>
+</wicket:extend>    
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/SendProposalPage.java b/src/com/gitblit/wicket/pages/SendProposalPage.java
new file mode 100644
index 0000000..adef0c5
--- /dev/null
+++ b/src/com/gitblit/wicket/pages/SendProposalPage.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Button;
+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.GitBlit;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.RequiresAdminRole;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.RepositoriesPanel;
+
+@RequiresAdminRole
+public class SendProposalPage extends BasePage {
+
+	public String myUrl;
+
+	public String destinationUrl;
+
+	public String message;
+
+	public SendProposalPage(PageParameters params) {
+		super(params);
+
+		setupPage("", getString("gb.sendProposal"));
+		setStatelessHint(true);
+
+		final String token = WicketUtils.getToken(params);
+
+		myUrl = WicketUtils.getHostURL(getRequest());
+		destinationUrl = "https://";
+
+		// temporary proposal
+		FederationProposal proposal = GitBlit.self().createFederationProposal(myUrl, token);
+		if (proposal == null) {
+			error("Could not create federation proposal!", true);
+		}
+
+		CompoundPropertyModel<SendProposalPage> model = new CompoundPropertyModel<SendProposalPage>(
+				this);
+
+		Form<SendProposalPage> form = new Form<SendProposalPage>("editForm", model) {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onSubmit() {
+				// confirm a repository name was entered
+				if (StringUtils.isEmpty(myUrl)) {
+					error("Please enter your Gitblit url!");
+					return;
+				}
+				if (StringUtils.isEmpty(destinationUrl)) {
+					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));
+						setResponsePage(RepositoriesPage.class);
+					} else {
+						error(MessageFormat.format("Sorry, {0} rejected your proposal.", destinationUrl));
+					}
+				} catch (Exception e) {
+					if (!StringUtils.isEmpty(e.getMessage())) {
+						error(e.getMessage());
+					} else {
+						error("Failed to send proposal!");
+					}
+				}
+			}
+		};
+		form.add(new TextField<String>("myUrl"));
+		form.add(new TextField<String>("destinationUrl"));
+		form.add(new TextField<String>("message"));
+		form.add(new Label("tokenType", proposal.tokenType.name()));
+		form.add(new Label("token", proposal.token));
+
+		form.add(new Button("save"));
+		Button cancel = new Button("cancel") {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			public void onSubmit() {
+				setResponsePage(RepositoriesPage.class);
+			}
+		};
+		cancel.setDefaultFormProcessing(false);
+		form.add(cancel);
+		add(form);
+
+		List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(
+				proposal.repositories.values());
+		RepositoriesPanel repositoriesPanel = new RepositoriesPanel("repositories", false,
+				repositories, getAccessRestrictions());
+		add(repositoriesPanel);
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/FederationProposalsPanel.java b/src/com/gitblit/wicket/panels/FederationProposalsPanel.java
index f83f926..11f3f5c 100644
--- a/src/com/gitblit/wicket/panels/FederationProposalsPanel.java
+++ b/src/com/gitblit/wicket/panels/FederationProposalsPanel.java
@@ -28,7 +28,7 @@
 import com.gitblit.GitBlit;
 import com.gitblit.models.FederationProposal;
 import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.pages.FederationProposalPage;
+import com.gitblit.wicket.pages.ReviewProposalPage;
 
 public class FederationProposalsPanel extends BasePanel {
 
@@ -54,11 +54,11 @@
 
 			public void populateItem(final Item<FederationProposal> item) {
 				final FederationProposal entry = item.getModelObject();
-				item.add(new LinkPanel("url", "list", entry.url, FederationProposalPage.class,
+				item.add(new LinkPanel("url", "list", entry.url, ReviewProposalPage.class,
 						WicketUtils.newTokenParameter(entry.token)));
 				item.add(WicketUtils.createDateLabel("received", entry.received, getTimeZone()));
 				item.add(new Label("tokenType", entry.tokenType.name()));
-				item.add(new LinkPanel("token", "list", entry.token, FederationProposalPage.class,
+				item.add(new LinkPanel("token", "list", entry.token, ReviewProposalPage.class,
 						WicketUtils.newTokenParameter(entry.token)));
 
 				Link<Void> deleteLink = new Link<Void>("deleteProposal") {
diff --git a/src/com/gitblit/wicket/panels/FederationTokensPanel.java b/src/com/gitblit/wicket/panels/FederationTokensPanel.java
index a9cbdcd..5a2e125 100644
--- a/src/com/gitblit/wicket/panels/FederationTokensPanel.java
+++ b/src/com/gitblit/wicket/panels/FederationTokensPanel.java
@@ -20,8 +20,8 @@
 import java.util.List;
 
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.apache.wicket.markup.html.link.ExternalLink;
-import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
@@ -32,6 +32,7 @@
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.SendProposalPage;
 
 public class FederationTokensPanel extends BasePanel {
 
@@ -41,11 +42,11 @@
 		super(wicketId);
 
 		final String baseUrl = getRequest().getRelativePathPrefixToContextRoot();
-		add(new ExternalLink("federatedUsers", FederationServlet.asPullLink(baseUrl, GitBlit.self()
+		add(new ExternalLink("federatedUsers", FederationServlet.asFederationLink(baseUrl, GitBlit.self()
 				.getFederationToken(FederationToken.USERS_AND_REPOSITORIES),
 				FederationRequest.PULL_USERS)));
 
-		add(new ExternalLink("federatedSettings", FederationServlet.asPullLink(baseUrl, GitBlit
+		add(new ExternalLink("federatedSettings", FederationServlet.asFederationLink(baseUrl, GitBlit
 				.self().getFederationToken(FederationToken.ALL), FederationRequest.PULL_SETTINGS)));
 
 		final List<String[]> data = new ArrayList<String[]>();
@@ -82,22 +83,11 @@
 				}
 				item.add(new Label("value", entry[1]));
 
-				item.add(new ExternalLink("repositoryDefinitions", FederationServlet.asPullLink(
+				item.add(new ExternalLink("repositoryDefinitions", FederationServlet.asFederationLink(
 						baseUrl, entry[1], FederationRequest.PULL_REPOSITORIES)));
 
-				// TODO make this work
-				Link<Void> sendProposal = new Link<Void>("send") {
-
-					private static final long serialVersionUID = 1L;
-
-					@Override
-					public void onClick() {
-						error("Sorry, this does not work yet.  :(");
-					}
-				};
-				sendProposal.add(new JavascriptTextPrompt("onclick",
-						"Please enter URL for remote Gitblit instance:"));
-				item.add(sendProposal);
+				item.add(new BookmarkablePageLink<Void>("send",
+						SendProposalPage.class, WicketUtils.newTokenParameter(entry[1])));
 
 				WicketUtils.setAlternatingBackground(item, counter);
 				counter++;
diff --git a/tests/com/gitblit/tests/FederationTests.java b/tests/com/gitblit/tests/FederationTests.java
index 903bd9c..28b089f 100644
--- a/tests/com/gitblit/tests/FederationTests.java
+++ b/tests/com/gitblit/tests/FederationTests.java
@@ -28,6 +28,7 @@
 import com.gitblit.Constants.FederationToken;
 import com.gitblit.FederationServlet;
 import com.gitblit.GitBlitServer;
+import com.gitblit.models.FederationProposal;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.utils.FederationUtils;
 import com.google.gson.Gson;
@@ -86,14 +87,17 @@
 			repositories.put(model.name, model);
 		}
 
+		FederationProposal proposal = new FederationProposal("http://testurl", FederationToken.ALL,
+				"testtoken", repositories);
+
 		// propose federation
-		assertTrue("proposal refused", FederationUtils.propose("http://localhost:" + port,
-				FederationToken.ALL, "testtoken", "http://testurl", repositories));
+		assertTrue("proposal refused",
+				FederationUtils.propose("http://localhost:" + port, proposal));
 	}
 
 	public void testPullRepositories() throws Exception {
 		try {
-			String url = FederationServlet.asPullLink("http://localhost:" + port, "testtoken",
+			String url = FederationServlet.asFederationLink("http://localhost:" + port, "testtoken",
 					FederationRequest.PULL_REPOSITORIES);
 			String json = FederationUtils.readJson(url);
 		} catch (IOException e) {

--
Gitblit v1.9.1