All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gitbucket.core.ssh.GitCommand.scala Maven / Gradle / Ivy

The newest version!
package gitbucket.core.ssh

import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
import gitbucket.core.service.{AccountService, DeployKeyService, RepositoryService, SystemSettingsService}
import gitbucket.core.servlet.{CommitLogHook, Database}
import gitbucket.core.util.Directory
import org.apache.sshd.server.{Environment, ExitCallback}
import org.apache.sshd.server.command.{Command, CommandFactory}
import org.apache.sshd.server.session.{ServerSession, ServerSessionAware}
import org.slf4j.LoggerFactory

import java.io.{File, InputStream, OutputStream}
import org.eclipse.jgit.api.Git
import Directory._
import gitbucket.core.service.SystemSettingsService.SshAddress
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
import org.apache.sshd.server.channel.ChannelSession
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
import org.apache.sshd.server.shell.UnknownCommand
import org.eclipse.jgit.errors.RepositoryNotFoundException

import scala.util.Using

object GitCommand {
  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
  val DefaultCommandRegexPort22 = """\Agit-(upload|receive)-pack '/?([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r
  val SimpleCommandRegexPort22 = """\Agit-(upload|receive)-pack '/?(.+\.git)'\Z""".r
}

abstract class GitCommand extends Command with ServerSessionAware {

  private val logger = LoggerFactory.getLogger(classOf[GitCommand])

  @volatile protected var err: OutputStream = null
  @volatile protected var in: InputStream = null
  @volatile protected var out: OutputStream = null
  @volatile protected var callback: ExitCallback = null
  @volatile private var authType: Option[AuthType] = None

  protected def runTask(authType: AuthType): Unit

  private def newTask(): Runnable = () => {
    authType match {
      case Some(authType) =>
        try {
          runTask(authType)
          callback.onExit(0)
        } catch {
          case e: RepositoryNotFoundException =>
            logger.info(e.getMessage)
            callback.onExit(1, "Repository Not Found")
          case e: Throwable =>
            logger.error(e.getMessage, e)
            callback.onExit(1)
        }
      case None =>
        val message = "User not authenticated"
        logger.error(message)
        callback.onExit(1, message)
    }
  }

  final override def start(channel: ChannelSession, env: Environment): Unit = {
    val thread = new Thread(newTask())
    thread.start()
  }

  override def destroy(channel: ChannelSession): Unit = {}

  override def setExitCallback(callback: ExitCallback): Unit = {
    this.callback = callback
  }

  override def setErrorStream(err: OutputStream): Unit = {
    this.err = err
  }

  override def setOutputStream(out: OutputStream): Unit = {
    this.out = out
  }

  override def setInputStream(in: InputStream): Unit = {
    this.in = in
  }

  override def setSession(serverSession: ServerSession): Unit = {
    this.authType = PublicKeyAuthenticator.getAuthType(serverSession)
  }

}

abstract class DefaultGitCommand(val owner: String, val repoName: String) extends GitCommand {
  self: RepositoryService with AccountService with DeployKeyService =>

  protected def userName(authType: AuthType): String = {
    authType match {
      case AuthType.UserAuthType(userName) => userName
      case AuthType.DeployKeyType(_)       => owner
    }
  }

  protected def isReadableUser(authType: AuthType, repositoryInfo: RepositoryService.RepositoryInfo)(implicit
    session: Session
  ): Boolean = {
    authType match {
      case AuthType.UserAuthType(username) => {
        getAccountByUserName(username) match {
          case Some(account) => hasGuestRole(owner, repoName, Some(account))
          case None          => false
        }
      }
      case AuthType.DeployKeyType(key) => {
        getDeployKeys(owner, repoName).filter(sshKey => SshUtil.str2PublicKey(sshKey.publicKey).contains(key)) match {
          case List(_) => true
          case _       => false
        }
      }
    }
  }

  protected def isWritableUser(authType: AuthType, repositoryInfo: RepositoryService.RepositoryInfo)(implicit
    session: Session
  ): Boolean = {
    authType match {
      case AuthType.UserAuthType(username) => {
        getAccountByUserName(username) match {
          case Some(account) => hasDeveloperRole(owner, repoName, Some(account))
          case None          => false
        }
      }
      case AuthType.DeployKeyType(key) => {
        getDeployKeys(owner, repoName).filter(sshKey => SshUtil.str2PublicKey(sshKey.publicKey).contains(key)) match {
          case List(x) if x.allowWrite => true
          case _                       => false
        }
      }
    }
  }

}

class DefaultGitUploadPack(owner: String, repoName: String)
    extends DefaultGitCommand(owner, repoName)
    with RepositoryService
    with AccountService
    with DeployKeyService {

  override protected def runTask(authType: AuthType): Unit = {
    val execute = Database() withSession { implicit session =>
      getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo =>
        !repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo)
      }
    }

    if (execute) {
      Using.resource(Git.open(getRepositoryDir(owner, repoName))) { git =>
        val repository = git.getRepository
        val upload = new UploadPack(repository)
        upload.upload(in, out, err)
      }
    }
  }
}

class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshAddress: SshAddress)
    extends DefaultGitCommand(owner, repoName)
    with RepositoryService
    with AccountService
    with DeployKeyService {

  override protected def runTask(authType: AuthType): Unit = {
    val execute = Database() withSession { implicit session =>
      getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo =>
        isWritableUser(authType, repositoryInfo)
      }
    }

    if (execute) {
      Using.resource(Git.open(getRepositoryDir(owner, repoName))) { git =>
        val repository = git.getRepository
        val receive = new ReceivePack(repository)
        if (!repoName.endsWith(".wiki")) {
          val hook =
            new CommitLogHook(owner, repoName, userName(authType), baseUrl, Some(sshAddress.getUrl(owner, repoName)))
          receive.setPreReceiveHook(hook)
          receive.setPostReceiveHook(hook)
        }
        receive.receive(in, out, err)
      }
    }
  }
}

class PluginGitUploadPack(repoName: String, routing: GitRepositoryRouting)
    extends GitCommand
    with SystemSettingsService {

  override protected def runTask(authType: AuthType): Unit = {
    val execute = Database() withSession { implicit session =>
      routing.filter.filter("/" + repoName, AuthType.userName(authType), loadSystemSettings(), false)
    }

    if (execute) {
      val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath)
      Using.resource(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, routing: GitRepositoryRouting)
    extends GitCommand
    with SystemSettingsService {

  override protected def runTask(authType: AuthType): Unit = {
    val execute = Database() withSession { implicit session =>
      routing.filter.filter("/" + repoName, AuthType.userName(authType), loadSystemSettings(), true)
    }

    if (execute) {
      val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath)
      Using.resource(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, sshAddress: SshAddress) extends CommandFactory {
  private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])

  override def createCommand(channel: ChannelSession, command: String): Command = {
    import GitCommand._
    logger.debug(s"command: $command")

    val pluginCommand = PluginRegistry().getSshCommandProviders.collectFirst {
      case f if f.isDefinedAt(command) => f(command)
    }

    pluginCommand.map(_.apply(channel)).getOrElse {
      val (simpleRegex, defaultRegex) =
        if (sshAddress.isDefaultPort) {
          (SimpleCommandRegexPort22, DefaultCommandRegexPort22)
        } else {
          (SimpleCommandRegex, DefaultCommandRegex)
        }
      command match {
        case simpleRegex("upload", repoName) if pluginRepository(repoName) =>
          new PluginGitUploadPack(repoName, routing(repoName))
        case simpleRegex("receive", repoName) if pluginRepository(repoName) =>
          new PluginGitReceivePack(repoName, routing(repoName))
        case defaultRegex("upload", owner, repoName) =>
          new DefaultGitUploadPack(owner, repoName)
        case defaultRegex("receive", owner, repoName) =>
          new DefaultGitReceivePack(owner, repoName, baseUrl, sshAddress)
        case _ => new UnknownCommand(command)
      }
    }
  }

  private def pluginRepository(repoName: String): Boolean =
    PluginRegistry().getRepositoryRouting("/" + repoName).isDefined
  private def routing(repoName: String): GitRepositoryRouting =
    PluginRegistry().getRepositoryRouting("/" + repoName).get

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy