(refs #812)SSH support for plug-in served git repository

This commit is contained in:
Naoki Takezoe
2015-07-05 14:16:16 +09:00
parent 02f16639ea
commit f0d4c6546a
6 changed files with 83 additions and 46 deletions

View File

@@ -1,16 +1,20 @@
package gitbucket.core.plugin
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import gitbucket.core.model.Session
import gitbucket.core.service.SystemSettingsService.SystemSettings
case class GitRepositoryRouting(urlPattern: String, localPath: String, filter: GitRepositoryFilter){
def this(urlPattern: String, localPath: String) = {
this(urlPattern, localPath, new GitRepositoryFilter(){
def filter(request: HttpServletRequest, response: HttpServletResponse, settings: SystemSettings, isUpdating: Boolean): Boolean = true
def filter(repositoryName: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)
(implicit session: Session): Boolean = true
})
}
}
trait GitRepositoryFilter {
def filter(request: HttpServletRequest, response: HttpServletResponse, settings: SystemSettings, isUpdating: Boolean): Boolean
def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)
(implicit session: Session): Boolean
}

View File

@@ -90,12 +90,10 @@ class PluginRegistry {
repositoryRoutings.toSeq
}
def getRepositoryRouting(requestURI: String): Option[GitRepositoryRouting] = {
val path = requestURI.replaceFirst("^/git/", "")
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
PluginRegistry().getRepositoryRoutings().find {
case GitRepositoryRouting(urlPath, _, _) => {
path.matches(urlPath + "(/.*)?")
repositoryPath.matches("/" + urlPath + "(/.*)?")
}
}
}

View File

@@ -2,7 +2,7 @@ package gitbucket.core.servlet
import javax.servlet._
import javax.servlet.http._
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
import gitbucket.core.plugin.{GitRepositoryFilter, GitRepositoryRouting, PluginRegistry}
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
import gitbucket.core.util.{Keys, Implicits}
@@ -32,13 +32,10 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
val settings = loadSystemSettings()
try {
PluginRegistry().getRepositoryRouting(request.getRequestURI).map { case GitRepositoryRouting(_, _, f) =>
PluginRegistry().getRepositoryRouting(request.gitRepositoryPath).map { case GitRepositoryRouting(_, _, filter) =>
// served by plug-ins
if(f.filter(request, wrappedResponse, settings, isUpdating)){
pluginRepository(request, wrappedResponse, chain, settings, isUpdating)
} else {
requireAuth(response)
}
pluginRepository(request, wrappedResponse, chain, settings, isUpdating, filter)
}.getOrElse {
// default repositories
defaultRepository(request, wrappedResponse, chain, settings, isUpdating)
@@ -52,26 +49,22 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
}
private def pluginRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain,
settings: SystemSettings, isUpdating: Boolean): Unit = {
settings: SystemSettings, isUpdating: Boolean, filter: GitRepositoryFilter): Unit = {
implicit val r = request
if(!isUpdating && settings.allowAnonymousAccess){
val account = for {
auth <- Option(request.getHeader("Authorization"))
Array(username, password) = decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password)
} yield {
request.setAttribute(Keys.Request.UserName, account.userName)
account
}
if(filter.filter(request.gitRepositoryPath, account.map(_.userName), settings, isUpdating)){
chain.doFilter(request, response)
} else {
val passed = for {
auth <- Option(request.getHeader("Authorization"))
Array(username, password) = decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password)
} yield {
request.setAttribute(Keys.Request.UserName, account.userName)
true
}
if(passed.getOrElse(false)){
chain.doFilter(request, response)
} else {
requireAuth(response)
}
requireAuth(response)
}
}

View File

