You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FederationManager.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. /*
  2. * Copyright 2013 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.manager;
  17. import java.io.File;
  18. import java.io.FileFilter;
  19. import java.nio.charset.Charset;
  20. import java.text.MessageFormat;
  21. import java.util.ArrayList;
  22. import java.util.Collections;
  23. import java.util.HashMap;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.concurrent.ConcurrentHashMap;
  27. import javax.servlet.http.HttpServletRequest;
  28. import org.slf4j.Logger;
  29. import org.slf4j.LoggerFactory;
  30. import com.gitblit.Constants;
  31. import com.gitblit.Constants.FederationRequest;
  32. import com.gitblit.Constants.FederationToken;
  33. import com.gitblit.IStoredSettings;
  34. import com.gitblit.Keys;
  35. import com.gitblit.models.FederationModel;
  36. import com.gitblit.models.FederationProposal;
  37. import com.gitblit.models.FederationSet;
  38. import com.gitblit.models.RepositoryModel;
  39. import com.gitblit.models.UserModel;
  40. import com.gitblit.utils.Base64;
  41. import com.gitblit.utils.FederationUtils;
  42. import com.gitblit.utils.JsonUtils;
  43. import com.gitblit.utils.StringUtils;
  44. /**
  45. * Federation manager controls all aspects of handling federation sets, tokens,
  46. * and proposals.
  47. *
  48. * @author James Moger
  49. *
  50. */
  51. public class FederationManager implements IFederationManager {
  52. private final Logger logger = LoggerFactory.getLogger(getClass());
  53. private final List<FederationModel> federationRegistrations = Collections
  54. .synchronizedList(new ArrayList<FederationModel>());
  55. private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
  56. private final IStoredSettings settings;
  57. private final IRuntimeManager runtimeManager;
  58. private final INotificationManager notificationManager;
  59. private final IRepositoryManager repositoryManager;
  60. public FederationManager(
  61. IRuntimeManager runtimeManager,
  62. INotificationManager notificationManager,
  63. IRepositoryManager repositoryManager) {
  64. this.settings = runtimeManager.getSettings();
  65. this.runtimeManager = runtimeManager;
  66. this.notificationManager = notificationManager;
  67. this.repositoryManager = repositoryManager;
  68. }
  69. @Override
  70. public FederationManager start() {
  71. return this;
  72. }
  73. @Override
  74. public FederationManager stop() {
  75. return this;
  76. }
  77. /**
  78. * Returns the path of the proposals folder. This method checks to see if
  79. * Gitblit is running on a cloud service and may return an adjusted path.
  80. *
  81. * @return the proposals folder path
  82. */
  83. @Override
  84. public File getProposalsFolder() {
  85. return runtimeManager.getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
  86. }
  87. @Override
  88. public boolean canFederate() {
  89. String passphrase = settings.getString(Keys.federation.passphrase, "");
  90. return !StringUtils.isEmpty(passphrase);
  91. }
  92. /**
  93. * Returns the federation user account.
  94. *
  95. * @return the federation user account
  96. */
  97. @Override
  98. public UserModel getFederationUser() {
  99. // the federation user is an administrator
  100. UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
  101. federationUser.canAdmin = true;
  102. return federationUser;
  103. }
  104. @Override
  105. public UserModel authenticate(HttpServletRequest httpRequest) {
  106. if (canFederate()) {
  107. // try to authenticate federation user for cloning
  108. final String authorization = httpRequest.getHeader("Authorization");
  109. if (authorization != null && authorization.startsWith("Basic")) {
  110. // Authorization: Basic base64credentials
  111. String base64Credentials = authorization.substring("Basic".length()).trim();
  112. String credentials = new String(Base64.decode(base64Credentials),
  113. Charset.forName("UTF-8"));
  114. // credentials = username:password
  115. final String[] values = credentials.split(":", 2);
  116. if (values.length == 2) {
  117. String username = StringUtils.decodeUsername(values[0]);
  118. String password = values[1];
  119. if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) {
  120. List<String> tokens = getFederationTokens();
  121. if (tokens.contains(password)) {
  122. return getFederationUser();
  123. }
  124. }
  125. }
  126. }
  127. }
  128. return null;
  129. }
  130. /**
  131. * Returns the list of federated gitblit instances that this instance will
  132. * try to pull.
  133. *
  134. * @return list of registered gitblit instances
  135. */
  136. @Override
  137. public List<FederationModel> getFederationRegistrations() {
  138. if (federationRegistrations.isEmpty()) {
  139. federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
  140. }
  141. return federationRegistrations;
  142. }
  143. /**
  144. * Retrieve the specified federation registration.
  145. *
  146. * @param name
  147. * the name of the registration
  148. * @return a federation registration
  149. */
  150. @Override
  151. public FederationModel getFederationRegistration(String url, String name) {
  152. // check registrations
  153. for (FederationModel r : getFederationRegistrations()) {
  154. if (r.name.equals(name) && r.url.equals(url)) {
  155. return r;
  156. }
  157. }
  158. // check the results
  159. for (FederationModel r : getFederationResultRegistrations()) {
  160. if (r.name.equals(name) && r.url.equals(url)) {
  161. return r;
  162. }
  163. }
  164. return null;
  165. }
  166. /**
  167. * Returns the list of federation sets.
  168. *
  169. * @return list of federation sets
  170. */
  171. @Override
  172. public List<FederationSet> getFederationSets(String gitblitUrl) {
  173. List<FederationSet> list = new ArrayList<FederationSet>();
  174. // generate standard tokens
  175. for (FederationToken type : FederationToken.values()) {
  176. FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
  177. fset.repositories = getRepositories(gitblitUrl, fset.token);
  178. list.add(fset);
  179. }
  180. // generate tokens for federation sets
  181. for (String set : settings.getStrings(Keys.federation.sets)) {
  182. FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
  183. getFederationToken(set));
  184. fset.repositories = getRepositories(gitblitUrl, fset.token);
  185. list.add(fset);
  186. }
  187. return list;
  188. }
  189. /**
  190. * Returns the list of possible federation tokens for this Gitblit instance.
  191. *
  192. * @return list of federation tokens
  193. */
  194. @Override
  195. public List<String> getFederationTokens() {
  196. List<String> tokens = new ArrayList<String>();
  197. // generate standard tokens
  198. for (FederationToken type : FederationToken.values()) {
  199. tokens.add(getFederationToken(type));
  200. }
  201. // generate tokens for federation sets
  202. for (String set : settings.getStrings(Keys.federation.sets)) {
  203. tokens.add(getFederationToken(set));
  204. }
  205. return tokens;
  206. }
  207. /**
  208. * Returns the specified federation token for this Gitblit instance.
  209. *
  210. * @param type
  211. * @return a federation token
  212. */
  213. @Override
  214. public String getFederationToken(FederationToken type) {
  215. return getFederationToken(type.name());
  216. }
  217. /**
  218. * Returns the specified federation token for this Gitblit instance.
  219. *
  220. * @param value
  221. * @return a federation token
  222. */
  223. @Override
  224. public String getFederationToken(String value) {
  225. String passphrase = settings.getString(Keys.federation.passphrase, "");
  226. return StringUtils.getSHA1(passphrase + "-" + value);
  227. }
  228. /**
  229. * Compares the provided token with this Gitblit instance's tokens and
  230. * determines if the requested permission may be granted to the token.
  231. *
  232. * @param req
  233. * @param token
  234. * @return true if the request can be executed
  235. */
  236. @Override
  237. public boolean validateFederationRequest(FederationRequest req, String token) {
  238. String all = getFederationToken(FederationToken.ALL);
  239. String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
  240. String jur = getFederationToken(FederationToken.REPOSITORIES);
  241. switch (req) {
  242. case PULL_REPOSITORIES:
  243. return token.equals(all) || token.equals(unr) || token.equals(jur);
  244. case PULL_USERS:
  245. case PULL_TEAMS:
  246. return token.equals(all) || token.equals(unr);
  247. case PULL_SETTINGS:
  248. case PULL_SCRIPTS:
  249. return token.equals(all);
  250. default:
  251. break;
  252. }
  253. return false;
  254. }
  255. /**
  256. * Acknowledge and cache the status of a remote Gitblit instance.
  257. *
  258. * @param identification
  259. * the identification of the pulling Gitblit instance
  260. * @param registration
  261. * the registration from the pulling Gitblit instance
  262. * @return true if acknowledged
  263. */
  264. @Override
  265. public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
  266. // reset the url to the identification of the pulling Gitblit instance
  267. registration.url = identification;
  268. String id = identification;
  269. if (!StringUtils.isEmpty(registration.folder)) {
  270. id += "-" + registration.folder;
  271. }
  272. federationPullResults.put(id, registration);
  273. return true;
  274. }
  275. /**
  276. * Returns the list of registration results.
  277. *
  278. * @return the list of registration results
  279. */
  280. @Override
  281. public List<FederationModel> getFederationResultRegistrations() {
  282. return new ArrayList<FederationModel>(federationPullResults.values());
  283. }
  284. /**
  285. * Submit a federation proposal. The proposal is cached locally and the
  286. * Gitblit administrator(s) are notified via email.
  287. *
  288. * @param proposal
  289. * the proposal
  290. * @param gitblitUrl
  291. * the url of your gitblit instance to send an email to
  292. * administrators
  293. * @return true if the proposal was submitted
  294. */
  295. @Override
  296. public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
  297. // convert proposal to json
  298. String json = JsonUtils.toJsonString(proposal);
  299. try {
  300. // make the proposals folder
  301. File proposalsFolder = getProposalsFolder();
  302. proposalsFolder.mkdirs();
  303. // cache json to a file
  304. File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
  305. com.gitblit.utils.FileUtils.writeContent(file, json);
  306. } catch (Exception e) {
  307. logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
  308. }
  309. // send an email, if possible
  310. notificationManager.sendMailToAdministrators("Federation proposal from " + proposal.url,
  311. "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token);
  312. return true;
  313. }
  314. /**
  315. * Returns the list of pending federation proposals
  316. *
  317. * @return list of federation proposals
  318. */
  319. @Override
  320. public List<FederationProposal> getPendingFederationProposals() {
  321. List<FederationProposal> list = new ArrayList<FederationProposal>();
  322. File folder = getProposalsFolder();
  323. if (folder.exists()) {
  324. File[] files = folder.listFiles(new FileFilter() {
  325. @Override
  326. public boolean accept(File file) {
  327. return file.isFile()
  328. && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
  329. }
  330. });
  331. for (File file : files) {
  332. String json = com.gitblit.utils.FileUtils.readContent(file, null);
  333. FederationProposal proposal = JsonUtils.fromJsonString(json,
  334. FederationProposal.class);
  335. list.add(proposal);
  336. }
  337. }
  338. return list;
  339. }
  340. /**
  341. * Get repositories for the specified token.
  342. *
  343. * @param gitblitUrl
  344. * the base url of this gitblit instance
  345. * @param token
  346. * the federation token
  347. * @return a map of <cloneurl, RepositoryModel>
  348. */
  349. @Override
  350. public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
  351. Map<String, String> federationSets = new HashMap<String, String>();
  352. for (String set : settings.getStrings(Keys.federation.sets)) {
  353. federationSets.put(getFederationToken(set), set);
  354. }
  355. // Determine the Gitblit clone url
  356. StringBuilder sb = new StringBuilder();
  357. sb.append(gitblitUrl);
  358. sb.append(Constants.R_PATH);
  359. sb.append("{0}");
  360. String cloneUrl = sb.toString();
  361. // Retrieve all available repositories
  362. UserModel user = getFederationUser();
  363. List<RepositoryModel> list = repositoryManager.getRepositoryModels(user);
  364. // create the [cloneurl, repositoryModel] map
  365. Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
  366. for (RepositoryModel model : list) {
  367. // by default, setup the url for THIS repository
  368. String url = MessageFormat.format(cloneUrl, model.name);
  369. switch (model.federationStrategy) {
  370. case EXCLUDE:
  371. // skip this repository
  372. continue;
  373. case FEDERATE_ORIGIN:
  374. // federate the origin, if it is defined
  375. if (!StringUtils.isEmpty(model.origin)) {
  376. url = model.origin;
  377. }
  378. break;
  379. default:
  380. break;
  381. }
  382. if (federationSets.containsKey(token)) {
  383. // include repositories only for federation set
  384. String set = federationSets.get(token);
  385. if (model.federationSets.contains(set)) {
  386. repositories.put(url, model);
  387. }
  388. } else {
  389. // standard federation token for ALL
  390. repositories.put(url, model);
  391. }
  392. }
  393. return repositories;
  394. }
  395. /**
  396. * Creates a proposal from the token.
  397. *
  398. * @param gitblitUrl
  399. * the url of this Gitblit instance
  400. * @param token
  401. * @return a potential proposal
  402. */
  403. @Override
  404. public FederationProposal createFederationProposal(String gitblitUrl, String token) {
  405. FederationToken tokenType = FederationToken.REPOSITORIES;
  406. for (FederationToken type : FederationToken.values()) {
  407. if (token.equals(getFederationToken(type))) {
  408. tokenType = type;
  409. break;
  410. }
  411. }
  412. Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
  413. FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
  414. repositories);
  415. return proposal;
  416. }
  417. /**
  418. * Returns the proposal identified by the supplied token.
  419. *
  420. * @param token
  421. * @return the specified proposal or null
  422. */
  423. @Override
  424. public FederationProposal getPendingFederationProposal(String token) {
  425. List<FederationProposal> list = getPendingFederationProposals();
  426. for (FederationProposal proposal : list) {
  427. if (proposal.token.equals(token)) {
  428. return proposal;
  429. }
  430. }
  431. return null;
  432. }
  433. /**
  434. * Deletes a pending federation proposal.
  435. *
  436. * @param a
  437. * proposal
  438. * @return true if the proposal was deleted
  439. */
  440. @Override
  441. public boolean deletePendingFederationProposal(FederationProposal proposal) {
  442. File folder = getProposalsFolder();
  443. File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
  444. return file.delete();
  445. }
  446. }