mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-09 04:37:19 +02:00
(refs #812)SSH support for plug-in served git repository
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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 + "(/.*)?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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){
|
||||
|
||||
Reference in New Issue
Block a user