@@ -62,13 +62,13 @@ class GitBucketRepositoryResolver(parent: FileResolver[HttpServletRequest]) exte
private val resolver = new FileResolver[HttpServletRequest](new File(Directory.GitBucketHome), true)
override def open(req: HttpServletRequest, name: String): Repository = {
// Check routing which are provided by plug-in
val routing = PluginRegistry().getRepositoryRoutings().find {
case GitRepositoryRouting(urlPattern, _, _) => name.matches(urlPattern)
}
// // Check routing which are provided by plug-in
// val routing = PluginRegistry().getRepositoryRoutings().find {
// case GitRepositoryRouting(urlPattern, _, _) => name.matches(urlPattern)
// }
// Rewrite repository path if routing is marched
routing.map { case GitRepositoryRouting(urlPattern, localPath, _) =>
PluginRegistry().getRepositoryRouting(name).map { case GitRepositoryRouting(urlPattern, localPath, _) =>
val path = urlPattern.r.replaceFirstIn(name, localPath)
resolver.open(req, path)
}.getOrElse {
@@ -85,7 +85,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
override def create(request: HttpServletRequest, db: Repository): ReceivePack = {
val receivePack = new ReceivePack(db)
if(PluginRegistry().getRepositoryRouting(request.getRequestURI).isEmpty){
if(PluginRegistry().getRepositoryRouting(request.gitRepositoryPath).isEmpty){
val pusher = request.getAttribute(Keys.Request.UserName).asInstanceOf[String]
logger.debug("requestURI: " + request.getRequestURI)

View File

@@ -1,12 +1,13 @@
package gitbucket.core.ssh
import gitbucket.core.model.Session
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
import gitbucket.core.servlet.{Database, CommitLogHook}
import gitbucket.core.util.{Directory, ControlUtil}
import org.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command}
import org.slf4j.LoggerFactory
import java.io.{InputStream, OutputStream}
import java.io.{File, InputStream, OutputStream}
import ControlUtil._
import org.eclipse.jgit.api.Git
import Directory._
@@ -15,11 +16,11 @@ import org.apache.sshd.server.command.UnknownCommand
import org.eclipse.jgit.errors.RepositoryNotFoundException
object GitCommand {
val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
}
abstract class GitCommand(val owner: String, val repoName: String) extends Command {
self: RepositoryService with AccountService =>
abstract class GitCommand() extends Command {
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
protected var err: OutputStream = null
@@ -71,6 +72,11 @@ abstract class GitCommand(val owner: String, val repoName: String) extends Comma
this.in = in
}
}
abstract class DefaultGitCommand(val owner: String, val repoName: String) extends GitCommand {
self: RepositoryService with AccountService =>
protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo)
(implicit session: Session): Boolean =
getAccountByUserName(username) match {
@@ -80,7 +86,8 @@ abstract class GitCommand(val owner: String, val repoName: String) extends Comma
}
class GitUploadPack(owner: String, repoName: String, baseUrl: String) extends GitCommand(owner, repoName)
class DefaultGitUploadPack(owner: String, repoName: String, baseUrl: String) extends DefaultGitCommand(owner, repoName)
with RepositoryService with AccountService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
@@ -94,10 +101,9 @@ class GitUploadPack(owner: String, repoName: String, baseUrl: String) extends Gi
}
}
}
}
class GitReceivePack(owner: String, repoName: String, baseUrl: String) extends GitCommand(owner, repoName)
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String) extends DefaultGitCommand(owner, repoName)
with SystemSettingsService with RepositoryService with AccountService {
override protected def runTask(user: String)(implicit session: Session): Unit = {
@@ -116,18 +122,52 @@ class GitReceivePack(owner: String, repoName: String, baseUrl: String) extends G
}
}
}
}
class PluginGitUploadPack(repoName: String, baseUrl: String, routing: GitRepositoryRouting) extends GitCommand {
override protected def runTask(user: String)(implicit session: Session): Unit = {
// TODO filter??
val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath)
using(Git.open(new File(Directory.GitBucketHome, path))){ git =>
val repository = git.getRepository
val upload = new UploadPack(repository)
upload.upload(in, out, err)
}
}
}
class PluginGitReceivePack(repoName: String, baseUrl: String, routing: GitRepositoryRouting) extends GitCommand {
override protected def runTask(user: String)(implicit session: Session): Unit = {
// TODO filter??
val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath)
using(Git.open(new File(Directory.GitBucketHome, path))){ git =>
val repository = git.getRepository
val receive = new ReceivePack(repository)
receive.receive(in, out, err)
}
}
}
class GitCommandFactory(baseUrl: String) extends CommandFactory {
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
override def createCommand(command: String): Command = {
import GitCommand._
logger.debug(s"command: $command")
command match {
case GitCommand.CommandRegex("upload", owner, repoName) => new GitUploadPack(owner, repoName, baseUrl)
case GitCommand.CommandRegex("receive", owner, repoName) => new GitReceivePack(owner, repoName, baseUrl)
case SimpleCommandRegex ("upload" , repoName) if(pluginRepository(repoName)) => new PluginGitUploadPack (repoName, baseUrl, routing(repoName))
case SimpleCommandRegex ("receive", repoName) if(pluginRepository(repoName)) => new PluginGitReceivePack(repoName, baseUrl, routing(repoName))
case DefaultCommandRegex("upload" , owner, repoName) => new DefaultGitUploadPack (owner, repoName, baseUrl)
case DefaultCommandRegex("receive", owner, repoName) => new DefaultGitReceivePack(owner, repoName, baseUrl)
case _ => new UnknownCommand(command)
}
}
private def pluginRepository(repoName: String): Boolean = PluginRegistry().getRepositoryRouting(repoName).isDefined
private def routing(repoName: String): GitRepositoryRouting = PluginRegistry().getRepositoryRouting(repoName).get
}

View File

@@ -72,6 +72,8 @@ object Implicits {
def hasAttribute(name: String): Boolean = request.getAttribute(name) != null
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^/git/", "/")
}
implicit class RichSession(session: HttpSession){