Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

GitServlet.java 14KB

12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
12 år sedan
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /*
  2. * Copyright 2011 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;
  17. import groovy.lang.Binding;
  18. import groovy.util.GroovyScriptEngine;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import java.text.MessageFormat;
  22. import java.util.Collection;
  23. import java.util.Enumeration;
  24. import java.util.LinkedHashSet;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Set;
  28. import javax.servlet.ServletConfig;
  29. import javax.servlet.ServletContext;
  30. import javax.servlet.ServletException;
  31. import javax.servlet.http.HttpServletRequest;
  32. import org.eclipse.jgit.http.server.resolver.DefaultReceivePackFactory;
  33. import org.eclipse.jgit.http.server.resolver.DefaultUploadPackFactory;
  34. import org.eclipse.jgit.lib.PersonIdent;
  35. import org.eclipse.jgit.lib.Ref;
  36. import org.eclipse.jgit.lib.Repository;
  37. import org.eclipse.jgit.revwalk.RevCommit;
  38. import org.eclipse.jgit.transport.PostReceiveHook;
  39. import org.eclipse.jgit.transport.PreReceiveHook;
  40. import org.eclipse.jgit.transport.ReceiveCommand;
  41. import org.eclipse.jgit.transport.ReceiveCommand.Result;
  42. import org.eclipse.jgit.transport.ReceivePack;
  43. import org.eclipse.jgit.transport.RefFilter;
  44. import org.eclipse.jgit.transport.UploadPack;
  45. import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException;
  46. import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
  47. import org.slf4j.Logger;
  48. import org.slf4j.LoggerFactory;
  49. import com.gitblit.Constants.AccessRestrictionType;
  50. import com.gitblit.models.RepositoryModel;
  51. import com.gitblit.models.UserModel;
  52. import com.gitblit.utils.ClientLogger;
  53. import com.gitblit.utils.HttpUtils;
  54. import com.gitblit.utils.IssueUtils;
  55. import com.gitblit.utils.JGitUtils;
  56. import com.gitblit.utils.PushLogUtils;
  57. import com.gitblit.utils.StringUtils;
  58. /**
  59. * The GitServlet exists to force configuration of the JGit GitServlet based on
  60. * the Gitblit settings from either gitblit.properties or from context
  61. * parameters in the web.xml file.
  62. *
  63. * It also implements and registers the Groovy hook mechanism.
  64. *
  65. * Access to this servlet is protected by the GitFilter.
  66. *
  67. * @author James Moger
  68. *
  69. */
  70. public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
  71. private static final long serialVersionUID = 1L;
  72. private GroovyScriptEngine gse;
  73. private File groovyDir;
  74. @Override
  75. public void init(ServletConfig config) throws ServletException {
  76. groovyDir = GitBlit.getGroovyScriptsFolder();
  77. try {
  78. // set Grape root
  79. File grapeRoot = GitBlit.getFileOrFolder(Keys.groovy.grapeFolder, "${baseFolder}/groovy/grape").getAbsoluteFile();
  80. grapeRoot.mkdirs();
  81. System.setProperty("grape.root", grapeRoot.getAbsolutePath());
  82. gse = new GroovyScriptEngine(groovyDir.getAbsolutePath());
  83. } catch (IOException e) {
  84. throw new ServletException("Failed to instantiate Groovy Script Engine!", e);
  85. }
  86. // set the Gitblit receive hook
  87. setReceivePackFactory(new DefaultReceivePackFactory() {
  88. @Override
  89. public ReceivePack create(HttpServletRequest req, Repository db)
  90. throws ServiceNotEnabledException, ServiceNotAuthorizedException {
  91. // determine repository name from request
  92. String repositoryName = req.getPathInfo().substring(1);
  93. repositoryName = GitFilter.getRepositoryName(repositoryName);
  94. GitblitReceiveHook hook = new GitblitReceiveHook();
  95. hook.repositoryName = repositoryName;
  96. hook.gitblitUrl = HttpUtils.getGitblitURL(req);
  97. ReceivePack rp = super.create(req, db);
  98. rp.setPreReceiveHook(hook);
  99. rp.setPostReceiveHook(hook);
  100. // determine pushing user
  101. PersonIdent person = rp.getRefLogIdent();
  102. UserModel user = GitBlit.self().getUserModel(person.getName());
  103. if (user == null) {
  104. // anonymous push, create a temporary usermodel
  105. user = new UserModel(person.getName());
  106. }
  107. // enforce advanced ref permissions
  108. RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
  109. rp.setAllowCreates(user.canCreateRef(repository));
  110. rp.setAllowDeletes(user.canDeleteRef(repository));
  111. rp.setAllowNonFastForwards(user.canRewindRef(repository));
  112. if (repository.isFrozen) {
  113. throw new ServiceNotEnabledException();
  114. }
  115. return rp;
  116. }
  117. });
  118. // override the default upload pack to exclude gitblit refs
  119. setUploadPackFactory(new DefaultUploadPackFactory() {
  120. @Override
  121. public UploadPack create(final HttpServletRequest req, final Repository db)
  122. throws ServiceNotEnabledException, ServiceNotAuthorizedException {
  123. UploadPack up = super.create(req, db);
  124. RefFilter refFilter = new RefFilter() {
  125. @Override
  126. public Map<String, Ref> filter(Map<String, Ref> refs) {
  127. // admin accounts can access all refs
  128. UserModel user = GitBlit.self().authenticate(req);
  129. if (user == null) {
  130. user = UserModel.ANONYMOUS;
  131. }
  132. if (user.canAdmin()) {
  133. return refs;
  134. }
  135. // normal users can not clone gitblit refs
  136. refs.remove(IssueUtils.GB_ISSUES);
  137. refs.remove(PushLogUtils.GB_PUSHES);
  138. return refs;
  139. }
  140. };
  141. up.setRefFilter(refFilter);
  142. return up;
  143. }
  144. });
  145. super.init(new GitblitServletConfig(config));
  146. }
  147. /**
  148. * Transitional wrapper class to configure the JGit 1.2 GitFilter. This
  149. * GitServlet will probably be replaced by a GitFilter so that Gitblit can
  150. * serve Git repositories on the root URL and not a /git sub-url.
  151. *
  152. * @author James Moger
  153. *
  154. */
  155. private class GitblitServletConfig implements ServletConfig {
  156. final ServletConfig config;
  157. GitblitServletConfig(ServletConfig config) {
  158. this.config = config;
  159. }
  160. @Override
  161. public String getServletName() {
  162. return config.getServletName();
  163. }
  164. @Override
  165. public ServletContext getServletContext() {
  166. return config.getServletContext();
  167. }
  168. @Override
  169. public String getInitParameter(String name) {
  170. if (name.equals("base-path")) {
  171. return GitBlit.getRepositoriesFolder().getAbsolutePath();
  172. } else if (name.equals("export-all")) {
  173. return "1";
  174. }
  175. return config.getInitParameter(name);
  176. }
  177. @Override
  178. public Enumeration<String> getInitParameterNames() {
  179. return config.getInitParameterNames();
  180. }
  181. }
  182. /**
  183. * The Gitblit receive hook allows for special processing on push events.
  184. * That might include rejecting writes to specific branches or executing a
  185. * script.
  186. *
  187. * @author James Moger
  188. *
  189. */
  190. private class GitblitReceiveHook implements PreReceiveHook, PostReceiveHook {
  191. protected final Logger logger = LoggerFactory.getLogger(GitblitReceiveHook.class);
  192. protected String repositoryName;
  193. protected String gitblitUrl;
  194. /**
  195. * Instrumentation point where the incoming push event has been parsed,
  196. * validated, objects created BUT refs have not been updated. You might
  197. * use this to enforce a branch-write permissions model.
  198. */
  199. @Override
  200. public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
  201. RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
  202. UserModel user = getUserModel(rp);
  203. if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH) && repository.verifyCommitter) {
  204. if (StringUtils.isEmpty(user.emailAddress)) {
  205. // emit warning if user does not have an email address
  206. logger.warn(MessageFormat.format("Consider setting an email address for {0} ({1}) to improve committer verification.", user.getDisplayName(), user.username));
  207. }
  208. // Optionally enforce that the committer of the left parent chain
  209. // match the account being used to push the commits.
  210. //
  211. // This requires all merge commits are executed with the "--no-ff"
  212. // option to force a merge commit even if fast-forward is possible.
  213. // This ensures that the chain of left parents has the commit
  214. // identity of the merging user.
  215. for (ReceiveCommand cmd : commands) {
  216. try {
  217. List<RevCommit> commits = JGitUtils.getRevLog(rp.getRepository(), cmd.getOldId().name(), cmd.getNewId().name());
  218. for (RevCommit commit : commits) {
  219. PersonIdent committer = commit.getCommitterIdent();
  220. if (!user.is(committer.getName(), committer.getEmailAddress())) {
  221. String reason;
  222. if (StringUtils.isEmpty(user.emailAddress)) {
  223. // account does not have en email address
  224. reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4})", commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username);
  225. } else {
  226. // account has an email address
  227. reason = MessageFormat.format("{0} by {1} <{2}> was not committed by {3} ({4}) <{5}>", commit.getId().name(), committer.getName(), StringUtils.isEmpty(committer.getEmailAddress()) ? "?":committer.getEmailAddress(), user.getDisplayName(), user.username, user.emailAddress);
  228. }
  229. cmd.setResult(Result.REJECTED_OTHER_REASON, reason);
  230. break;
  231. }
  232. }
  233. } catch (Exception e) {
  234. logger.error("Failed to verify commits were made by pushing user", e);
  235. }
  236. }
  237. }
  238. Set<String> scripts = new LinkedHashSet<String>();
  239. scripts.addAll(GitBlit.self().getPreReceiveScriptsInherited(repository));
  240. scripts.addAll(repository.preReceiveScripts);
  241. runGroovy(repository, user, commands, rp, scripts);
  242. for (ReceiveCommand cmd : commands) {
  243. if (!Result.NOT_ATTEMPTED.equals(cmd.getResult())) {
  244. logger.warn(MessageFormat.format("{0} {1} because \"{2}\"", cmd.getNewId()
  245. .getName(), cmd.getResult(), cmd.getMessage()));
  246. }
  247. }
  248. }
  249. /**
  250. * Instrumentation point where the incoming push has been applied to the
  251. * repository. This is the point where we would trigger a Jenkins build
  252. * or send an email.
  253. */
  254. @Override
  255. public void onPostReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
  256. if (commands.size() == 0) {
  257. logger.info("skipping post-receive hooks, no refs created, updated, or removed");
  258. return;
  259. }
  260. UserModel user = getUserModel(rp);
  261. RepositoryModel repository = GitBlit.self().getRepositoryModel(repositoryName);
  262. // log ref changes
  263. for (ReceiveCommand cmd : commands) {
  264. if (Result.OK.equals(cmd.getResult())) {
  265. // add some logging for important ref changes
  266. switch (cmd.getType()) {
  267. case DELETE:
  268. logger.info(MessageFormat.format("{0} DELETED {1} in {2} ({3})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name()));
  269. break;
  270. case CREATE:
  271. logger.info(MessageFormat.format("{0} CREATED {1} in {2}", user.username, cmd.getRefName(), repository.name));
  272. break;
  273. case UPDATE_NONFASTFORWARD:
  274. logger.info(MessageFormat.format("{0} UPDATED NON-FAST-FORWARD {1} in {2} (from {3} to {4})", user.username, cmd.getRefName(), repository.name, cmd.getOldId().name(), cmd.getNewId().name()));
  275. break;
  276. default:
  277. break;
  278. }
  279. }
  280. }
  281. // update push log
  282. try {
  283. PushLogUtils.updatePushLog(user, rp.getRepository(), commands);
  284. logger.info(MessageFormat.format("{0} push log updated", repository.name));
  285. } catch (Exception e) {
  286. logger.error(MessageFormat.format("Failed to update {0} pushlog", repository.name), e);
  287. }
  288. // run Groovy hook scripts
  289. Set<String> scripts = new LinkedHashSet<String>();
  290. scripts.addAll(GitBlit.self().getPostReceiveScriptsInherited(repository));
  291. scripts.addAll(repository.postReceiveScripts);
  292. runGroovy(repository, user, commands, rp, scripts);
  293. }
  294. /**
  295. * Returns the UserModel for the user pushing the changes.
  296. *
  297. * @param rp
  298. * @return a UserModel
  299. */
  300. protected UserModel getUserModel(ReceivePack rp) {
  301. PersonIdent person = rp.getRefLogIdent();
  302. UserModel user = GitBlit.self().getUserModel(person.getName());
  303. if (user == null) {
  304. // anonymous push, create a temporary usermodel
  305. user = new UserModel(person.getName());
  306. user.isAuthenticated = false;
  307. }
  308. return user;
  309. }
  310. /**
  311. * Runs the specified Groovy hook scripts.
  312. *
  313. * @param repository
  314. * @param user
  315. * @param commands
  316. * @param scripts
  317. */
  318. protected void runGroovy(RepositoryModel repository, UserModel user,
  319. Collection<ReceiveCommand> commands, ReceivePack rp, Set<String> scripts) {
  320. if (scripts == null || scripts.size() == 0) {
  321. // no Groovy scripts to execute
  322. return;
  323. }
  324. Binding binding = new Binding();
  325. binding.setVariable("gitblit", GitBlit.self());
  326. binding.setVariable("repository", repository);
  327. binding.setVariable("receivePack", rp);
  328. binding.setVariable("user", user);
  329. binding.setVariable("commands", commands);
  330. binding.setVariable("url", gitblitUrl);
  331. binding.setVariable("logger", logger);
  332. binding.setVariable("clientLogger", new ClientLogger(rp));
  333. for (String script : scripts) {
  334. if (StringUtils.isEmpty(script)) {
  335. continue;
  336. }
  337. // allow script to be specified without .groovy extension
  338. // this is easier to read in the settings
  339. File file = new File(groovyDir, script);
  340. if (!file.exists() && !script.toLowerCase().endsWith(".groovy")) {
  341. file = new File(groovyDir, script + ".groovy");
  342. if (file.exists()) {
  343. script = file.getName();
  344. }
  345. }
  346. try {
  347. Object result = gse.run(script, binding);
  348. if (result instanceof Boolean) {
  349. if (!((Boolean) result)) {
  350. logger.error(MessageFormat.format(
  351. "Groovy script {0} has failed! Hook scripts aborted.", script));
  352. break;
  353. }
  354. }
  355. } catch (Exception e) {
  356. logger.error(
  357. MessageFormat.format("Failed to execute Groovy script {0}", script), e);
  358. }
  359. }
  360. }
  361. }
  362. }