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

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