From c86ece4dc0617eadb9450c22e3a8c69a0718e2ca Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Tue, 28 Jan 2014 22:06:55 +0900 Subject: [PATCH 01/91] Add mina ssh to dependencies --- project/build.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/project/build.scala b/project/build.scala index ce6d4d9f9..9a07a4ad7 100644 --- a/project/build.scala +++ b/project/build.scala @@ -36,6 +36,7 @@ object MyBuild extends Build { "org.apache.commons" % "commons-compress" % "1.5", "org.apache.commons" % "commons-email" % "1.3.1", "org.apache.httpcomponents" % "httpclient" % "4.3", + "org.apache.sshd" % "apache-sshd" % "0.9.0", "com.typesafe.slick" %% "slick" % "1.0.1", "com.novell.ldap" % "jldap" % "2009-10-07", "com.h2database" % "h2" % "1.3.173", From e902da6595fd746440fe120b047a95b5f78fb451 Mon Sep 17 00:00:00 2001 From: Ali Ayas Date: Wed, 26 Feb 2014 11:40:15 +0200 Subject: [PATCH 02/91] Improve code view Now it's more similar to GitHub --- src/main/webapp/assets/common/css/gitbucket.css | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/webapp/assets/common/css/gitbucket.css b/src/main/webapp/assets/common/css/gitbucket.css index 01c96b414..83359a044 100644 --- a/src/main/webapp/assets/common/css/gitbucket.css +++ b/src/main/webapp/assets/common/css/gitbucket.css @@ -464,10 +464,9 @@ ul#commit-file-list li.border { li.L0, li.L1, li.L2, li.L3, li.L4, li.L5, li.L6, li.L7, li.L8, li.L9 { list-style-type: decimal; background: white; -} - -li.L1, li.L3, li.L5, li.L7, li.L9 { - background: #f5f5f5; + border-left: 1px solid #E5E5E5; + padding-left: 10px; + color: rgba(0, 0, 0, 0.3); } pre.blob { From 057c5f073cf98238f785e11a5517d702a4335753 Mon Sep 17 00:00:00 2001 From: Ali Ayas Date: Wed, 26 Feb 2014 12:09:25 +0200 Subject: [PATCH 03/91] Improve diff view --- src/main/webapp/assets/jsdifflib/diffview.css | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main/webapp/assets/jsdifflib/diffview.css b/src/main/webapp/assets/jsdifflib/diffview.css index 4c4a7c7f2..399a6c7af 100644 --- a/src/main/webapp/assets/jsdifflib/diffview.css +++ b/src/main/webapp/assets/jsdifflib/diffview.css @@ -35,12 +35,22 @@ table.diff { table.diff tbody { font-family:Courier, monospace } +table.diff tbody tr:hover { + background-color:#F8EEC7; +} + +table.diff tbody tr:hover th { + background-color:#F6E8B5; +} +table.diff tbody tr:hover td { + background-color:#F6E8B5; +} table.diff tbody th { font-family:verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif; - background:#EED; + background-color:#FBFBFB; font-size:11px; font-weight:normal; - border:1px solid #BBC; + border-top:none; /* for overriding bootstrap */ color:#886; padding:.3em .5em .1em 2em; text-align:right; @@ -58,6 +68,7 @@ table.diff tbody td { padding:0px .4em; padding-top:.4em; vertical-align:top; + border-top: none; } table.diff .empty { background-color:#DDD; @@ -69,9 +80,10 @@ table.diff .delete { background-color:#FFDDDD; } table.diff .skip { - background-color:#EFEFEF; - border:1px solid #AAA; - border-right:1px solid #BBC; + background-color: #F8F8FF; +} +table.diff .skip:before { + content: " ..."; } table.diff .insert { background-color:#DDFFDD From 9ed2a50d26447164bd91a09be44a5a36947eaff9 Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Mon, 24 Feb 2014 22:45:10 +0900 Subject: [PATCH 04/91] Add SSH Service Listener --- .../scala/servlet/SshServiceListener.scala | 66 +++++++++++++++++++ src/main/webapp/WEB-INF/web.xml | 8 +++ 2 files changed, 74 insertions(+) create mode 100644 src/main/scala/servlet/SshServiceListener.scala diff --git a/src/main/scala/servlet/SshServiceListener.scala b/src/main/scala/servlet/SshServiceListener.scala new file mode 100644 index 000000000..c441e6da0 --- /dev/null +++ b/src/main/scala/servlet/SshServiceListener.scala @@ -0,0 +1,66 @@ +package servlet + +import javax.servlet.{ServletContextEvent, ServletContextListener} +import org.apache.sshd.SshServer +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider +import org.apache.sshd.server._ +import org.apache.sshd.server.session.ServerSession +import java.io.{OutputStream, InputStream} +import org.slf4j.LoggerFactory + +/* + * Start a SSH Service Daemon + */ +class SshServiceListener extends ServletContextListener { + private val logger = LoggerFactory.getLogger(classOf[SshServiceListener]) + + val sshService = SshServer.setUpDefaultServer + // TODO make configurable ssh feature + val enableSsh = true + + override def contextInitialized(sce: ServletContextEvent): Unit = { + if (!enableSsh) return + sshService.setPort(29418) + + val authenticator = new MyPasswordAuthenticator + sshService.setPasswordAuthenticator(authenticator) + // TODO gitbucket.ser should be in GITBUCKET_HOME + sshService.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("gitbucket.ser")) + sshService.setCommandFactory(new CommandFactory{ + override def createCommand(command: String): Command = { + logger.error("createCommand!") + return new Command{ + override def destroy(): Unit = {} + + override def start(env: Environment): Unit = { + logger.info("start command") + logger.info(env.getEnv.toString) + } + + override def setExitCallback(callback: ExitCallback): Unit = {} + + override def setErrorStream(err: OutputStream): Unit = {} + + override def setOutputStream(out: OutputStream): Unit = {} + + override def setInputStream(in: InputStream): Unit = {} + } + } + }) + sshService.start() + } + + override def contextDestroyed(sce: ServletContextEvent): Unit = { + sshService.stop(true) + } + +} + +class MyPasswordAuthenticator extends PasswordAuthenticator { + private val logger = LoggerFactory.getLogger(classOf[MyPasswordAuthenticator]) + + override def authenticate(username: String, password: String, session: ServerSession): Boolean = { + logger.error("authenticate!!!") + true + } +} diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index d57d64e16..25bc10e4e 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -18,6 +18,7 @@ servlet.AutoUpdateListener + @@ -25,6 +26,13 @@ org.scalatra.servlet.ScalatraListener + + + + + servlet.SshServiceListener + + From 891ca70ade9aea10068e6a545a86e198cb6e22f1 Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Thu, 27 Feb 2014 02:05:58 +0900 Subject: [PATCH 05/91] refs #115: Support git access via ssh (1st step) EXPERIMENTAL TODOs * Authentication (PublicKey) * Publickey management in profile view * Commit hooks integration (WebHook too) * Parse command correctly * Make Configurable many options(enable/disable, port, etc...) * ShellProcessFactory * Test, test, test... --- .../scala/servlet/SshServiceListener.scala | 124 +++++++++++++++--- 1 file changed, 104 insertions(+), 20 deletions(-) diff --git a/src/main/scala/servlet/SshServiceListener.scala b/src/main/scala/servlet/SshServiceListener.scala index c441e6da0..f3fa31f31 100644 --- a/src/main/scala/servlet/SshServiceListener.scala +++ b/src/main/scala/servlet/SshServiceListener.scala @@ -7,43 +7,49 @@ import org.apache.sshd.server._ import org.apache.sshd.server.session.ServerSession import java.io.{OutputStream, InputStream} import org.slf4j.LoggerFactory +import org.eclipse.jgit.transport.{ReceivePack, UploadPack} +import org.eclipse.jgit.api.Git +import util.Directory._ +import util.ControlUtil._ +import org.apache.sshd.server.command.UnknownCommand /* * Start a SSH Service Daemon + * + * How to use ? + * git clone ssh://username@host_or_ip:29418/username/repository_name.git + * */ class SshServiceListener extends ServletContextListener { + // TODO SshServer should be controllable by admin view (start and stop) + // TODO Use Singleton object SshServer instance management private val logger = LoggerFactory.getLogger(classOf[SshServiceListener]) val sshService = SshServer.setUpDefaultServer - // TODO make configurable ssh feature + + // TODO allow to disable ssh feature(ssh feature requires many stability test) val enableSsh = true override def contextInitialized(sce: ServletContextEvent): Unit = { if (!enableSsh) return - sshService.setPort(29418) + sshService.setPort(29418) // TODO must be configurable + + // TODO PasswordAuthentication should be disable + // TODO Use PublicKeyAuthentication only => and It's stored db on account profile view val authenticator = new MyPasswordAuthenticator sshService.setPasswordAuthenticator(authenticator) + // TODO gitbucket.ser should be in GITBUCKET_HOME sshService.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("gitbucket.ser")) - sshService.setCommandFactory(new CommandFactory{ + + sshService.setCommandFactory(new CommandFactory { override def createCommand(command: String): Command = { - logger.error("createCommand!") - return new Command{ - override def destroy(): Unit = {} - - override def start(env: Environment): Unit = { - logger.info("start command") - logger.info(env.getEnv.toString) - } - - override def setExitCallback(callback: ExitCallback): Unit = {} - - override def setErrorStream(err: OutputStream): Unit = {} - - override def setOutputStream(out: OutputStream): Unit = {} - - override def setInputStream(in: InputStream): Unit = {} + logger.info(s"command: String -> ${command}") + command match { + case s if s.startsWith("git-upload-pack ") => new GitUploadPack(command) + case s if s.startsWith("git-receive-pack") => new GitReceivePack(command) + case _ => new UnknownCommand(command) } } }) @@ -56,11 +62,89 @@ class SshServiceListener extends ServletContextListener { } +// always true authenticator... TODO Implements PublicKeyAuthenticator class MyPasswordAuthenticator extends PasswordAuthenticator { private val logger = LoggerFactory.getLogger(classOf[MyPasswordAuthenticator]) override def authenticate(username: String, password: String, session: ServerSession): Boolean = { - logger.error("authenticate!!!") + logger.info("noop authenticate!!!") true } } + +abstract class GitCommand(val command: String) extends Command { + private val logger = LoggerFactory.getLogger(classOf[GitCommand]) + val (gitCommand, owner, repositoryName) = parseCommand + var err: OutputStream = null + var in: InputStream = null + var out: OutputStream = null + var callback: ExitCallback = null + + def runnable: Runnable + + override def start(env: Environment): Unit = { + logger.info(s"start command : ${command}") + logger.info(s"parsed command : ${gitCommand}, ${owner}, ${repositoryName}") + val thread = new Thread(runnable) + thread.start + } + + override def destroy(): 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 + } + + private def parseCommand: Tuple3[String, String, String] = { + // command sample: git-upload-pack '/username/repository_name.git' + // command sample: git-receive-pack '/username/repository_name.git' + // TODO This is not correct.... + val splitted = command.split(" ") + val gitCommand = splitted(0) + val gitUser = splitted(1).substring(1, splitted(1).length - 5).split("/")(1) + val gitRepo = splitted(1).substring(1, splitted(1).length - 5).split("/")(2) + (gitCommand, gitUser, gitRepo) + } +} +class GitUploadPack(command: String) extends GitCommand(command: String) { + override val runnable = new Runnable { + override def run(): Unit = { + using(Git.open(getRepositoryDir(owner, repositoryName))) { + git => + val repository = git.getRepository + val upload = new UploadPack(repository) + upload.upload(in, out, err) + callback.onExit(0) + } + } + } +} + +class GitReceivePack(command: String) extends GitCommand(command: String) { + override val runnable = new Runnable { + override def run(): Unit = { + using(Git.open(getRepositoryDir(owner, repositoryName))) { + git => + val repository = git.getRepository + val receive = new ReceivePack(repository) + // TODO ReceivePack has many options. Need more check. + receive.receive(in, out, err) + callback.onExit(0) + } + } + } +} + From ceab1d2fd2b683da571765b8f44b59f1e2d4eb1d Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Fri, 28 Feb 2014 22:39:37 +0900 Subject: [PATCH 06/91] (refs #115) Reorganize ssh sources --- .../scala/servlet/SshServiceListener.scala | 150 ------------------ src/main/scala/ssh/GitCommand.scala | 101 ++++++++++++ .../scala/ssh/PublicKeyAuthenticator.scala | 24 +++ src/main/scala/ssh/SshServerListener.scala | 59 +++++++ src/main/webapp/WEB-INF/web.xml | 2 +- 5 files changed, 185 insertions(+), 151 deletions(-) delete mode 100644 src/main/scala/servlet/SshServiceListener.scala create mode 100644 src/main/scala/ssh/GitCommand.scala create mode 100644 src/main/scala/ssh/PublicKeyAuthenticator.scala create mode 100644 src/main/scala/ssh/SshServerListener.scala diff --git a/src/main/scala/servlet/SshServiceListener.scala b/src/main/scala/servlet/SshServiceListener.scala deleted file mode 100644 index f3fa31f31..000000000 --- a/src/main/scala/servlet/SshServiceListener.scala +++ /dev/null @@ -1,150 +0,0 @@ -package servlet - -import javax.servlet.{ServletContextEvent, ServletContextListener} -import org.apache.sshd.SshServer -import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider -import org.apache.sshd.server._ -import org.apache.sshd.server.session.ServerSession -import java.io.{OutputStream, InputStream} -import org.slf4j.LoggerFactory -import org.eclipse.jgit.transport.{ReceivePack, UploadPack} -import org.eclipse.jgit.api.Git -import util.Directory._ -import util.ControlUtil._ -import org.apache.sshd.server.command.UnknownCommand - -/* - * Start a SSH Service Daemon - * - * How to use ? - * git clone ssh://username@host_or_ip:29418/username/repository_name.git - * - */ -class SshServiceListener extends ServletContextListener { - // TODO SshServer should be controllable by admin view (start and stop) - // TODO Use Singleton object SshServer instance management - private val logger = LoggerFactory.getLogger(classOf[SshServiceListener]) - - val sshService = SshServer.setUpDefaultServer - - // TODO allow to disable ssh feature(ssh feature requires many stability test) - val enableSsh = true - - override def contextInitialized(sce: ServletContextEvent): Unit = { - if (!enableSsh) return - - sshService.setPort(29418) // TODO must be configurable - - // TODO PasswordAuthentication should be disable - // TODO Use PublicKeyAuthentication only => and It's stored db on account profile view - val authenticator = new MyPasswordAuthenticator - sshService.setPasswordAuthenticator(authenticator) - - // TODO gitbucket.ser should be in GITBUCKET_HOME - sshService.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("gitbucket.ser")) - - sshService.setCommandFactory(new CommandFactory { - override def createCommand(command: String): Command = { - logger.info(s"command: String -> ${command}") - command match { - case s if s.startsWith("git-upload-pack ") => new GitUploadPack(command) - case s if s.startsWith("git-receive-pack") => new GitReceivePack(command) - case _ => new UnknownCommand(command) - } - } - }) - sshService.start() - } - - override def contextDestroyed(sce: ServletContextEvent): Unit = { - sshService.stop(true) - } - -} - -// always true authenticator... TODO Implements PublicKeyAuthenticator -class MyPasswordAuthenticator extends PasswordAuthenticator { - private val logger = LoggerFactory.getLogger(classOf[MyPasswordAuthenticator]) - - override def authenticate(username: String, password: String, session: ServerSession): Boolean = { - logger.info("noop authenticate!!!") - true - } -} - -abstract class GitCommand(val command: String) extends Command { - private val logger = LoggerFactory.getLogger(classOf[GitCommand]) - val (gitCommand, owner, repositoryName) = parseCommand - var err: OutputStream = null - var in: InputStream = null - var out: OutputStream = null - var callback: ExitCallback = null - - def runnable: Runnable - - override def start(env: Environment): Unit = { - logger.info(s"start command : ${command}") - logger.info(s"parsed command : ${gitCommand}, ${owner}, ${repositoryName}") - val thread = new Thread(runnable) - thread.start - } - - override def destroy(): 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 - } - - private def parseCommand: Tuple3[String, String, String] = { - // command sample: git-upload-pack '/username/repository_name.git' - // command sample: git-receive-pack '/username/repository_name.git' - // TODO This is not correct.... - val splitted = command.split(" ") - val gitCommand = splitted(0) - val gitUser = splitted(1).substring(1, splitted(1).length - 5).split("/")(1) - val gitRepo = splitted(1).substring(1, splitted(1).length - 5).split("/")(2) - (gitCommand, gitUser, gitRepo) - } -} -class GitUploadPack(command: String) extends GitCommand(command: String) { - override val runnable = new Runnable { - override def run(): Unit = { - using(Git.open(getRepositoryDir(owner, repositoryName))) { - git => - val repository = git.getRepository - val upload = new UploadPack(repository) - upload.upload(in, out, err) - callback.onExit(0) - } - } - } -} - -class GitReceivePack(command: String) extends GitCommand(command: String) { - override val runnable = new Runnable { - override def run(): Unit = { - using(Git.open(getRepositoryDir(owner, repositoryName))) { - git => - val repository = git.getRepository - val receive = new ReceivePack(repository) - // TODO ReceivePack has many options. Need more check. - receive.receive(in, out, err) - callback.onExit(0) - } - } - } -} - diff --git a/src/main/scala/ssh/GitCommand.scala b/src/main/scala/ssh/GitCommand.scala new file mode 100644 index 000000000..26cb2e96a --- /dev/null +++ b/src/main/scala/ssh/GitCommand.scala @@ -0,0 +1,101 @@ +package ssh + +import org.apache.sshd.server.{CommandFactory, Environment, ExitCallback, Command} +import org.slf4j.LoggerFactory +import java.io.{InputStream, OutputStream} +import util.ControlUtil._ +import org.eclipse.jgit.api.Git +import util.Directory._ +import org.eclipse.jgit.transport.{ReceivePack, UploadPack} +import org.apache.sshd.server.command.UnknownCommand + + +class GitCommandFactory extends CommandFactory { + private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory]) + + override def createCommand(command: String): Command = { + logger.info(s"command: String -> " + command) + command match { + // TODO MUST use regular expression and UnitTest + case s if s.startsWith("git-upload-pack") => new GitUploadPack(command) + case s if s.startsWith("git-receive-pack") => new GitReceivePack(command) + case _ => new UnknownCommand(command) + } + } +} + +abstract class GitCommand(val command: String) extends Command { + private val logger = LoggerFactory.getLogger(classOf[GitCommand]) + protected val (gitCommand, owner, repositoryName) = parseCommand + protected var err: OutputStream = null + protected var in: InputStream = null + protected var out: OutputStream = null + protected var callback: ExitCallback = null + + protected def runnable: Runnable + + override def start(env: Environment): Unit = { + logger.info(s"start command : " + command) + logger.info(s"parsed command : $gitCommand, $owner, $repositoryName") + val thread = new Thread(runnable) + thread.start() + } + + override def destroy(): 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 + } + + private def parseCommand: (String, String, String) = { + // command sample: git-upload-pack '/username/repository_name.git' + // command sample: git-receive-pack '/username/repository_name.git' + // TODO This is not correct.... + val split = command.split(" ") + val gitCommand = split(0) + val gitUser = split(1).substring(1, split(1).length - 5).split("/")(1) + val gitRepo = split(1).substring(1, split(1).length - 5).split("/")(2) + (gitCommand, gitUser, gitRepo) + } +} + +class GitUploadPack(command: String) extends GitCommand(command: String) { + override def runnable = new Runnable { + override def run(): Unit = { + using(Git.open(getRepositoryDir(owner, repositoryName))) { git => + val repository = git.getRepository + val upload = new UploadPack(repository) + upload.upload(in, out, err) + callback.onExit(0) + } + } + } +} + +class GitReceivePack(command: String) extends GitCommand(command: String) { + override def runnable = new Runnable { + override def run(): Unit = { + using(Git.open(getRepositoryDir(owner, repositoryName))) { git => + val repository = git.getRepository + // TODO hook commit + val receive = new ReceivePack(repository) + receive.receive(in, out, err) + callback.onExit(0) + } + } + } +} + diff --git a/src/main/scala/ssh/PublicKeyAuthenticator.scala b/src/main/scala/ssh/PublicKeyAuthenticator.scala new file mode 100644 index 000000000..fb8222e9e --- /dev/null +++ b/src/main/scala/ssh/PublicKeyAuthenticator.scala @@ -0,0 +1,24 @@ +package ssh + +import org.apache.sshd.server.{PublickeyAuthenticator, PasswordAuthenticator} +import org.slf4j.LoggerFactory +import org.apache.sshd.server.session.ServerSession +import java.security.PublicKey + + +class PublicKeyAuthenticator extends PublickeyAuthenticator { + override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = { + // TODO Implements PublicKeyAuthenticator + true + } +} + +// always true authenticator... +class MyPasswordAuthenticator extends PasswordAuthenticator { + private val logger = LoggerFactory.getLogger(classOf[MyPasswordAuthenticator]) + + override def authenticate(username: String, password: String, session: ServerSession): Boolean = { + logger.info("noop authenticate!!!") + true + } +} \ No newline at end of file diff --git a/src/main/scala/ssh/SshServerListener.scala b/src/main/scala/ssh/SshServerListener.scala new file mode 100644 index 000000000..aa62c6173 --- /dev/null +++ b/src/main/scala/ssh/SshServerListener.scala @@ -0,0 +1,59 @@ +package ssh + +import javax.servlet.{ServletContextEvent, ServletContextListener} +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider +import org.slf4j.LoggerFactory + + +object SshServer { + private val logger = LoggerFactory.getLogger(SshServer.getClass) + + val DEFAULT_PORT: Int = 29418 // TODO read from config + val SSH_SERVICE_ENABLE = true + + private val server = org.apache.sshd.SshServer.setUpDefaultServer() + + + private def configure() = { + server.setPort(DEFAULT_PORT) + + // TODO not password use PublicKeyAuthenticator + val authenticator = new MyPasswordAuthenticator + server.setPasswordAuthenticator(authenticator) + + // TODO gitbucket.ser should be in GITBUCKET_HOME + server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("gitbucket.ser")) + server.setCommandFactory(new GitCommandFactory) + } + + def start() = { + if (SSH_SERVICE_ENABLE) { + configure() + server.start() + } + } + + def stop() = { + server.stop(true) + } +} + +/* + * Start a SSH Service Daemon + * + * How to use ? + * git clone ssh://username@host_or_ip:29418/username/repository_name.git + * + */ +class SshServerListener extends ServletContextListener { + override def contextInitialized(sce: ServletContextEvent): Unit = { + SshServer.start + } + + override def contextDestroyed(sce: ServletContextEvent): Unit = { + SshServer.stop + } + +} + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 25bc10e4e..398ffd867 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -30,7 +30,7 @@ - servlet.SshServiceListener + ssh.SshServerListener From c5aee0810cb1617f0fa4276b3298c1ff1b02bfae Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Sat, 1 Mar 2014 01:00:00 +0900 Subject: [PATCH 07/91] (refs #115) Add PublicKeyAuthenticator sample impl Auth TODOs * Fetch user account pubkeys from DB --- src/main/scala/ssh/GitCommand.scala | 1 - .../scala/ssh/PublicKeyAuthenticator.scala | 39 ++++++++++++------- src/main/scala/ssh/SshServerListener.scala | 19 +++++---- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/main/scala/ssh/GitCommand.scala b/src/main/scala/ssh/GitCommand.scala index 26cb2e96a..06e450a69 100644 --- a/src/main/scala/ssh/GitCommand.scala +++ b/src/main/scala/ssh/GitCommand.scala @@ -98,4 +98,3 @@ class GitReceivePack(command: String) extends GitCommand(command: String) { } } } - diff --git a/src/main/scala/ssh/PublicKeyAuthenticator.scala b/src/main/scala/ssh/PublicKeyAuthenticator.scala index fb8222e9e..a3c8ef04d 100644 --- a/src/main/scala/ssh/PublicKeyAuthenticator.scala +++ b/src/main/scala/ssh/PublicKeyAuthenticator.scala @@ -3,22 +3,35 @@ package ssh import org.apache.sshd.server.{PublickeyAuthenticator, PasswordAuthenticator} import org.slf4j.LoggerFactory import org.apache.sshd.server.session.ServerSession -import java.security.PublicKey +import java.security.{KeyFactory, PublicKey} +import org.apache.commons.codec.binary.Base64 +import java.security.spec.X509EncodedKeySpec +import org.apache.sshd.common.util.Buffer class PublicKeyAuthenticator extends PublickeyAuthenticator { + private val logger = LoggerFactory.getLogger(classOf[PublicKeyAuthenticator]) + override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = { - // TODO Implements PublicKeyAuthenticator - true + // TODO this string is read from DB and Users register this public key string on Account Profile view + val testAuthkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRzuX0WtSLzCY45nEhfFDPXzYGmvQdqnOgOUY4yGL5io/2ztyUvJdhWowkyakeoPxVk/jIP7Tu8Are5TuSD+fJp7aUbZW2CYOEsxo8cwndh/ezIX6RFjlu+xvKvZ8G7BtFLlLCcnza9uB+uEAyPH5HvGQLdV7dXctLfFqXPTr1p1RjSI7Noubm+vN4n9108rILd32MlhQiToXjL4HKWWwmppaln6bEsonOQW4/GieRjQeyWDkbVekIofnedjWl4+W0kAA+WosNwRFShgsaJLfU964HT/cGjK5auqOG+nATY0suECnxAK+5Wb6jXXYNmKiIMHypeXG1Qy2wMyMB1Gq9 tanacasino-local" + toPublicKey(testAuthkey) match { + case Some(publicKey) => key.equals(publicKey) + case _ => false + } + } + + private def toPublicKey(key: String): Option[PublicKey] = { + try { + val parts = key.split(" ") + val encodedKey = key.split(" ")(1) + val decode = Base64.decodeBase64(encodedKey) + Some(new Buffer(decode).getRawPublicKey) + } catch { + case e: Throwable => { + logger.error(e.getMessage, e) + None + } + } } } - -// always true authenticator... -class MyPasswordAuthenticator extends PasswordAuthenticator { - private val logger = LoggerFactory.getLogger(classOf[MyPasswordAuthenticator]) - - override def authenticate(username: String, password: String, session: ServerSession): Boolean = { - logger.info("noop authenticate!!!") - true - } -} \ No newline at end of file diff --git a/src/main/scala/ssh/SshServerListener.scala b/src/main/scala/ssh/SshServerListener.scala index aa62c6173..0d3633946 100644 --- a/src/main/scala/ssh/SshServerListener.scala +++ b/src/main/scala/ssh/SshServerListener.scala @@ -16,13 +16,10 @@ object SshServer { private def configure() = { server.setPort(DEFAULT_PORT) - - // TODO not password use PublicKeyAuthenticator - val authenticator = new MyPasswordAuthenticator - server.setPasswordAuthenticator(authenticator) - // TODO gitbucket.ser should be in GITBUCKET_HOME server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("gitbucket.ser")) + + server.setPublickeyAuthenticator(new PublicKeyAuthenticator) server.setCommandFactory(new GitCommandFactory) } @@ -30,6 +27,7 @@ object SshServer { if (SSH_SERVICE_ENABLE) { configure() server.start() + logger.info(s"Start SSH Server Listen on ${server.getPort}") } } @@ -39,19 +37,20 @@ object SshServer { } /* - * Start a SSH Service Daemon + * Start a SSH Server Daemon * - * How to use ? - * git clone ssh://username@host_or_ip:29418/username/repository_name.git + * How to use: + * git clone ssh://username@host_or_ip:29418/owner/repository_name.git * */ class SshServerListener extends ServletContextListener { + override def contextInitialized(sce: ServletContextEvent): Unit = { - SshServer.start + SshServer.start() } override def contextDestroyed(sce: ServletContextEvent): Unit = { - SshServer.stop + SshServer.stop() } } From 2e236e90ba9c91d35a8a811bedf6ff2dd98614e7 Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Sat, 1 Mar 2014 02:09:24 +0900 Subject: [PATCH 08/91] (refs #110) Add CommitHook sample WIP: Some important functions are not implement yet. --- src/main/scala/ssh/GitCommand.scala | 18 +++++++++++++++--- src/main/scala/ssh/SshServerListener.scala | 12 +++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/main/scala/ssh/GitCommand.scala b/src/main/scala/ssh/GitCommand.scala index 06e450a69..f855168bd 100644 --- a/src/main/scala/ssh/GitCommand.scala +++ b/src/main/scala/ssh/GitCommand.scala @@ -8,6 +8,9 @@ import org.eclipse.jgit.api.Git import util.Directory._ import org.eclipse.jgit.transport.{ReceivePack, UploadPack} import org.apache.sshd.server.command.UnknownCommand +import servlet.{Database, CommitLogHook} +import service.SystemSettingsService.SystemSettings +import service.SystemSettingsService class GitCommandFactory extends CommandFactory { @@ -85,16 +88,25 @@ class GitUploadPack(command: String) extends GitCommand(command: String) { } } -class GitReceivePack(command: String) extends GitCommand(command: String) { +class GitReceivePack(command: String) extends GitCommand(command: String) with SystemSettingsService { override def runnable = new Runnable { + + // TODO correct this info + val pusher: String = "user1" + val baseURL: String = loadSystemSettings().baseUrl.getOrElse("http://localhost:8080") + override def run(): Unit = { using(Git.open(getRepositoryDir(owner, repositoryName))) { git => val repository = git.getRepository // TODO hook commit val receive = new ReceivePack(repository) - receive.receive(in, out, err) - callback.onExit(0) + receive.setPostReceiveHook(new CommitLogHook(owner, repositoryName, pusher, baseURL)) + Database(SshServer.getServletContext) withTransaction { + receive.receive(in, out, err) + callback.onExit(0) + } } } } + } diff --git a/src/main/scala/ssh/SshServerListener.scala b/src/main/scala/ssh/SshServerListener.scala index 0d3633946..dda7dcd8d 100644 --- a/src/main/scala/ssh/SshServerListener.scala +++ b/src/main/scala/ssh/SshServerListener.scala @@ -1,6 +1,6 @@ package ssh -import javax.servlet.{ServletContextEvent, ServletContextListener} +import javax.servlet.{ServletContext, ServletContextEvent, ServletContextListener} import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider import org.slf4j.LoggerFactory @@ -13,6 +13,9 @@ object SshServer { private val server = org.apache.sshd.SshServer.setUpDefaultServer() + // TODO think other way to create database session + private var context: ServletContext = null + private def configure() = { server.setPort(DEFAULT_PORT) @@ -23,8 +26,9 @@ object SshServer { server.setCommandFactory(new GitCommandFactory) } - def start() = { + def start(context: ServletContext) = { if (SSH_SERVICE_ENABLE) { + this.context = context configure() server.start() logger.info(s"Start SSH Server Listen on ${server.getPort}") @@ -34,6 +38,8 @@ object SshServer { def stop() = { server.stop(true) } + + def getServletContext = this.context; } /* @@ -46,7 +52,7 @@ object SshServer { class SshServerListener extends ServletContextListener { override def contextInitialized(sce: ServletContextEvent): Unit = { - SshServer.start() + SshServer.start(sce.getServletContext()) } override def contextDestroyed(sce: ServletContextEvent): Unit = { From 0961eb59761327ffee50cfaefec3bb6c3d5caca9 Mon Sep 17 00:00:00 2001 From: takezoe Date: Sun, 2 Mar 2014 23:55:03 +0900 Subject: [PATCH 09/91] (refs #279)Fix redirect url generation. --- src/main/scala/app/ControllerBase.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/app/ControllerBase.scala b/src/main/scala/app/ControllerBase.scala index cf91fea25..5e60eb42c 100644 --- a/src/main/scala/app/ControllerBase.scala +++ b/src/main/scala/app/ControllerBase.scala @@ -116,7 +116,7 @@ abstract class ControllerBase extends ScalatraFilter includeContextPath: Boolean = true, includeServletPath: Boolean = true) (implicit request: HttpServletRequest, response: HttpServletResponse) = if (path.startsWith("http")) path - else baseUrl + url(path, params, includeContextPath, includeServletPath) + else baseUrl + url(path, params, false, false) } From 527fd94145f86f5e72f07133e1d71d4deb7847a4 Mon Sep 17 00:00:00 2001 From: eiryu Date: Mon, 3 Mar 2014 00:28:48 +0900 Subject: [PATCH 10/91] fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41c57d1a8..43470be57 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Following features are not implemented, but we will make them in the future rele - File editing in repository viewer - Comment for the changeset - Network graph -- Statics +- Statistics - Watch / Star If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/takezoe/gitbucket/wiki). From 270eb7cf1ddbd2111df19dad56b7d61fdd8c4bc5 Mon Sep 17 00:00:00 2001 From: takezoe Date: Mon, 3 Mar 2014 01:17:52 +0900 Subject: [PATCH 11/91] (refs #198)Allow create group by normal users. --- .../app/CreateRepositoryController.scala | 96 ++++++++++++-- src/main/twirl/account/main.scala.html | 7 + src/main/twirl/group.scala.html | 121 ++++++++++++++++++ src/main/twirl/main.scala.html | 6 +- 4 files changed, 217 insertions(+), 13 deletions(-) create mode 100644 src/main/twirl/group.scala.html diff --git a/src/main/scala/app/CreateRepositoryController.scala b/src/main/scala/app/CreateRepositoryController.scala index 16cd05c95..90e9ac3ae 100644 --- a/src/main/scala/app/CreateRepositoryController.scala +++ b/src/main/scala/app/CreateRepositoryController.scala @@ -9,35 +9,59 @@ import jp.sf.amateras.scalatra.forms._ import org.eclipse.jgit.lib.{FileMode, Constants} import org.eclipse.jgit.dircache.DirCache import org.scalatra.i18n.Messages +import org.apache.commons.io.FileUtils class CreateRepositoryController extends CreateRepositoryControllerBase with RepositoryService with AccountService with WikiService with LabelsService with ActivityService with UsersAuthenticator with ReadableUsersAuthenticator /** - * Creates new repository. + * Creates new repository or group. */ -trait CreateRepositoryControllerBase extends ControllerBase { +trait CreateRepositoryControllerBase extends AccountManagementControllerBase { self: RepositoryService with AccountService with WikiService with LabelsService with ActivityService with UsersAuthenticator with ReadableUsersAuthenticator => - case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) + case class RepositoryCreationForm(owner: String, name: String, description: Option[String], + isPrivate: Boolean, createReadme: Boolean) case class ForkRepositoryForm(owner: String, name: String) - val newForm = mapping( + val newRepositoryForm = mapping( "owner" -> trim(label("Owner" , text(required, maxlength(40), identifier, existsAccount))), - "name" -> trim(label("Repository name", text(required, maxlength(40), identifier, unique))), + "name" -> trim(label("Repository name", text(required, maxlength(40), identifier, uniqueRepository))), "description" -> trim(label("Description" , optional(text()))), "isPrivate" -> trim(label("Repository Type", boolean())), "createReadme" -> trim(label("Create README" , boolean())) )(RepositoryCreationForm.apply) - val forkForm = mapping( + val forkRepositoryForm = mapping( "owner" -> trim(label("Repository owner", text(required))), "name" -> trim(label("Repository name", text(required))) )(ForkRepositoryForm.apply) + case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String], + memberNames: Option[String]) + + case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], + memberNames: Option[String], clearImage: Boolean, isRemoved: Boolean) + + val newGroupForm = mapping( + "groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))), + "url" -> trim(label("URL" ,optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID" ,optional(text()))), + "memberNames" -> trim(label("Member Names" ,optional(text()))) + )(NewGroupForm.apply) + + val editGroupForm = mapping( + "groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))), + "url" -> trim(label("URL" ,optional(text(maxlength(200))))), + "fileId" -> trim(label("File ID" ,optional(text()))), + "memberNames" -> trim(label("Member Names" ,optional(text()))), + "clearImage" -> trim(label("Clear image" ,boolean())), + "removed" -> trim(label("Disable" ,boolean())) + )(EditGroupForm.apply) + /** * Show the new repository form. */ @@ -48,7 +72,7 @@ trait CreateRepositoryControllerBase extends ControllerBase { /** * Create new repository. */ - post("/new", newForm)(usersOnly { form => + post("/new", newRepositoryForm)(usersOnly { form => LockUtil.lock(s"${form.owner}/${form.name}/create"){ if(getRepository(form.owner, form.name, baseUrl).isEmpty){ val ownerAccount = getAccountByUserName(form.owner).get @@ -172,6 +196,57 @@ trait CreateRepositoryControllerBase extends ControllerBase { } }) + get("/groups/new")(usersOnly { + html.group(None, Nil) + }) + + post("/groups/new", newGroupForm)(usersOnly { form => + createGroup(form.groupName, form.url) + updateGroupMembers(form.groupName, form.memberNames.map(_.split(",").toList).getOrElse(Nil)) + updateImage(form.groupName, form.fileId, false) + redirect(s"/${form.groupName}") + }) + + get("/:groupName/_edit")(usersOnly { // TODO group manager only + defining(params("groupName")){ groupName => + html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName)) + } + }) + + post("/:groupName/_edit", editGroupForm)(usersOnly { form => // TODO group manager only + defining(params("groupName"), form.memberNames.map(_.split(",").toList).getOrElse(Nil)){ case (groupName, memberNames) => + getAccountByUserName(groupName, true).map { account => + updateGroup(groupName, form.url, form.isRemoved) + + if(form.isRemoved){ + // Remove from GROUP_MEMBER + updateGroupMembers(form.groupName, Nil) + // Remove repositories + getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => + deleteRepository(groupName, repositoryName) + FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName)) + FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName)) + FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName)) + } + } else { + // Update GROUP_MEMBER + updateGroupMembers(form.groupName, memberNames) + // Update COLLABORATOR for group repositories + getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => + removeCollaborators(form.groupName, repositoryName) + memberNames.foreach { userName => + addCollaborator(form.groupName, repositoryName, userName) + } + } + } + + updateImage(form.groupName, form.fileId, form.clearImage) + redirect(s"/${form.groupName}") + + } getOrElse NotFound + } + }) + private def insertDefaultLabels(userName: String, repositoryName: String): Unit = { createLabel(userName, repositoryName, "bug", "fc2929") createLabel(userName, repositoryName, "duplicate", "cccccc") @@ -186,14 +261,11 @@ trait CreateRepositoryControllerBase extends ControllerBase { if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None } - /** - * Duplicate check for the repository name. - */ - private def unique: Constraint = new Constraint(){ + private def uniqueRepository: Constraint = new Constraint(){ override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] = params.get("owner").flatMap { userName => getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.") } } - + } diff --git a/src/main/twirl/account/main.scala.html b/src/main/twirl/account/main.scala.html index 0b6cc6e6d..efc844102 100644 --- a/src/main/twirl/account/main.scala.html +++ b/src/main/twirl/account/main.scala.html @@ -41,6 +41,13 @@ } + @if(loginAccount.isDefined && account.isGroupAccount){ +
  • + +
  • + } @body diff --git a/src/main/twirl/group.scala.html b/src/main/twirl/group.scala.html new file mode 100644 index 000000000..4cc5474a2 --- /dev/null +++ b/src/main/twirl/group.scala.html @@ -0,0 +1,121 @@ +@(account: Option[model.Account], members: List[String])(implicit context: app.Context) +@import context._ +@import view.helpers._ +@main("Create a group"){ +
    +
    +
    +
    +
    + +
    + +
    + + @if(account.isDefined){ + + } +
    +
    + +
    + +
    + +
    +
    + + @helper.html.uploadavatar(account) +
    +
    +
    +
    + + + @helper.html.account("memberName", 200) + + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +} + \ No newline at end of file diff --git a/src/main/twirl/main.scala.html b/src/main/twirl/main.scala.html index 7ec984289..5c08ab12a 100644 --- a/src/main/twirl/main.scala.html +++ b/src/main/twirl/main.scala.html @@ -54,7 +54,11 @@ } @if(loginAccount.isDefined){ @avatar(loginAccount.get.userName, 20) @loginAccount.get.userName - + + @if(loginAccount.get.isAdmin){ From d870896cfb4ee786b44a7df58196c7f85ae4a3f2 Mon Sep 17 00:00:00 2001 From: takezoe Date: Mon, 3 Mar 2014 01:21:22 +0900 Subject: [PATCH 12/91] Rename CreateRepositoryController to CreateController. --- src/main/scala/ScalatraBootstrap.scala | 2 +- ...reateRepositoryController.scala => CreateController.scala} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/main/scala/app/{CreateRepositoryController.scala => CreateController.scala} (98%) diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index de3c40d04..b03456578 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -20,7 +20,7 @@ class ScalatraBootstrap extends LifeCycle { context.mount(new DashboardController, "/*") context.mount(new UserManagementController, "/*") context.mount(new SystemSettingsController, "/*") - context.mount(new CreateRepositoryController, "/*") + context.mount(new CreateController, "/*") context.mount(new AccountController, "/*") context.mount(new RepositoryViewerController, "/*") context.mount(new WikiController, "/*") diff --git a/src/main/scala/app/CreateRepositoryController.scala b/src/main/scala/app/CreateController.scala similarity index 98% rename from src/main/scala/app/CreateRepositoryController.scala rename to src/main/scala/app/CreateController.scala index 90e9ac3ae..022277ed7 100644 --- a/src/main/scala/app/CreateRepositoryController.scala +++ b/src/main/scala/app/CreateController.scala @@ -11,14 +11,14 @@ import org.eclipse.jgit.dircache.DirCache import org.scalatra.i18n.Messages import org.apache.commons.io.FileUtils -class CreateRepositoryController extends CreateRepositoryControllerBase +class CreateController extends CreateControllerBase with RepositoryService with AccountService with WikiService with LabelsService with ActivityService with UsersAuthenticator with ReadableUsersAuthenticator /** * Creates new repository or group. */ -trait CreateRepositoryControllerBase extends AccountManagementControllerBase { +trait CreateControllerBase extends AccountManagementControllerBase { self: RepositoryService with AccountService with WikiService with LabelsService with ActivityService with UsersAuthenticator with ReadableUsersAuthenticator => From 17920e119571e65d3731025e37a2b557bfb5f2f6 Mon Sep 17 00:00:00 2001 From: takezoe Date: Mon, 3 Mar 2014 01:45:00 +0900 Subject: [PATCH 13/91] (refs #198)Allow group editing by group members. --- src/main/scala/app/AccountController.scala | 14 ++++++++++---- src/main/scala/app/CreateController.scala | 8 ++++---- src/main/scala/util/Authenticator.scala | 19 +++++++++++++++++++ src/main/twirl/account/main.scala.html | 5 +++-- src/main/twirl/account/members.scala.html | 4 ++-- .../twirl/account/repositories.scala.html | 6 ++++-- src/main/twirl/group.scala.html | 2 +- 7 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala index df24aad3f..7fc358901 100644 --- a/src/main/scala/app/AccountController.scala +++ b/src/main/scala/app/AccountController.scala @@ -51,14 +51,20 @@ trait AccountControllerBase extends AccountManagementControllerBase { getActivitiesByUser(userName, true)) // Members - case "members" if(account.isGroupAccount) => - _root_.account.html.members(account, getGroupMembers(account.userName)) + case "members" if(account.isGroupAccount) => { + val members = getGroupMembers(account.userName) + _root_.account.html.members(account, members, + context.loginAccount.exists(x => members.contains(x.userName))) + } // Repositories - case _ => + case _ => { + val members = getGroupMembers(account.userName) _root_.account.html.repositories(account, if(account.isGroupAccount) Nil else getGroupsByUserName(userName), - getVisibleRepositories(context.loginAccount, baseUrl, Some(userName))) + getVisibleRepositories(context.loginAccount, baseUrl, Some(userName)), + context.loginAccount.exists(x => members.contains(x.userName))) + } } } getOrElse NotFound } diff --git a/src/main/scala/app/CreateController.scala b/src/main/scala/app/CreateController.scala index 022277ed7..ccfd37b20 100644 --- a/src/main/scala/app/CreateController.scala +++ b/src/main/scala/app/CreateController.scala @@ -13,14 +13,14 @@ import org.apache.commons.io.FileUtils class CreateController extends CreateControllerBase with RepositoryService with AccountService with WikiService with LabelsService with ActivityService - with UsersAuthenticator with ReadableUsersAuthenticator + with UsersAuthenticator with ReadableUsersAuthenticator with GroupMemberAuthenticator /** * Creates new repository or group. */ trait CreateControllerBase extends AccountManagementControllerBase { self: RepositoryService with AccountService with WikiService with LabelsService with ActivityService - with UsersAuthenticator with ReadableUsersAuthenticator => + with UsersAuthenticator with ReadableUsersAuthenticator with GroupMemberAuthenticator => case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) @@ -207,13 +207,13 @@ trait CreateControllerBase extends AccountManagementControllerBase { redirect(s"/${form.groupName}") }) - get("/:groupName/_edit")(usersOnly { // TODO group manager only + get("/:groupName/_edit")(membersOnly { defining(params("groupName")){ groupName => html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName)) } }) - post("/:groupName/_edit", editGroupForm)(usersOnly { form => // TODO group manager only + post("/:groupName/_edit", editGroupForm)(membersOnly { form => defining(params("groupName"), form.memberNames.map(_.split(",").toList).getOrElse(Nil)){ case (groupName, memberNames) => getAccountByUserName(groupName, true).map { account => updateGroup(groupName, form.url, form.isRemoved) diff --git a/src/main/scala/util/Authenticator.scala b/src/main/scala/util/Authenticator.scala index c5247137c..c43b0ec17 100644 --- a/src/main/scala/util/Authenticator.scala +++ b/src/main/scala/util/Authenticator.scala @@ -155,3 +155,22 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService = } } } + +/** + * Allows only the group members. + */ +trait GroupMemberAuthenticator { self: ControllerBase with AccountService => + protected def membersOnly(action: => Any) = { authenticate(action) } + protected def membersOnly[T](action: T => Any) = (form: T) => { authenticate(action(form)) } + + private def authenticate(action: => Any) = { + { + defining(request.paths){ paths => + context.loginAccount match { + case Some(x) if(getGroupMembers(paths(0)).contains(x.userName)) => action + case _ => Unauthorized() + } + } + } + } +} diff --git a/src/main/twirl/account/main.scala.html b/src/main/twirl/account/main.scala.html index efc844102..2cbe2577c 100644 --- a/src/main/twirl/account/main.scala.html +++ b/src/main/twirl/account/main.scala.html @@ -1,4 +1,5 @@ -@(account: model.Account, groupNames: List[String], active: String)(body: Html)(implicit context: app.Context) +@(account: model.Account, groupNames: List[String], active: String, + isGroupMember: Boolean = false)(body: Html)(implicit context: app.Context) @import context._ @import view.helpers._ @html.main(account.userName){ @@ -41,7 +42,7 @@ } - @if(loginAccount.isDefined && account.isGroupAccount){ + @if(loginAccount.isDefined && account.isGroupAccount && isGroupMember){
  • Edit Group diff --git a/src/main/twirl/account/members.scala.html b/src/main/twirl/account/members.scala.html index 14d7c773f..b69fc0af0 100644 --- a/src/main/twirl/account/members.scala.html +++ b/src/main/twirl/account/members.scala.html @@ -1,7 +1,7 @@ -@(account: model.Account, members: List[String])(implicit context: app.Context) +@(account: model.Account, members: List[String], isGroupMember: Boolean)(implicit context: app.Context) @import context._ @import view.helpers._ -@main(account, Nil, "members"){ +@main(account, Nil, "members", isGroupMember){ @if(members.isEmpty){ No members } else { diff --git a/src/main/twirl/account/repositories.scala.html b/src/main/twirl/account/repositories.scala.html index f9037f568..7772088ca 100644 --- a/src/main/twirl/account/repositories.scala.html +++ b/src/main/twirl/account/repositories.scala.html @@ -1,7 +1,9 @@ -@(account: model.Account, groupNames: List[String], repositories: List[service.RepositoryService.RepositoryInfo])(implicit context: app.Context) +@(account: model.Account, groupNames: List[String], + repositories: List[service.RepositoryService.RepositoryInfo], + isGroupMember: Boolean)(implicit context: app.Context) @import context._ @import view.helpers._ -@main(account, groupNames, "repositories"){ +@main(account, groupNames, "repositories", isGroupMember){ @if(repositories.isEmpty){ No repositories } else { diff --git a/src/main/twirl/group.scala.html b/src/main/twirl/group.scala.html index 4cc5474a2..ed70394e2 100644 --- a/src/main/twirl/group.scala.html +++ b/src/main/twirl/group.scala.html @@ -3,7 +3,7 @@ @import view.helpers._ @main("Create a group"){
    -
    +
    From 607c477e7dec6fe4020a432779343e6666b68412 Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 4 Mar 2014 02:39:18 +0900 Subject: [PATCH 14/91] Add options for remote debugging --- sbt.bat | 2 +- sbt.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sbt.bat b/sbt.bat index d86d1e05b..41a5c115a 100644 --- a/sbt.bat +++ b/sbt.bat @@ -1,2 +1,2 @@ set SCRIPT_DIR=%~dp0 -java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -jar "%SCRIPT_DIR%\sbt-launch-0.12.3.jar" %* +java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.12.3.jar" %* diff --git a/sbt.sh b/sbt.sh index 23c721f7d..cd2266f44 100755 --- a/sbt.sh +++ b/sbt.sh @@ -1 +1 @@ -java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -jar `dirname $0`/sbt-launch-0.12.3.jar "$@" +java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.12.3.jar "$@" From 5cf96134d5b453a1620ac5b254ccd1d4ed3a9ffe Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 4 Mar 2014 03:25:18 +0900 Subject: [PATCH 15/91] (refs #296)Fix redirection path generation again --- src/main/scala/app/ControllerBase.scala | 9 ++++++++- src/main/twirl/main.scala.html | 3 +-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/scala/app/ControllerBase.scala b/src/main/scala/app/ControllerBase.scala index 5e60eb42c..81e1dca83 100644 --- a/src/main/scala/app/ControllerBase.scala +++ b/src/main/scala/app/ControllerBase.scala @@ -38,12 +38,15 @@ abstract class ControllerBase extends ScalatraFilter val account = httpRequest.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account] if(account == null){ // Redirect to login form + // TODO Should use the configured base url. httpResponse.sendRedirect(context + "/signin?" + StringUtil.urlEncode(path)) } else if(account.isAdmin){ // H2 Console (administrators only) + // TODO Should use the configured base url. chain.doFilter(request, response) } else { // Redirect to dashboard + // TODO Should use the configured base url. httpResponse.sendRedirect(context + "/") } } else if(path.startsWith("/git/")){ @@ -116,15 +119,19 @@ abstract class ControllerBase extends ScalatraFilter includeContextPath: Boolean = true, includeServletPath: Boolean = true) (implicit request: HttpServletRequest, response: HttpServletResponse) = if (path.startsWith("http")) path - else baseUrl + url(path, params, false, false) + else baseUrl + url(path, params, false, false, false) } /** * Context object for the current request. + * + * @param path the context path */ case class Context(path: String, loginAccount: Option[Account], request: HttpServletRequest){ + lazy val currentPath = request.getRequestURI.substring(path.length) + /** * Get object from cache. * diff --git a/src/main/twirl/main.scala.html b/src/main/twirl/main.scala.html index 7ec984289..0d73555ea 100644 --- a/src/main/twirl/main.scala.html +++ b/src/main/twirl/main.scala.html @@ -61,7 +61,7 @@ } } else { - Sign in + Sign in }
    @@ -76,7 +76,6 @@ $('#search').submit(function(){ return $.trim($(this).find('input[name=query]').val()) != ''; }); - $('#signin').attr('href', '@path/signin?redirect=' + encodeURIComponent(location.pathname + location.search + location.hash)); }); From e3fd564efd874c2b2f419cdb72db2ab95f73255d Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 4 Mar 2014 04:25:44 +0900 Subject: [PATCH 16/91] (refs #241)Work for specifying group manager --- src/main/resources/update/1_12.sql | 1 + src/main/scala/app/AccountController.scala | 6 ++--- src/main/scala/app/CreateController.scala | 26 ++++++++++++------- .../scala/app/UserManagementController.scala | 18 +++++++++---- src/main/scala/model/GroupMembers.scala | 6 +++-- src/main/scala/service/AccountService.scala | 10 +++---- .../scala/servlet/AutoUpdateListener.scala | 1 + src/main/scala/util/Authenticator.scala | 10 +++---- src/main/twirl/account/main.scala.html | 4 +-- src/main/twirl/account/members.scala.html | 4 +-- .../twirl/account/repositories.scala.html | 4 +-- src/main/twirl/admin/users/group.scala.html | 4 +-- src/main/twirl/group.scala.html | 12 ++++++--- 13 files changed, 65 insertions(+), 41 deletions(-) create mode 100644 src/main/resources/update/1_12.sql diff --git a/src/main/resources/update/1_12.sql b/src/main/resources/update/1_12.sql new file mode 100644 index 000000000..00301693c --- /dev/null +++ b/src/main/resources/update/1_12.sql @@ -0,0 +1 @@ +ALTER TABLE GROUP_MEMBER ADD COLUMN MANAGER BOOLEAN DEFAULT FALSE; \ No newline at end of file diff --git a/src/main/scala/app/AccountController.scala b/src/main/scala/app/AccountController.scala index 7fc358901..e1c293f77 100644 --- a/src/main/scala/app/AccountController.scala +++ b/src/main/scala/app/AccountController.scala @@ -53,8 +53,8 @@ trait AccountControllerBase extends AccountManagementControllerBase { // Members case "members" if(account.isGroupAccount) => { val members = getGroupMembers(account.userName) - _root_.account.html.members(account, members, - context.loginAccount.exists(x => members.contains(x.userName))) + _root_.account.html.members(account, members.map(_._1), + context.loginAccount.exists(x => members.exists { case (userName, isManager) => userName == x.userName && isManager })) } // Repositories @@ -63,7 +63,7 @@ trait AccountControllerBase extends AccountManagementControllerBase { _root_.account.html.repositories(account, if(account.isGroupAccount) Nil else getGroupsByUserName(userName), getVisibleRepositories(context.loginAccount, baseUrl, Some(userName)), - context.loginAccount.exists(x => members.contains(x.userName))) + context.loginAccount.exists(x => members.exists { case (userName, isManager) => userName == x.userName && isManager })) } } } getOrElse NotFound diff --git a/src/main/scala/app/CreateController.scala b/src/main/scala/app/CreateController.scala index ccfd37b20..7b2acbb4b 100644 --- a/src/main/scala/app/CreateController.scala +++ b/src/main/scala/app/CreateController.scala @@ -13,14 +13,14 @@ import org.apache.commons.io.FileUtils class CreateController extends CreateControllerBase with RepositoryService with AccountService with WikiService with LabelsService with ActivityService - with UsersAuthenticator with ReadableUsersAuthenticator with GroupMemberAuthenticator + with UsersAuthenticator with ReadableUsersAuthenticator with GroupManagerAuthenticator /** * Creates new repository or group. */ trait CreateControllerBase extends AccountManagementControllerBase { self: RepositoryService with AccountService with WikiService with LabelsService with ActivityService - with UsersAuthenticator with ReadableUsersAuthenticator with GroupMemberAuthenticator => + with UsersAuthenticator with ReadableUsersAuthenticator with GroupManagerAuthenticator => case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) @@ -84,7 +84,7 @@ trait CreateControllerBase extends AccountManagementControllerBase { // Add collaborators for group repository if(ownerAccount.isGroupAccount){ - getGroupMembers(form.owner).foreach { userName => + getGroupMembers(form.owner).foreach { case (userName, isManager) => addCollaborator(form.owner, form.name, userName) } } @@ -202,19 +202,27 @@ trait CreateControllerBase extends AccountManagementControllerBase { post("/groups/new", newGroupForm)(usersOnly { form => createGroup(form.groupName, form.url) - updateGroupMembers(form.groupName, form.memberNames.map(_.split(",").toList).getOrElse(Nil)) + updateGroupMembers(form.groupName, form.memberNames.map(_.split(",").map { + _.split(":") match { + case Array(userName, isManager) => (userName, isManager.toBoolean) + } + }.toList).getOrElse(Nil)) updateImage(form.groupName, form.fileId, false) redirect(s"/${form.groupName}") }) - get("/:groupName/_edit")(membersOnly { + get("/:groupName/_edit")(managersOnly { defining(params("groupName")){ groupName => html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName)) } }) - post("/:groupName/_edit", editGroupForm)(membersOnly { form => - defining(params("groupName"), form.memberNames.map(_.split(",").toList).getOrElse(Nil)){ case (groupName, memberNames) => + post("/:groupName/_edit", editGroupForm)(managersOnly { form => + defining(params("groupName"), form.memberNames.map(_.split(",").map { + _.split(":") match { + case Array(userName, isManager) => (userName, isManager.toBoolean) + } + }.toList).getOrElse(Nil)){ case (groupName, members) => getAccountByUserName(groupName, true).map { account => updateGroup(groupName, form.url, form.isRemoved) @@ -230,11 +238,11 @@ trait CreateControllerBase extends AccountManagementControllerBase { } } else { // Update GROUP_MEMBER - updateGroupMembers(form.groupName, memberNames) + updateGroupMembers(form.groupName, members) // Update COLLABORATOR for group repositories getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => removeCollaborators(form.groupName, repositoryName) - memberNames.foreach { userName => + members.foreach { case (userName, isManager) => addCollaborator(form.groupName, repositoryName, userName) } } diff --git a/src/main/scala/app/UserManagementController.scala b/src/main/scala/app/UserManagementController.scala index 50ffb3c34..dffc2e2cc 100644 --- a/src/main/scala/app/UserManagementController.scala +++ b/src/main/scala/app/UserManagementController.scala @@ -71,7 +71,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase { val users = getAllUsers(includeRemoved) val members = users.collect { case account if(account.isGroupAccount) => - account.userName -> getGroupMembers(account.userName) + account.userName -> getGroupMembers(account.userName).map(_._1) }.toMap admin.users.html.list(users, members, includeRemoved) }) @@ -127,7 +127,11 @@ trait UserManagementControllerBase extends AccountManagementControllerBase { post("/admin/users/_newgroup", newGroupForm)(adminOnly { form => createGroup(form.groupName, form.url) - updateGroupMembers(form.groupName, form.memberNames.map(_.split(",").toList).getOrElse(Nil)) + updateGroupMembers(form.groupName, form.memberNames.map(_.split(",").map { + _.split(":") match { + case Array(userName, isManager) => (userName, isManager.toBoolean) + } + }.toList).getOrElse(Nil)) updateImage(form.groupName, form.fileId, false) redirect("/admin/users") }) @@ -139,7 +143,11 @@ trait UserManagementControllerBase extends AccountManagementControllerBase { }) post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form => - defining(params("groupName"), form.memberNames.map(_.split(",").toList).getOrElse(Nil)){ case (groupName, memberNames) => + defining(params("groupName"), form.memberNames.map(_.split(",").map { + _.split(":") match { + case Array(userName, isManager) => (userName, isManager.toBoolean) + } + }.toList).getOrElse(Nil)){ case (groupName, members) => getAccountByUserName(groupName, true).map { account => updateGroup(groupName, form.url, form.isRemoved) @@ -155,11 +163,11 @@ trait UserManagementControllerBase extends AccountManagementControllerBase { } } else { // Update GROUP_MEMBER - updateGroupMembers(form.groupName, memberNames) + updateGroupMembers(form.groupName, members) // Update COLLABORATOR for group repositories getRepositoryNamesOfUser(form.groupName).foreach { repositoryName => removeCollaborators(form.groupName, repositoryName) - memberNames.foreach { userName => + members.foreach { case (userName, isManager) => addCollaborator(form.groupName, repositoryName, userName) } } diff --git a/src/main/scala/model/GroupMembers.scala b/src/main/scala/model/GroupMembers.scala index 0bcd0af73..a2a38f350 100644 --- a/src/main/scala/model/GroupMembers.scala +++ b/src/main/scala/model/GroupMembers.scala @@ -5,10 +5,12 @@ import scala.slick.driver.H2Driver.simple._ object GroupMembers extends Table[GroupMember]("GROUP_MEMBER") { def groupName = column[String]("GROUP_NAME", O PrimaryKey) def userName = column[String]("USER_NAME", O PrimaryKey) - def * = groupName ~ userName <> (GroupMember, GroupMember.unapply _) + def isManager = column[Boolean]("MANAGER") + def * = groupName ~ userName ~ isManager <> (GroupMember, GroupMember.unapply _) } case class GroupMember( groupName: String, - userName: String + userName: String, + isManager: Boolean ) \ No newline at end of file diff --git a/src/main/scala/service/AccountService.scala b/src/main/scala/service/AccountService.scala index ff1186f65..2357f58b9 100644 --- a/src/main/scala/service/AccountService.scala +++ b/src/main/scala/service/AccountService.scala @@ -122,18 +122,18 @@ trait AccountService { def updateGroup(groupName: String, url: Option[String], removed: Boolean): Unit = Accounts.filter(_.userName is groupName.bind).map(t => t.url.? ~ t.removed).update(url, removed) - def updateGroupMembers(groupName: String, members: List[String]): Unit = { + def updateGroupMembers(groupName: String, members: List[(String, Boolean)]): Unit = { Query(GroupMembers).filter(_.groupName is groupName.bind).delete - members.foreach { userName => - GroupMembers insert GroupMember (groupName, userName) + members.foreach { case (userName, isManager) => + GroupMembers insert GroupMember (groupName, userName, isManager) } } - def getGroupMembers(groupName: String): List[String] = + def getGroupMembers(groupName: String): List[(String, Boolean)] = Query(GroupMembers) .filter(_.groupName is groupName.bind) .sortBy(_.userName) - .map(_.userName) + .map(m => m.userName ~ m.isManager) .list def getGroupsByUserName(userName: String): List[String] = diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala index bfa699208..830ca292c 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -50,6 +50,7 @@ object AutoUpdate { * The history of versions. A head of this sequence is the current BitBucket version. */ val versions = Seq( + Version(1, 12), Version(1, 11), Version(1, 10), Version(1, 9), diff --git a/src/main/scala/util/Authenticator.scala b/src/main/scala/util/Authenticator.scala index c43b0ec17..bfa2a40e0 100644 --- a/src/main/scala/util/Authenticator.scala +++ b/src/main/scala/util/Authenticator.scala @@ -157,17 +157,17 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService = } /** - * Allows only the group members. + * Allows only the group managers. */ -trait GroupMemberAuthenticator { self: ControllerBase with AccountService => - protected def membersOnly(action: => Any) = { authenticate(action) } - protected def membersOnly[T](action: T => Any) = (form: T) => { authenticate(action(form)) } +trait GroupManagerAuthenticator { self: ControllerBase with AccountService => + protected def managersOnly(action: => Any) = { authenticate(action) } + protected def managersOnly[T](action: T => Any) = (form: T) => { authenticate(action(form)) } private def authenticate(action: => Any) = { { defining(request.paths){ paths => context.loginAccount match { - case Some(x) if(getGroupMembers(paths(0)).contains(x.userName)) => action + case Some(x) if(getGroupMembers(paths(0)).exists { case (userName, isManager) => userName == x.userName && isManager }) => action case _ => Unauthorized() } } diff --git a/src/main/twirl/account/main.scala.html b/src/main/twirl/account/main.scala.html index 2cbe2577c..d5f0d1ff9 100644 --- a/src/main/twirl/account/main.scala.html +++ b/src/main/twirl/account/main.scala.html @@ -1,5 +1,5 @@ @(account: model.Account, groupNames: List[String], active: String, - isGroupMember: Boolean = false)(body: Html)(implicit context: app.Context) + isGroupManager: Boolean = false)(body: Html)(implicit context: app.Context) @import context._ @import view.helpers._ @html.main(account.userName){ @@ -42,7 +42,7 @@
  • } - @if(loginAccount.isDefined && account.isGroupAccount && isGroupMember){ + @if(loginAccount.isDefined && account.isGroupAccount && isGroupManager){
  • Edit Group diff --git a/src/main/twirl/account/members.scala.html b/src/main/twirl/account/members.scala.html index b69fc0af0..0e21d0102 100644 --- a/src/main/twirl/account/members.scala.html +++ b/src/main/twirl/account/members.scala.html @@ -1,7 +1,7 @@ -@(account: model.Account, members: List[String], isGroupMember: Boolean)(implicit context: app.Context) +@(account: model.Account, members: List[String], isGroupManager: Boolean)(implicit context: app.Context) @import context._ @import view.helpers._ -@main(account, Nil, "members", isGroupMember){ +@main(account, Nil, "members", isGroupManager){ @if(members.isEmpty){ No members } else { diff --git a/src/main/twirl/account/repositories.scala.html b/src/main/twirl/account/repositories.scala.html index 7772088ca..100c2b4f6 100644 --- a/src/main/twirl/account/repositories.scala.html +++ b/src/main/twirl/account/repositories.scala.html @@ -1,9 +1,9 @@ @(account: model.Account, groupNames: List[String], repositories: List[service.RepositoryService.RepositoryInfo], - isGroupMember: Boolean)(implicit context: app.Context) + isGroupManager: Boolean)(implicit context: app.Context) @import context._ @import view.helpers._ -@main(account, groupNames, "repositories", isGroupMember){ +@main(account, groupNames, "repositories", isGroupManager){ @if(repositories.isEmpty){ No repositories } else { diff --git a/src/main/twirl/admin/users/group.scala.html b/src/main/twirl/admin/users/group.scala.html index abf64cbe3..fe037ea23 100644 --- a/src/main/twirl/admin/users/group.scala.html +++ b/src/main/twirl/admin/users/group.scala.html @@ -1,4 +1,4 @@ -@(account: Option[model.Account], members: List[String])(implicit context: app.Context) +@(account: Option[model.Account], members: List[(String, Boolean)])(implicit context: app.Context) @import context._ @import view.helpers._ @html.main(if(account.isEmpty) "New Group" else "Update Group"){ @@ -35,7 +35,7 @@
    + @if(account.isDefined){ + + } + @if(account.isDefined){ + Cancel + }
    @@ -118,6 +128,10 @@ $(function(){ updateMembers(); }); + $('#delete').click(function(){ + return confirm('Once you delete this group, there is no going back.\nAre you sure?'); + }); + function updateMembers(){ var members = $('#member-list li').map(function(i, e){ var userName = $(e).data('name'); From 9bd1f0a4927582c991ec97c48487278a1b54f96c Mon Sep 17 00:00:00 2001 From: takezoe Date: Tue, 4 Mar 2014 10:53:26 +0900 Subject: [PATCH 19/91] (refs #241)Remove unnecessary code --- src/main/twirl/group.scala.html | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/twirl/group.scala.html b/src/main/twirl/group.scala.html index 710f58d6e..0432a79c2 100644 --- a/src/main/twirl/group.scala.html +++ b/src/main/twirl/group.scala.html @@ -12,14 +12,6 @@ - @* - @if(account.isDefined){ - - } - *@
    From 79e1abe624016b8d6419fb6b0dc1008967b11dd4 Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Tue, 4 Mar 2014 22:42:32 +0900 Subject: [PATCH 20/91] (refs #110)Update apache sshd version For fix authentication partial bug --- project/build.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/build.scala b/project/build.scala index 9a07a4ad7..82b4fbe99 100644 --- a/project/build.scala +++ b/project/build.scala @@ -36,7 +36,7 @@ object MyBuild extends Build { "org.apache.commons" % "commons-compress" % "1.5", "org.apache.commons" % "commons-email" % "1.3.1", "org.apache.httpcomponents" % "httpclient" % "4.3", - "org.apache.sshd" % "apache-sshd" % "0.9.0", + "org.apache.sshd" % "apache-sshd" % "0.10.0", "com.typesafe.slick" %% "slick" % "1.0.1", "com.novell.ldap" % "jldap" % "2009-10-07", "com.h2database" % "h2" % "1.3.173", From 09b7e67c52f95cf83799e5ec5f150a2ee5d5c327 Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Tue, 4 Mar 2014 22:49:25 +0900 Subject: [PATCH 21/91] (refs #110)Correct authentication and CommitHook --- src/main/scala/ssh/GitCommand.scala | 93 ++++++++++++------- .../scala/ssh/PublicKeyAuthenticator.scala | 50 ++++++---- src/main/scala/ssh/SshServerListener.scala | 22 ++--- 3 files changed, 99 insertions(+), 66 deletions(-) diff --git a/src/main/scala/ssh/GitCommand.scala b/src/main/scala/ssh/GitCommand.scala index f855168bd..4a139751a 100644 --- a/src/main/scala/ssh/GitCommand.scala +++ b/src/main/scala/ssh/GitCommand.scala @@ -9,15 +9,15 @@ import util.Directory._ import org.eclipse.jgit.transport.{ReceivePack, UploadPack} import org.apache.sshd.server.command.UnknownCommand import servlet.{Database, CommitLogHook} -import service.SystemSettingsService.SystemSettings import service.SystemSettingsService +import org.eclipse.jgit.errors.RepositoryNotFoundException class GitCommandFactory extends CommandFactory { private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory]) override def createCommand(command: String): Command = { - logger.info(s"command: String -> " + command) + logger.debug(s"command: $command") command match { // TODO MUST use regular expression and UnitTest case s if s.startsWith("git-upload-pack") => new GitUploadPack(command) @@ -28,24 +28,24 @@ class GitCommandFactory extends CommandFactory { } abstract class GitCommand(val command: String) extends Command { - private val logger = LoggerFactory.getLogger(classOf[GitCommand]) + protected val logger = LoggerFactory.getLogger(classOf[GitCommand]) protected val (gitCommand, owner, repositoryName) = parseCommand protected var err: OutputStream = null protected var in: InputStream = null protected var out: OutputStream = null protected var callback: ExitCallback = null - protected def runnable: Runnable + protected def runnable(user: String): Runnable override def start(env: Environment): Unit = { logger.info(s"start command : " + command) logger.info(s"parsed command : $gitCommand, $owner, $repositoryName") - val thread = new Thread(runnable) + val user = env.getEnv.get("USER") + val thread = new Thread(runnable(user)) thread.start() } - override def destroy(): Unit = { - } + override def destroy(): Unit = {} override def setExitCallback(callback: ExitCallback): Unit = { this.callback = callback @@ -64,47 +64,70 @@ abstract class GitCommand(val command: String) extends Command { } private def parseCommand: (String, String, String) = { - // command sample: git-upload-pack '/username/repository_name.git' - // command sample: git-receive-pack '/username/repository_name.git' - // TODO This is not correct.... + // command sample: git-upload-pack '/owner/repository_name.git' + // command sample: git-receive-pack '/owner/repository_name.git' + // TODO This is not correct.... but works val split = command.split(" ") val gitCommand = split(0) - val gitUser = split(1).substring(1, split(1).length - 5).split("/")(1) - val gitRepo = split(1).substring(1, split(1).length - 5).split("/")(2) - (gitCommand, gitUser, gitRepo) + val owner = split(1).substring(1, split(1).length - 5).split("/")(1) + val repositoryName = split(1).substring(1, split(1).length - 5).split("/")(2) + (gitCommand, owner, repositoryName) } } -class GitUploadPack(command: String) extends GitCommand(command: String) { - override def runnable = new Runnable { +class GitUploadPack(override val command: String) extends GitCommand(command: String) { + override def runnable(user: String) = new Runnable { override def run(): Unit = { - using(Git.open(getRepositoryDir(owner, repositoryName))) { git => - val repository = git.getRepository - val upload = new UploadPack(repository) - upload.upload(in, out, err) - callback.onExit(0) + try { + using(Git.open(getRepositoryDir(owner, repositoryName))) { + git => + val repository = git.getRepository + val upload = new UploadPack(repository) + try { + upload.upload(in, out, err) + callback.onExit(0) + } catch { + case e: Throwable => + logger.error(e.getMessage, e) + callback.onExit(1) + } + } + } catch { + case e: RepositoryNotFoundException => + logger.info(e.getMessage, e) + callback.onExit(1) } } } } -class GitReceivePack(command: String) extends GitCommand(command: String) with SystemSettingsService { - override def runnable = new Runnable { - - // TODO correct this info - val pusher: String = "user1" - val baseURL: String = loadSystemSettings().baseUrl.getOrElse("http://localhost:8080") +class GitReceivePack(override val command: String) extends GitCommand(command: String) with SystemSettingsService { + // TODO Correct this info. where i get base url? + val BaseURL: String = loadSystemSettings().baseUrl.getOrElse("http://localhost:8080") + override def runnable(user: String) = new Runnable { override def run(): Unit = { - using(Git.open(getRepositoryDir(owner, repositoryName))) { git => - val repository = git.getRepository - // TODO hook commit - val receive = new ReceivePack(repository) - receive.setPostReceiveHook(new CommitLogHook(owner, repositoryName, pusher, baseURL)) - Database(SshServer.getServletContext) withTransaction { - receive.receive(in, out, err) - callback.onExit(0) - } + try { + using(Git.open(getRepositoryDir(owner, repositoryName))) { + git => + val repository = git.getRepository + val receive = new ReceivePack(repository) + receive.setPostReceiveHook(new CommitLogHook(owner, repositoryName, user, BaseURL)) + Database(SshServer.getServletContext) withTransaction { + try { + receive.receive(in, out, err) + callback.onExit(0) + } catch { + case e: Throwable => + logger.error(e.getMessage, e) + callback.onExit(1) + } + } + } + } catch { + case e: RepositoryNotFoundException => + logger.info(e.getMessage, e) + callback.onExit(1) } } } diff --git a/src/main/scala/ssh/PublicKeyAuthenticator.scala b/src/main/scala/ssh/PublicKeyAuthenticator.scala index a3c8ef04d..a59761ae8 100644 --- a/src/main/scala/ssh/PublicKeyAuthenticator.scala +++ b/src/main/scala/ssh/PublicKeyAuthenticator.scala @@ -1,37 +1,49 @@ package ssh -import org.apache.sshd.server.{PublickeyAuthenticator, PasswordAuthenticator} +import org.apache.sshd.server.PublickeyAuthenticator import org.slf4j.LoggerFactory import org.apache.sshd.server.session.ServerSession -import java.security.{KeyFactory, PublicKey} +import java.security.PublicKey import org.apache.commons.codec.binary.Base64 -import java.security.spec.X509EncodedKeySpec import org.apache.sshd.common.util.Buffer +import org.eclipse.jgit.lib.Constants + + +object DummyData { + val userPublicKeys = List( + "ssh-rsa AAB3NzaC1yc2EAAAADAQABAAABAQDRzuX0WtSLzCY45nEhfFDPXzYGmvQdqnOgOUY4yGL5io/2ztyUvJdhWowkyakeoPxVk/jIP7Tu8Are5TuSD+fJp7aUbZW2CYOEsxo8cwndh/ezIX6RFjlu+xvKvZ8G7BtFLlLCcnza9uB+uEAyPH5HvGQLdV7dXctLfFqXPTr1p1RjSI7Noubm+vN4n9108rILd32MlhQiToXjL4HKWWwmppaln6bEsonOQW4/GieRjQeyWDkbVekIofnedjWl4+W0kAA+WosNwRFShgsaJLfU964HT/cGjK5auqOG+nATY0suECnxAK+5Wb6jXXYNmKiIMHypeXG1Qy2wMyMB1Gq9 tanacasino-local", + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRzuX0WtSLzCY45nEhfFDPXzYGmvQdqnOgOUY4yGL5io/2ztyUvJdhWowkyakeoPxVk/jIP7Tu8Are5TuSD+fJp7aUbZW2CYOEsxo8cwndh/ezIX6RFjlu+xvKvZ8G7BtFLlLCcnza9uB+uEAyPH5HvGQLdV7dXctLfFqXPTr1p1RjSI7Noubm+vN4n9108rILd32MlhQiToXjL4HKWWwmppaln6bEsonOQW4/GieRjQeyWDkbVekIofnedjWl4+W0kAA+WosNwRFShgsaJLfU964HT/cGjK5auqOG+nATY0suECnxAK+5Wb6jXXYNmKiIMHypeXG1Qy2wMyMB1Gq9 tanacasino-local" + ) +} class PublicKeyAuthenticator extends PublickeyAuthenticator { private val logger = LoggerFactory.getLogger(classOf[PublicKeyAuthenticator]) override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = { - // TODO this string is read from DB and Users register this public key string on Account Profile view - val testAuthkey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRzuX0WtSLzCY45nEhfFDPXzYGmvQdqnOgOUY4yGL5io/2ztyUvJdhWowkyakeoPxVk/jIP7Tu8Are5TuSD+fJp7aUbZW2CYOEsxo8cwndh/ezIX6RFjlu+xvKvZ8G7BtFLlLCcnza9uB+uEAyPH5HvGQLdV7dXctLfFqXPTr1p1RjSI7Noubm+vN4n9108rILd32MlhQiToXjL4HKWWwmppaln6bEsonOQW4/GieRjQeyWDkbVekIofnedjWl4+W0kAA+WosNwRFShgsaJLfU964HT/cGjK5auqOG+nATY0suECnxAK+5Wb6jXXYNmKiIMHypeXG1Qy2wMyMB1Gq9 tanacasino-local" - toPublicKey(testAuthkey) match { + // TODO userPublicKeys is read from DB and Users register this public key string list on Account Profile view + DummyData.userPublicKeys.exists(str => str2PublicKey(str) match { case Some(publicKey) => key.equals(publicKey) case _ => false + }) + } + + private def str2PublicKey(key: String): Option[PublicKey] = { + // TODO RFC 4716 Public Key is not supported... + val parts = key.split(" ") + if (parts.size < 2) { + logger.debug(s"Invalid PublicKey Format: key") + return None + } + try { + val encodedKey = parts(1) + val decode = Base64.decodeBase64(Constants.encodeASCII(encodedKey)) + Some(new Buffer(decode).getRawPublicKey) + } catch { + case e: Throwable => + logger.debug(e.getMessage, e) + None } } - private def toPublicKey(key: String): Option[PublicKey] = { - try { - val parts = key.split(" ") - val encodedKey = key.split(" ")(1) - val decode = Base64.decodeBase64(encodedKey) - Some(new Buffer(decode).getRawPublicKey) - } catch { - case e: Throwable => { - logger.error(e.getMessage, e) - None - } - } - } } diff --git a/src/main/scala/ssh/SshServerListener.scala b/src/main/scala/ssh/SshServerListener.scala index dda7dcd8d..477f3a1a3 100644 --- a/src/main/scala/ssh/SshServerListener.scala +++ b/src/main/scala/ssh/SshServerListener.scala @@ -3,30 +3,30 @@ package ssh import javax.servlet.{ServletContext, ServletContextEvent, ServletContextListener} import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider import org.slf4j.LoggerFactory +import util.Directory object SshServer { private val logger = LoggerFactory.getLogger(SshServer.getClass) - val DEFAULT_PORT: Int = 29418 // TODO read from config - val SSH_SERVICE_ENABLE = true + val DEFAULT_PORT: Int = 29418 + // TODO read from config + val SSH_SERVICE_ENABLE = true // TODO read from config private val server = org.apache.sshd.SshServer.setUpDefaultServer() - // TODO think other way to create database session + // TODO think other way. this is for create database session private var context: ServletContext = null private def configure() = { - server.setPort(DEFAULT_PORT) - // TODO gitbucket.ser should be in GITBUCKET_HOME - server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("gitbucket.ser")) - + server.setPort(DEFAULT_PORT) // TODO read from config + server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(s"${Directory.GitBucketHome}/gitbucket.ser")) server.setPublickeyAuthenticator(new PublicKeyAuthenticator) server.setCommandFactory(new GitCommandFactory) } - def start(context: ServletContext) = { + def start(context: ServletContext) = this.synchronized { if (SSH_SERVICE_ENABLE) { this.context = context configure() @@ -39,7 +39,7 @@ object SshServer { server.stop(true) } - def getServletContext = this.context; + def getServletContext = this.context } /* @@ -52,7 +52,7 @@ object SshServer { class SshServerListener extends ServletContextListener { override def contextInitialized(sce: ServletContextEvent): Unit = { - SshServer.start(sce.getServletContext()) + SshServer.start(sce.getServletContext) } override def contextDestroyed(sce: ServletContextEvent): Unit = { @@ -60,5 +60,3 @@ class SshServerListener extends ServletContextListener { } } - - From d46589ad29d9ac34281c29c635b64aa023960f7c Mon Sep 17 00:00:00 2001 From: Tomofumi Tanaka Date: Tue, 4 Mar 2014 23:18:38 +0900 Subject: [PATCH 22/91] (refs #115)Ensure exit status 1 on GitCommand error --- src/main/scala/ssh/GitCommand.scala | 80 ++++++++++++----------------- 1 file changed, 34 insertions(+), 46 deletions(-) diff --git a/src/main/scala/ssh/GitCommand.scala b/src/main/scala/ssh/GitCommand.scala index 4a139751a..7ec5842d1 100644 --- a/src/main/scala/ssh/GitCommand.scala +++ b/src/main/scala/ssh/GitCommand.scala @@ -35,13 +35,29 @@ abstract class GitCommand(val command: String) extends Command { protected var out: OutputStream = null protected var callback: ExitCallback = null - protected def runnable(user: String): Runnable + protected def runTask(user: String): Unit + + private def newTask(user: String): Runnable = new Runnable { + override def run(): Unit = { + try { + runTask(user) + callback.onExit(0) + } catch { + case e: RepositoryNotFoundException => + logger.info(e.getMessage) + callback.onExit(1, "Repository Not Found") + case e: Throwable => + logger.info(e.getMessage, e) + callback.onExit(1) + } + } + } override def start(env: Environment): Unit = { logger.info(s"start command : " + command) logger.info(s"parsed command : $gitCommand, $owner, $repositoryName") val user = env.getEnv.get("USER") - val thread = new Thread(runnable(user)) + val thread = new Thread(newTask(user)) thread.start() } @@ -76,59 +92,31 @@ abstract class GitCommand(val command: String) extends Command { } class GitUploadPack(override val command: String) extends GitCommand(command: String) { - override def runnable(user: String) = new Runnable { - override def run(): Unit = { - try { - using(Git.open(getRepositoryDir(owner, repositoryName))) { - git => - val repository = git.getRepository - val upload = new UploadPack(repository) - try { - upload.upload(in, out, err) - callback.onExit(0) - } catch { - case e: Throwable => - logger.error(e.getMessage, e) - callback.onExit(1) - } - } - } catch { - case e: RepositoryNotFoundException => - logger.info(e.getMessage, e) - callback.onExit(1) - } + + override protected def runTask(user: String): Unit = { + using(Git.open(getRepositoryDir(owner, repositoryName))) { + git => + val repository = git.getRepository + val upload = new UploadPack(repository) + upload.upload(in, out, err) } } + } class GitReceivePack(override val command: String) extends GitCommand(command: String) with SystemSettingsService { // TODO Correct this info. where i get base url? val BaseURL: String = loadSystemSettings().baseUrl.getOrElse("http://localhost:8080") - override def runnable(user: String) = new Runnable { - override def run(): Unit = { - try { - using(Git.open(getRepositoryDir(owner, repositoryName))) { - git => - val repository = git.getRepository - val receive = new ReceivePack(repository) - receive.setPostReceiveHook(new CommitLogHook(owner, repositoryName, user, BaseURL)) - Database(SshServer.getServletContext) withTransaction { - try { - receive.receive(in, out, err) - callback.onExit(0) - } catch { - case e: Throwable => - logger.error(e.getMessage, e) - callback.onExit(1) - } - } + override protected def runTask(user: String): Unit = { + using(Git.open(getRepositoryDir(owner, repositoryName))) { + git => + val repository = git.getRepository + val receive = new ReceivePack(repository) + receive.setPostReceiveHook(new CommitLogHook(owner, repositoryName, user, BaseURL)) + Database(SshServer.getServletContext) withTransaction { + receive.receive(in, out, err) } - } catch { - case e: RepositoryNotFoundException => - logger.info(e.getMessage, e) - callback.onExit(1) - } } } From f11be44c02414b85dbd0485f6f225586dc99205e Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 5 Mar 2014 04:18:45 +0900 Subject: [PATCH 23/91] (refs #330)Return NotFound if specified file does not exist --- .../app/RepositoryViewerController.scala | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index fcc81dfe6..3a2141f78 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -82,44 +82,45 @@ trait RepositoryViewerControllerBase extends ControllerBase { val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id)) @scala.annotation.tailrec - def getPathObjectId(path: String, walk: TreeWalk): ObjectId = walk.next match { - case true if(walk.getPathString == path) => walk.getObjectId(0) - case true => getPathObjectId(path, walk) + def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match { + case true if(walk.getPathString == path) => Some(walk.getObjectId(0)) + case true => getPathObjectId(path, walk) + case false => None } - val objectId = using(new TreeWalk(git.getRepository)){ treeWalk => + using(new TreeWalk(git.getRepository)){ treeWalk => treeWalk.addTree(revCommit.getTree) treeWalk.setRecursive(true) getPathObjectId(path, treeWalk) - } - - if(raw){ - // Download - defining(JGitUtil.getContent(git, objectId, false).get){ bytes => - contentType = FileUtil.getContentType(path, bytes) - bytes - } - } else { - // Viewer - val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize) - val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other" - val bytes = if(viewer == "other") JGitUtil.getContent(git, objectId, false) else None - - val content = if(viewer == "other"){ - if(bytes.isDefined && FileUtil.isText(bytes.get)){ - // text - JGitUtil.ContentInfo("text", bytes.map(StringUtil.convertFromByteArray)) - } else { - // binary - JGitUtil.ContentInfo("binary", None) + } map { objectId => + if(raw){ + // Download + defining(JGitUtil.getContent(git, objectId, false).get){ bytes => + contentType = FileUtil.getContentType(path, bytes) + bytes } } else { - // image or large - JGitUtil.ContentInfo(viewer, None) - } + // Viewer + val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize) + val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other" + val bytes = if(viewer == "other") JGitUtil.getContent(git, objectId, false) else None - repo.html.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit)) - } + val content = if(viewer == "other"){ + if(bytes.isDefined && FileUtil.isText(bytes.get)){ + // text + JGitUtil.ContentInfo("text", bytes.map(StringUtil.convertFromByteArray)) + } else { + // binary + JGitUtil.ContentInfo("binary", None) + } + } else { + // image or large + JGitUtil.ContentInfo(viewer, None) + } + + repo.html.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit)) + } + } getOrElse NotFound } }) From aa5b9dbbbdd7dbc877a0d442bc1db348297c9549 Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 5 Mar 2014 04:23:44 +0900 Subject: [PATCH 24/91] Remove unnecessary code --- src/main/scala/app/RepositoryViewerController.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/app/RepositoryViewerController.scala b/src/main/scala/app/RepositoryViewerController.scala index 3a2141f78..6eb929656 100644 --- a/src/main/scala/app/RepositoryViewerController.scala +++ b/src/main/scala/app/RepositoryViewerController.scala @@ -267,7 +267,7 @@ trait RepositoryViewerControllerBase extends ControllerBase { repo.html.guide(repository) } else { using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => - val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head) + //val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head) // get specified commit JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) => defining(JGitUtil.getRevCommitFromId(git, objectId)){ revCommit => From 6f9ef32d96d6de6cae9701a72ceb0ab2229fd26d Mon Sep 17 00:00:00 2001 From: shimamoto Date: Wed, 5 Mar 2014 14:35:49 +0900 Subject: [PATCH 25/91] (refs #292) Fix to limit issue result before joins issue labels. --- src/main/scala/service/IssuesService.scala | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/scala/service/IssuesService.scala b/src/main/scala/service/IssuesService.scala index b1215995f..e109dd46f 100644 --- a/src/main/scala/service/IssuesService.scala +++ b/src/main/scala/service/IssuesService.scala @@ -120,16 +120,10 @@ trait IssuesService { // get issues and comment count and labels searchIssueQuery(repos, condition, filterUser, onlyPullRequest) .innerJoin(IssueOutline).on { (t1, t2) => t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) } - .leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) } - .leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) } - .map { case (((t1, t2), t3), t4) => - (t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?) - } - .sortBy(_._4) // labelName - .sortBy { case (t1, commentCount, _,_,_) => + .sortBy { case (t1, t2) => (condition.sort match { case "created" => t1.registeredDate - case "comments" => commentCount + case "comments" => t2.commentCount case "updated" => t1.updatedDate }) match { case sort => condition.direction match { @@ -139,6 +133,11 @@ trait IssuesService { } } .drop(offset).take(limit) + .leftJoin (IssueLabels) .on { case ((t1, t2), t3) => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) } + .leftJoin (Labels) .on { case (((t1, t2), t3), t4) => t3.byLabel(t4.userName, t4.repositoryName, t4.labelId) } + .map { case (((t1, t2), t3), t4) => + (t1, t2.commentCount, t4.labelId.?, t4.labelName.?, t4.color.?) + } .list .splitWith { (c1, c2) => c1._1.userName == c2._1.userName && From 145c155ba5cc37ce31697862edc390833acceb51 Mon Sep 17 00:00:00 2001 From: shimamoto Date: Wed, 5 Mar 2014 14:40:52 +0900 Subject: [PATCH 26/91] Remove unnecessary import. --- src/main/scala/service/IssuesService.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/scala/service/IssuesService.scala b/src/main/scala/service/IssuesService.scala index e109dd46f..590e300bf 100644 --- a/src/main/scala/service/IssuesService.scala +++ b/src/main/scala/service/IssuesService.scala @@ -8,7 +8,6 @@ import Q.interpolation import model._ import util.Implicits._ import util.StringUtil._ -import util.StringUtil trait IssuesService { import IssuesService._ @@ -315,7 +314,7 @@ trait IssuesService { } def closeIssuesFromMessage(message: String, userName: String, owner: String, repository: String) = { - StringUtil.extractCloseId(message).foreach { issueId => + extractCloseId(message).foreach { issueId => for(issue <- getIssue(owner, repository, issueId) if !issue.closed){ createComment(owner, repository, userName, issue.issueId, "Close", "close") updateClosed(owner, repository, issue.issueId, true) From c7a2ec82909a2b87b6f7c659649a84f268a9c874 Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 5 Mar 2014 16:23:27 +0900 Subject: [PATCH 27/91] (refs #299)Fix redirection path in PullRequestsController --- src/main/scala/app/PullRequestsController.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/app/PullRequestsController.scala b/src/main/scala/app/PullRequestsController.scala index bf3bea268..be47ee932 100644 --- a/src/main/scala/app/PullRequestsController.scala +++ b/src/main/scala/app/PullRequestsController.scala @@ -228,16 +228,16 @@ trait PullRequestsControllerBase extends ControllerBase { val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2 val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2 - redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}") + redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}") } } getOrElse NotFound } case _ => { using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git => JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) => - redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}") + redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}") } getOrElse { - redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}") + redirect(s"/${forkedRepository.owner}/${forkedRepository.name}") } } } From d92a1cee1c01af2cea16fc21302878d3c849bcca Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 5 Mar 2014 19:18:50 +0900 Subject: [PATCH 28/91] Replace Context#path with the base url if it's configured. --- src/main/scala/app/ControllerBase.scala | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/scala/app/ControllerBase.scala b/src/main/scala/app/ControllerBase.scala index 81e1dca83..2ae40648f 100644 --- a/src/main/scala/app/ControllerBase.scala +++ b/src/main/scala/app/ControllerBase.scala @@ -28,7 +28,7 @@ abstract class ControllerBase extends ScalatraFilter // Don't set content type via Accept header. override def format(implicit request: HttpServletRequest) = "" - override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) { + override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try { val httpRequest = request.asInstanceOf[HttpServletRequest] val httpResponse = response.asInstanceOf[HttpServletResponse] val context = request.getServletContext.getContextPath @@ -56,12 +56,25 @@ abstract class ControllerBase extends ScalatraFilter // Scalatra actions super.doFilter(request, response, chain) } + } finally { + contextCache.remove(); } + private val contextCache = new java.lang.ThreadLocal[Context]() + /** * Returns the context object for the request. */ - implicit def context: Context = Context(servletContext.getContextPath, LoginAccount, request) + implicit def context: Context = { + contextCache.get match { + case null => { + val context = Context(loadSystemSettings().baseUrl.getOrElse(servletContext.getContextPath), LoginAccount, request) + contextCache.set(context) + context + } + case context => context + } + } private def LoginAccount: Option[Account] = session.getAs[Account](Keys.Session.LoginAccount) From b732e0d55a1b180aee8b0a3d060a34cc0367b2d2 Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 5 Mar 2014 22:12:34 +0900 Subject: [PATCH 29/91] Fix error when base url is configured. --- src/main/scala/app/ControllerBase.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/app/ControllerBase.scala b/src/main/scala/app/ControllerBase.scala index 2ae40648f..4168a8d3d 100644 --- a/src/main/scala/app/ControllerBase.scala +++ b/src/main/scala/app/ControllerBase.scala @@ -143,7 +143,7 @@ abstract class ControllerBase extends ScalatraFilter */ case class Context(path: String, loginAccount: Option[Account], request: HttpServletRequest){ - lazy val currentPath = request.getRequestURI.substring(path.length) + lazy val currentPath = request.getRequestURI.substring(request.getContextPath.length) /** * Get object from cache. From 9b15af3bb726f45150b5332bf116d5e8228e46b0 Mon Sep 17 00:00:00 2001 From: takezoe Date: Wed, 5 Mar 2014 22:15:22 +0900 Subject: [PATCH 30/91] Update README.md for 1.11.1 release --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 43470be57..07314d893 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,9 @@ Run the following commands in `Terminal` to Release Notes -------- +### 1.11.1 - 06 Mar 2014 +- Bug fix + ### 1.11 - 01 Mar 2014 - Base URL for redirection, notification and repository URL box is configurable - Remove ```--https``` option because it's possible to substitute in the base url From 716eddac7b1e9a9ab2882b3ed0aec88dac77281d Mon Sep 17 00:00:00 2001 From: takezoe Date: Thu, 6 Mar 2014 02:46:40 +0900 Subject: [PATCH 31/91] (refs #241)Checkbox for manager is replaced with toggle buttons --- src/main/twirl/group.scala.html | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/main/twirl/group.scala.html b/src/main/twirl/group.scala.html index 0432a79c2..de8d1c047 100644 --- a/src/main/twirl/group.scala.html +++ b/src/main/twirl/group.scala.html @@ -31,8 +31,11 @@