Merge pull request #1104 from ritschwumm/wip/generic

generic ssh user
This commit is contained in:
Naoki Takezoe
2016-05-13 15:30:59 -04:00
8 changed files with 106 additions and 40 deletions

View File

@@ -417,9 +417,7 @@ object RepositoryService {
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
if(context.settings.ssh){
context.loginAccount.flatMap { loginAccount =>
context.settings.sshAddress.map { x => s"ssh://${loginAccount.userName}@${x.host}:${x.port}/${owner}/${name}.git" }
}
context.settings.sshAddress.map { x => s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git" }
} else None
def openRepoUrl(openUrl: String)(implicit context: Context): String = s"github-${context.platform}://openRepo/${openUrl}"

View File

@@ -12,6 +12,9 @@ trait SshKeyService {
def getPublicKeys(userName: String)(implicit s: Session): List[SshKey] =
SshKeys.filter(_.userName === userName.bind).sortBy(_.sshKeyId).list
def getAllKeys()(implicit s: Session): List[SshKey] =
SshKeys.filter(_.publicKey.trim =!= "").list
def deletePublicKey(userName: String, sshKeyId: Int)(implicit s: Session): Unit =
SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete

View File

@@ -141,7 +141,11 @@ object SystemSettingsService {
for {
host <- sshHost if ssh
}
yield SshAddress(host, sshPort.getOrElse(DefaultSshPort))
yield SshAddress(
host,
sshPort.getOrElse(DefaultSshPort),
"git"
)
}
case class Ldap(
@@ -169,7 +173,8 @@ object SystemSettingsService {
case class SshAddress(
host:String,
port:Int)
port:Int,
genericUser:String)
val DefaultSshPort = 29418
val DefaultSmtpPort = 25

View File

@@ -5,7 +5,8 @@ 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.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command, SessionAware}
import org.apache.sshd.server.session.ServerSession
import org.slf4j.LoggerFactory
import java.io.{File, InputStream, OutputStream}
import ControlUtil._
@@ -20,37 +21,44 @@ object GitCommand {
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
}
abstract class GitCommand() extends Command {
abstract class GitCommand extends Command with SessionAware {
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 authUser:Option[String] = None
protected def runTask(user: String)(implicit session: Session): Unit
protected def runTask(authUser: String)(implicit session: Session): Unit
private def newTask(user: String): Runnable = new Runnable {
private def newTask(): Runnable = new Runnable {
override def run(): Unit = {
Database() withSession { implicit session =>
try {
runTask(user)
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)
}
authUser match {
case Some(authUser) =>
Database() withSession { implicit session =>
try {
runTask(authUser)
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)
}
}
}
override def start(env: Environment): Unit = {
val user = env.getEnv.get("USER")
val thread = new Thread(newTask(user))
final override def start(env: Environment): Unit = {
val thread = new Thread(newTask())
thread.start()
}
@@ -72,6 +80,10 @@ abstract class GitCommand() extends Command {
this.in = in
}
override def setSession(serverSession:ServerSession) {
this.authUser = PublicKeyAuthenticator.getUserName(serverSession)
}
}
abstract class DefaultGitCommand(val owner: String, val repoName: String) extends GitCommand {

View File

@@ -15,7 +15,6 @@ class NoShell(sshAddress:SshAddress) extends Factory[Command] {
private var callback: ExitCallback = null
override def start(env: Environment): Unit = {
val user = env.getEnv.get("USER")
val message =
"""
| Welcome to
@@ -32,7 +31,7 @@ class NoShell(sshAddress:SshAddress) extends Factory[Command] {
| Please use:
|
| git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
""".stripMargin.format(user, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
""".stripMargin.format(sshAddress.genericUser, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
err.write(Constants.encode(message))
err.flush()
in.close()

View File

@@ -2,22 +2,73 @@ package gitbucket.core.ssh
import java.security.PublicKey
import gitbucket.core.model.SshKey
import gitbucket.core.service.SshKeyService
import gitbucket.core.servlet.Database
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
import org.apache.sshd.server.session.ServerSession
import org.apache.sshd.common.session.Session
import org.slf4j.LoggerFactory
class PublicKeyAuthenticator extends PublickeyAuthenticator with SshKeyService {
object PublicKeyAuthenticator {
// put in the ServerSession here to be read by GitCommand later
private val userNameSessionKey = new Session.AttributeKey[String]
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = {
Database() withSession { implicit session =>
getPublicKeys(username).exists { sshKey =>
SshUtil.str2PublicKey(sshKey.publicKey) match {
case Some(publicKey) => key.equals(publicKey)
case _ => false
}
}
def putUserName(serverSession:ServerSession, userName:String):Unit =
serverSession.setAttribute(userNameSessionKey, userName)
def getUserName(serverSession:ServerSession):Option[String] =
Option(serverSession.getAttribute(userNameSessionKey))
}
class PublicKeyAuthenticator(genericUser:String) extends PublickeyAuthenticator with SshKeyService {
private val logger = LoggerFactory.getLogger(classOf[PublicKeyAuthenticator])
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean =
if (username == genericUser) authenticateGenericUser(username, key, session, genericUser)
else authenticateLoginUser(username, key, session)
private def authenticateLoginUser(username: String, key: PublicKey, session: ServerSession): Boolean = {
val authenticated =
Database()
.withSession { implicit dbSession => getPublicKeys(username) }
.map(_.publicKey)
.flatMap(SshUtil.str2PublicKey)
.contains(key)
if (authenticated) {
logger.info(s"authentication as ssh user ${username} succeeded")
PublicKeyAuthenticator.putUserName(session, username)
}
else {
logger.info(s"authentication as ssh user ${username} failed")
}
authenticated
}
private def authenticateGenericUser(username: String, key: PublicKey, session: ServerSession, genericUser:String): Boolean = {
// find all users having the key we got from ssh
val possibleUserNames =
Database()
.withSession { implicit dbSession => getAllKeys() }
.filter { sshKey =>
SshUtil.str2PublicKey(sshKey.publicKey).exists(_ == key)
}
.map(_.userName)
.distinct
// determine the user - if different accounts share the same key, tough luck
val uniqueUserName =
possibleUserNames match {
case List() =>
logger.info(s"authentication as generic user ${genericUser} failed, public key not found")
None
case List(name) =>
logger.info(s"authentication as generic user ${genericUser} succeeded, identified ${name}")
Some(name)
case _ =>
logger.info(s"authentication as generic user ${genericUser} failed, public key is ambiguous")
None
}
uniqueUserName.foreach(PublicKeyAuthenticator.putUserName(session, _))
uniqueUserName.isDefined
}
}

View File

@@ -21,7 +21,7 @@ object SshServer {
provider.setAlgorithm("RSA")
provider.setOverwriteAllowed(false)
server.setKeyPairProvider(provider)
server.setPublickeyAuthenticator(new PublicKeyAuthenticator)
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser))
server.setCommandFactory(new GitCommandFactory(baseUrl))
server.setShellFactory(new NoShell(sshAddress))
}

View File

@@ -31,9 +31,7 @@ object SshUtil {
}
}
def fingerPrint(key: String): Option[String] = str2PublicKey(key) match {
case Some(publicKey) => Some(KeyUtils.getFingerPrint(publicKey))
case None => None
}
def fingerPrint(key: String): Option[String] =
str2PublicKey(key) map KeyUtils.getFingerPrint
}