mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-09 15:05:50 +01:00
Merge branch 'master' into add-features-to-ldapauth
Conflicts: src/main/twirl/account/edit.scala.html
This commit is contained in:
@@ -36,6 +36,7 @@ object MyBuild extends Build {
|
|||||||
"org.apache.commons" % "commons-compress" % "1.5",
|
"org.apache.commons" % "commons-compress" % "1.5",
|
||||||
"org.apache.commons" % "commons-email" % "1.3.1",
|
"org.apache.commons" % "commons-email" % "1.3.1",
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.3",
|
"org.apache.httpcomponents" % "httpclient" % "4.3",
|
||||||
|
"org.apache.sshd" % "apache-sshd" % "0.10.0",
|
||||||
"com.typesafe.slick" %% "slick" % "1.0.1",
|
"com.typesafe.slick" %% "slick" % "1.0.1",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.3.173",
|
"com.h2database" % "h2" % "1.3.173",
|
||||||
|
|||||||
@@ -1 +1,11 @@
|
|||||||
ALTER TABLE GROUP_MEMBER ADD COLUMN MANAGER BOOLEAN DEFAULT FALSE;
|
ALTER TABLE GROUP_MEMBER ADD COLUMN MANAGER BOOLEAN DEFAULT FALSE;
|
||||||
|
|
||||||
|
CREATE TABLE SSH_KEY (
|
||||||
|
USER_NAME VARCHAR(100) NOT NULL,
|
||||||
|
SSH_KEY_ID INT AUTO_INCREMENT,
|
||||||
|
TITLE VARCHAR(100) NOT NULL,
|
||||||
|
PUBLIC_KEY TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_PK PRIMARY KEY (USER_NAME, SSH_KEY_ID);
|
||||||
|
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import org.eclipse.jgit.dircache.DirCache
|
|||||||
import model.GroupMember
|
import model.GroupMember
|
||||||
|
|
||||||
class AccountController extends AccountControllerBase
|
class AccountController extends AccountControllerBase
|
||||||
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService
|
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
|
||||||
|
|
||||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||||
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService
|
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
||||||
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator =>
|
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator =>
|
||||||
|
|
||||||
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
||||||
@@ -27,6 +27,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
case class AccountEditForm(password: Option[String], fullName: String, mailAddress: String,
|
case class AccountEditForm(password: Option[String], fullName: String, mailAddress: String,
|
||||||
url: Option[String], fileId: Option[String], clearImage: Boolean)
|
url: Option[String], fileId: Option[String], clearImage: Boolean)
|
||||||
|
|
||||||
|
case class SshKeyForm(title: String, publicKey: String)
|
||||||
|
|
||||||
val newForm = mapping(
|
val newForm = mapping(
|
||||||
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
|
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
|
||||||
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
||||||
@@ -45,6 +47,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
"clearImage" -> trim(label("Clear image" , boolean()))
|
"clearImage" -> trim(label("Clear image" , boolean()))
|
||||||
)(AccountEditForm.apply)
|
)(AccountEditForm.apply)
|
||||||
|
|
||||||
|
val sshKeyForm = mapping(
|
||||||
|
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||||
|
"publicKey" -> trim(label("Key" , text(required)))
|
||||||
|
)(SshKeyForm.apply)
|
||||||
|
|
||||||
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String)
|
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String)
|
||||||
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
||||||
|
|
||||||
@@ -124,7 +131,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
get("/:userName/_edit")(oneselfOnly {
|
get("/:userName/_edit")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map(x => account.html.edit(Some(x), flash.get("info"))) getOrElse NotFound
|
getAccountByUserName(userName).map { x =>
|
||||||
|
account.html.edit(x, loadSystemSettings(), flash.get("info"))
|
||||||
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:userName/_edit", editForm)(oneselfOnly { form =>
|
post("/:userName/_edit", editForm)(oneselfOnly { form =>
|
||||||
@@ -164,12 +173,32 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
redirect("/")
|
redirect("/")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
get("/:userName/_ssh")(oneselfOnly {
|
||||||
|
val userName = params("userName")
|
||||||
|
getAccountByUserName(userName).map { x =>
|
||||||
|
account.html.ssh(x, loadSystemSettings(), getPublicKeys(x.userName))
|
||||||
|
} getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form =>
|
||||||
|
val userName = params("userName")
|
||||||
|
addPublicKey(userName, form.title, form.publicKey)
|
||||||
|
redirect(s"/${userName}/_ssh")
|
||||||
|
})
|
||||||
|
|
||||||
|
get("/:userName/_ssh/delete/:id")(oneselfOnly {
|
||||||
|
val userName = params("userName")
|
||||||
|
val sshKeyId = params("id").toInt
|
||||||
|
deletePublicKey(userName, sshKeyId)
|
||||||
|
redirect(s"/${userName}/_ssh")
|
||||||
|
})
|
||||||
|
|
||||||
get("/register"){
|
get("/register"){
|
||||||
if(loadSystemSettings().allowAccountRegistration){
|
if(loadSystemSettings().allowAccountRegistration){
|
||||||
if(context.loginAccount.isDefined){
|
if(context.loginAccount.isDefined){
|
||||||
redirect("/")
|
redirect("/")
|
||||||
} else {
|
} else {
|
||||||
account.html.edit(None, None)
|
account.html.register()
|
||||||
}
|
}
|
||||||
} else NotFound
|
} else NotFound
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
|
||||||
logs.splitWith{ (commit1, commit2) =>
|
logs.splitWith{ (commit1, commit2) =>
|
||||||
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
|
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
|
||||||
}, page, hasNext)
|
}, page, hasNext, loadSystemSettings())
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
JGitUtil.ContentInfo(viewer, None)
|
JGitUtil.ContentInfo(viewer, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
repo.html.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit))
|
repo.html.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit), loadSystemSettings())
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||||
repository, diffs, oldCommitId)
|
repository, diffs, oldCommitId, loadSystemSettings())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
val revCommit = git.log.add(git.getRepository.resolve(branchName)).setMaxCount(1).call.iterator.next
|
val revCommit = git.log.add(git.getRepository.resolve(branchName)).setMaxCount(1).call.iterator.next
|
||||||
(branchName, revCommit.getCommitterIdent.getWhen)
|
(branchName, revCommit.getCommitterIdent.getWhen)
|
||||||
}
|
}
|
||||||
repo.html.branches(branchInfo, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
repo.html.branches(branchInfo, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||||
|
repository, loadSystemSettings())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -175,7 +176,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
* Displays tags.
|
* Displays tags.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/tags")(referrersOnly {
|
get("/:owner/:repository/tags")(referrersOnly {
|
||||||
repo.html.tags(_)
|
repo.html.tags(_, loadSystemSettings())
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -284,7 +285,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
repo.html.files(revision, repository,
|
repo.html.files(revision, repository,
|
||||||
if(path == ".") Nil else path.split("/").toList, // current path
|
if(path == ".") Nil else path.split("/").toList, // current path
|
||||||
new JGitUtil.CommitInfo(revCommit), // latest commit
|
new JGitUtil.CommitInfo(revCommit), // latest commit
|
||||||
files, readme)
|
files, readme, loadSystemSettings())
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import service.{AccountService, SystemSettingsService}
|
|||||||
import SystemSettingsService._
|
import SystemSettingsService._
|
||||||
import util.AdminAuthenticator
|
import util.AdminAuthenticator
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
import ssh.SshServer
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
with SystemSettingsService with AccountService with AdminAuthenticator
|
with SystemSettingsService with AccountService with AdminAuthenticator
|
||||||
@@ -16,6 +17,8 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
||||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||||
"notification" -> trim(label("Notification", boolean())),
|
"notification" -> trim(label("Notification", boolean())),
|
||||||
|
"ssh" -> trim(label("SSH access", boolean())),
|
||||||
|
"sshPort" -> trim(label("SSH port", optional(number()))),
|
||||||
"smtp" -> optionalIfNotChecked("notification", mapping(
|
"smtp" -> optionalIfNotChecked("notification", mapping(
|
||||||
"host" -> trim(label("SMTP Host", text(required))),
|
"host" -> trim(label("SMTP Host", text(required))),
|
||||||
"port" -> trim(label("SMTP Port", optional(number()))),
|
"port" -> trim(label("SMTP Port", optional(number()))),
|
||||||
@@ -39,7 +42,11 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
"tls" -> trim(label("Enable TLS", optional(boolean()))),
|
||||||
"keystore" -> trim(label("Keystore", optional(text())))
|
"keystore" -> trim(label("Keystore", optional(text())))
|
||||||
)(Ldap.apply))
|
)(Ldap.apply))
|
||||||
)(SystemSettings.apply)
|
)(SystemSettings.apply).verifying { settings =>
|
||||||
|
if(settings.ssh && settings.baseUrl.isEmpty){
|
||||||
|
Seq("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
||||||
|
} else Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
get("/admin/system")(adminOnly {
|
get("/admin/system")(adminOnly {
|
||||||
@@ -48,6 +55,15 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
post("/admin/system", form)(adminOnly { form =>
|
post("/admin/system", form)(adminOnly { form =>
|
||||||
saveSystemSettings(form)
|
saveSystemSettings(form)
|
||||||
|
|
||||||
|
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
|
||||||
|
SshServer.start(request.getServletContext,
|
||||||
|
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
|
||||||
|
form.baseUrl.get)
|
||||||
|
} else if(!form.ssh && SshServer.isActive){
|
||||||
|
SshServer.stop()
|
||||||
|
}
|
||||||
|
|
||||||
flash += "info" -> "System settings has been updated."
|
flash += "info" -> "System settings has been updated."
|
||||||
redirect("/admin/system")
|
redirect("/admin/system")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
||||||
wiki.html.page("Home", page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
wiki.html.page("Home", page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount), loadSystemSettings())
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
|
||||||
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
getWikiPage(repository.owner, repository.name, pageName).map { page =>
|
||||||
wiki.html.page(pageName, page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
wiki.html.page(pageName, page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount), loadSystemSettings())
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
||||||
case Right((logs, hasNext)) => wiki.html.history(Some(pageName), logs, repository)
|
case Right((logs, hasNext)) => wiki.html.history(Some(pageName), logs, repository, loadSystemSettings())
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
wiki.html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
wiki.html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
hasWritePermission(repository.owner, repository.name, context.loginAccount), loadSystemSettings(), flash.get("info"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
wiki.html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
wiki.html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
|
hasWritePermission(repository.owner, repository.name, context.loginAccount), loadSystemSettings(), flash.get("info"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
wiki.html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
wiki.html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository, loadSystemSettings())
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
||||||
@@ -118,7 +118,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
|
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
|
||||||
wiki.html.edit("", None, _)
|
wiki.html.edit("", None, _, loadSystemSettings())
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
||||||
@@ -146,13 +146,13 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
||||||
wiki.html.pages(getWikiPageList(repository.owner, repository.name), repository,
|
wiki.html.pages(getWikiPageList(repository.owner, repository.name), repository,
|
||||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
hasWritePermission(repository.owner, repository.name, context.loginAccount), loadSystemSettings())
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master") match {
|
JGitUtil.getCommitLog(git, "master") match {
|
||||||
case Right((logs, hasNext)) => wiki.html.history(None, logs, repository)
|
case Right((logs, hasNext)) => wiki.html.history(None, logs, repository, loadSystemSettings())
|
||||||
case Left(_) => NotFound
|
case Left(_) => NotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/main/scala/model/SshKey.scala
Normal file
22
src/main/scala/model/SshKey.scala
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import scala.slick.driver.H2Driver.simple._
|
||||||
|
|
||||||
|
object SshKeys extends Table[SshKey]("SSH_KEY") {
|
||||||
|
def userName = column[String]("USER_NAME")
|
||||||
|
def sshKeyId = column[Int]("SSH_KEY_ID", O AutoInc)
|
||||||
|
def title = column[String]("TITLE")
|
||||||
|
def publicKey = column[String]("PUBLIC_KEY")
|
||||||
|
|
||||||
|
def ins = userName ~ title ~ publicKey returning sshKeyId
|
||||||
|
def * = userName ~ sshKeyId ~ title ~ publicKey <> (SshKey, SshKey.unapply _)
|
||||||
|
|
||||||
|
def byPrimaryKey(userName: String, sshKeyId: Int) = (this.userName is userName.bind) && (this.sshKeyId is sshKeyId.bind)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SshKey(
|
||||||
|
userName: String,
|
||||||
|
sshKeyId: Int,
|
||||||
|
title: String,
|
||||||
|
publicKey: String
|
||||||
|
)
|
||||||
@@ -288,10 +288,14 @@ trait RepositoryService { self: AccountService =>
|
|||||||
|
|
||||||
object RepositoryService {
|
object RepositoryService {
|
||||||
|
|
||||||
case class RepositoryInfo(owner: String, name: String, url: String, repository: Repository,
|
case class RepositoryInfo(owner: String, name: String, httpUrl: String, repository: Repository,
|
||||||
issueCount: Int, pullCount: Int, commitCount: Int, forkedCount: Int,
|
issueCount: Int, pullCount: Int, commitCount: Int, forkedCount: Int,
|
||||||
branchList: Seq[String], tags: Seq[util.JGitUtil.TagInfo], managers: Seq[String]){
|
branchList: Seq[String], tags: Seq[util.JGitUtil.TagInfo], managers: Seq[String]){
|
||||||
|
|
||||||
|
lazy val host = """^https?://(.+?)(:\d+)?/""".r.findFirstMatchIn(httpUrl).get.group(1)
|
||||||
|
|
||||||
|
def sshUrl(port: Int) = s"ssh://${host}:${port}/${owner}/${name}.git"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates instance with issue count and pull request count.
|
* Creates instance with issue count and pull request count.
|
||||||
*/
|
*/
|
||||||
|
|||||||
19
src/main/scala/service/SshKeyService.scala
Normal file
19
src/main/scala/service/SshKeyService.scala
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import model._
|
||||||
|
import scala.slick.driver.H2Driver.simple._
|
||||||
|
import Database.threadLocalSession
|
||||||
|
|
||||||
|
trait SshKeyService {
|
||||||
|
|
||||||
|
def addPublicKey(userName: String, title: String, publicKey: String): Unit =
|
||||||
|
SshKeys.ins insert (userName, title, publicKey)
|
||||||
|
|
||||||
|
def getPublicKeys(userName: String): List[SshKey] =
|
||||||
|
Query(SshKeys).filter(_.userName is userName.bind).sortBy(_.sshKeyId).list
|
||||||
|
|
||||||
|
def deletePublicKey(userName: String, sshKeyId: Int): Unit =
|
||||||
|
SshKeys filter (_.byPrimaryKey(userName, sshKeyId)) delete
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,10 +15,12 @@ trait SystemSettingsService {
|
|||||||
|
|
||||||
def saveSystemSettings(settings: SystemSettings): Unit = {
|
def saveSystemSettings(settings: SystemSettings): Unit = {
|
||||||
defining(new java.util.Properties()){ props =>
|
defining(new java.util.Properties()){ props =>
|
||||||
settings.baseUrl.foreach(props.setProperty(BaseURL, _))
|
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
||||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||||
props.setProperty(Notification, settings.notification.toString)
|
props.setProperty(Notification, settings.notification.toString)
|
||||||
|
props.setProperty(Ssh, settings.ssh.toString)
|
||||||
|
settings.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
|
||||||
if(settings.notification) {
|
if(settings.notification) {
|
||||||
settings.smtp.foreach { smtp =>
|
settings.smtp.foreach { smtp =>
|
||||||
props.setProperty(SmtpHost, smtp.host)
|
props.setProperty(SmtpHost, smtp.host)
|
||||||
@@ -57,10 +59,12 @@ trait SystemSettingsService {
|
|||||||
props.load(new java.io.FileInputStream(GitBucketConf))
|
props.load(new java.io.FileInputStream(GitBucketConf))
|
||||||
}
|
}
|
||||||
SystemSettings(
|
SystemSettings(
|
||||||
getOptionValue(props, BaseURL, None),
|
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
||||||
getValue(props, AllowAccountRegistration, false),
|
getValue(props, AllowAccountRegistration, false),
|
||||||
getValue(props, Gravatar, true),
|
getValue(props, Gravatar, true),
|
||||||
getValue(props, Notification, false),
|
getValue(props, Notification, false),
|
||||||
|
getValue(props, Ssh, false),
|
||||||
|
getOptionValue(props, SshPort, Some(DefaultSshPort)),
|
||||||
if(getValue(props, Notification, false)){
|
if(getValue(props, Notification, false)){
|
||||||
Some(Smtp(
|
Some(Smtp(
|
||||||
getValue(props, SmtpHost, ""),
|
getValue(props, SmtpHost, ""),
|
||||||
@@ -104,6 +108,8 @@ object SystemSettingsService {
|
|||||||
allowAccountRegistration: Boolean,
|
allowAccountRegistration: Boolean,
|
||||||
gravatar: Boolean,
|
gravatar: Boolean,
|
||||||
notification: Boolean,
|
notification: Boolean,
|
||||||
|
ssh: Boolean,
|
||||||
|
sshPort: Option[Int],
|
||||||
smtp: Option[Smtp],
|
smtp: Option[Smtp],
|
||||||
ldapAuthentication: Boolean,
|
ldapAuthentication: Boolean,
|
||||||
ldap: Option[Ldap])
|
ldap: Option[Ldap])
|
||||||
@@ -130,6 +136,7 @@ object SystemSettingsService {
|
|||||||
fromAddress: Option[String],
|
fromAddress: Option[String],
|
||||||
fromName: Option[String])
|
fromName: Option[String])
|
||||||
|
|
||||||
|
val DefaultSshPort = 29418
|
||||||
val DefaultSmtpPort = 25
|
val DefaultSmtpPort = 25
|
||||||
val DefaultLdapPort = 389
|
val DefaultLdapPort = 389
|
||||||
|
|
||||||
@@ -137,6 +144,8 @@ object SystemSettingsService {
|
|||||||
private val AllowAccountRegistration = "allow_account_registration"
|
private val AllowAccountRegistration = "allow_account_registration"
|
||||||
private val Gravatar = "gravatar"
|
private val Gravatar = "gravatar"
|
||||||
private val Notification = "notification"
|
private val Notification = "notification"
|
||||||
|
private val Ssh = "ssh"
|
||||||
|
private val SshPort = "ssh.port"
|
||||||
private val SmtpHost = "smtp.host"
|
private val SmtpHost = "smtp.host"
|
||||||
private val SmtpPort = "smtp.port"
|
private val SmtpPort = "smtp.port"
|
||||||
private val SmtpUser = "smtp.user"
|
private val SmtpUser = "smtp.user"
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ object WebHookService {
|
|||||||
refName,
|
refName,
|
||||||
commits.map { commit =>
|
commits.map { commit =>
|
||||||
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
||||||
val commitUrl = repositoryInfo.url.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/commit/" + commit.id
|
val commitUrl = repositoryInfo.httpUrl.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/commit/" + commit.id
|
||||||
|
|
||||||
WebHookCommit(
|
WebHookCommit(
|
||||||
id = commit.id,
|
id = commit.id,
|
||||||
@@ -106,7 +106,7 @@ object WebHookService {
|
|||||||
}.toList,
|
}.toList,
|
||||||
WebHookRepository(
|
WebHookRepository(
|
||||||
name = repositoryInfo.name,
|
name = repositoryInfo.name,
|
||||||
url = repositoryInfo.url,
|
url = repositoryInfo.httpUrl,
|
||||||
description = repositoryInfo.repository.description.getOrElse(""),
|
description = repositoryInfo.repository.description.getOrElse(""),
|
||||||
watchers = 0,
|
watchers = 0,
|
||||||
forks = repositoryInfo.forkedCount,
|
forks = repositoryInfo.forkedCount,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.eclipse.jgit.patch._
|
|||||||
import org.eclipse.jgit.api.errors.PatchFormatException
|
import org.eclipse.jgit.api.errors.PatchFormatException
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
import scala.Some
|
import scala.Some
|
||||||
|
import service.RepositoryService.RepositoryInfo
|
||||||
|
|
||||||
|
|
||||||
object WikiService {
|
object WikiService {
|
||||||
@@ -40,6 +41,10 @@ object WikiService {
|
|||||||
*/
|
*/
|
||||||
case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
|
case class WikiPageHistoryInfo(name: String, committer: String, message: String, date: Date)
|
||||||
|
|
||||||
|
def httpUrl(repository: RepositoryInfo) = repository.httpUrl.replaceFirst("\\.git\\Z", ".wiki.git")
|
||||||
|
|
||||||
|
def sshUrl(repository: RepositoryInfo, settings: SystemSettingsService.SystemSettings) =
|
||||||
|
repository.sshUrl(settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort)).replaceFirst("\\.git\\Z", ".wiki.git")
|
||||||
}
|
}
|
||||||
|
|
||||||
trait WikiService {
|
trait WikiService {
|
||||||
|
|||||||
130
src/main/scala/ssh/GitCommand.scala
Normal file
130
src/main/scala/ssh/GitCommand.scala
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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
|
||||||
|
import servlet.{Database, CommitLogHook}
|
||||||
|
import service.{AccountService, RepositoryService, SystemSettingsService}
|
||||||
|
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
||||||
|
import javax.servlet.ServletContext
|
||||||
|
|
||||||
|
|
||||||
|
object GitCommand {
|
||||||
|
val CommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GitCommand(val context: ServletContext, val owner: String, val repoName: String) extends Command {
|
||||||
|
self: RepositoryService with AccountService =>
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
||||||
|
protected var err: OutputStream = null
|
||||||
|
protected var in: InputStream = null
|
||||||
|
protected var out: OutputStream = null
|
||||||
|
protected var callback: ExitCallback = null
|
||||||
|
|
||||||
|
protected def runTask(user: String): Unit
|
||||||
|
|
||||||
|
private def newTask(user: String): Runnable = new Runnable {
|
||||||
|
override def run(): Unit = {
|
||||||
|
Database(context) withTransaction {
|
||||||
|
try {
|
||||||
|
runTask(user)
|
||||||
|
callback.onExit(0)
|
||||||
|
} catch {
|
||||||
|
case e: RepositoryNotFoundException =>
|
||||||
|
logger.info(e.getMessage)
|
||||||
|
callback.onExit(1, "Repository Not Found")
|
||||||
|
case e: Throwable =>
|
||||||
|
logger.error(e.getMessage, e)
|
||||||
|
callback.onExit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def start(env: Environment): Unit = {
|
||||||
|
val user = env.getEnv.get("USER")
|
||||||
|
val thread = new Thread(newTask(user))
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def isWritableUser(username: String, repositoryInfo: RepositoryService.RepositoryInfo): Boolean =
|
||||||
|
getAccountByUserName(username) match {
|
||||||
|
case Some(account) => hasWritePermission(repositoryInfo.owner, repositoryInfo.name, Some(account))
|
||||||
|
case None => false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class GitUploadPack(context: ServletContext, owner: String, repoName: String, baseUrl: String) extends GitCommand(context, owner, repoName)
|
||||||
|
with RepositoryService with AccountService {
|
||||||
|
|
||||||
|
override protected def runTask(user: String): Unit = {
|
||||||
|
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo =>
|
||||||
|
if(!repositoryInfo.repository.isPrivate || isWritableUser(user, repositoryInfo)){
|
||||||
|
using(Git.open(getRepositoryDir(owner, repoName))) { git =>
|
||||||
|
val repository = git.getRepository
|
||||||
|
val upload = new UploadPack(repository)
|
||||||
|
upload.upload(in, out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class GitReceivePack(context: ServletContext, owner: String, repoName: String, baseUrl: String) extends GitCommand(context, owner, repoName)
|
||||||
|
with SystemSettingsService with RepositoryService with AccountService {
|
||||||
|
|
||||||
|
override protected def runTask(user: String): Unit = {
|
||||||
|
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""), baseUrl).foreach { repositoryInfo =>
|
||||||
|
if(isWritableUser(user, repositoryInfo)){
|
||||||
|
using(Git.open(getRepositoryDir(owner, repoName))) { git =>
|
||||||
|
val repository = git.getRepository
|
||||||
|
val receive = new ReceivePack(repository)
|
||||||
|
if(!repoName.endsWith(".wiki")){
|
||||||
|
receive.setPostReceiveHook(new CommitLogHook(owner, repoName, user, baseUrl))
|
||||||
|
}
|
||||||
|
receive.receive(in, out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class GitCommandFactory(context: ServletContext, baseUrl: String) extends CommandFactory {
|
||||||
|
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
|
||||||
|
|
||||||
|
override def createCommand(command: String): Command = {
|
||||||
|
logger.debug(s"command: $command")
|
||||||
|
command match {
|
||||||
|
case GitCommand.CommandRegex("upload", owner, repoName) => new GitUploadPack(context, owner, repoName, baseUrl)
|
||||||
|
case GitCommand.CommandRegex("receive", owner, repoName) => new GitReceivePack(context, owner, repoName, baseUrl)
|
||||||
|
case _ => new UnknownCommand(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/main/scala/ssh/NoShell.scala
Normal file
62
src/main/scala/ssh/NoShell.scala
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import org.apache.sshd.common.Factory
|
||||||
|
import org.apache.sshd.server.{Environment, ExitCallback, Command}
|
||||||
|
import java.io.{OutputStream, InputStream}
|
||||||
|
import org.eclipse.jgit.lib.Constants
|
||||||
|
import service.SystemSettingsService
|
||||||
|
|
||||||
|
class NoShell extends Factory[Command] with SystemSettingsService {
|
||||||
|
override def create(): Command = new Command() {
|
||||||
|
private var in: InputStream = null
|
||||||
|
private var out: OutputStream = null
|
||||||
|
private var err: OutputStream = null
|
||||||
|
private var callback: ExitCallback = null
|
||||||
|
|
||||||
|
override def start(env: Environment): Unit = {
|
||||||
|
val user = env.getEnv.get("USER")
|
||||||
|
val port = loadSystemSettings().sshPort.getOrElse(SystemSettingsService.DefaultSshPort)
|
||||||
|
val message =
|
||||||
|
"""
|
||||||
|
| Welcome to
|
||||||
|
| _____ _ _ ____ _ _
|
||||||
|
| / ____| (_) | | | _ \ | | | |
|
||||||
|
| | | __ _ | |_ | |_) | _ _ ___ | | __ ___ | |_
|
||||||
|
| | | |_ | | | | __| | _ < | | | | / __| | |/ / / _ \ | __|
|
||||||
|
| | |__| | | | | |_ | |_) | | |_| | | (__ | < | __/ | |_
|
||||||
|
| \_____| |_| \__| |____/ \__,_| \___| |_|\_\ \___| \__|
|
||||||
|
|
|
||||||
|
| Successfully SSH Access.
|
||||||
|
| But interactive shell is disabled.
|
||||||
|
|
|
||||||
|
| Please use:
|
||||||
|
|
|
||||||
|
| git clone ssh://%s@GITBUCKET_HOST:%d/OWNER/REPOSITORY_NAME.git
|
||||||
|
""".stripMargin.format(user, port).replace("\n", "\r\n") + "\r\n"
|
||||||
|
err.write(Constants.encode(message))
|
||||||
|
err.flush()
|
||||||
|
in.close()
|
||||||
|
out.close()
|
||||||
|
err.close()
|
||||||
|
callback.onExit(127)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def destroy(): Unit = {}
|
||||||
|
|
||||||
|
override def setInputStream(in: InputStream): Unit = {
|
||||||
|
this.in = in
|
||||||
|
}
|
||||||
|
|
||||||
|
override def setOutputStream(out: OutputStream): Unit = {
|
||||||
|
this.out = out
|
||||||
|
}
|
||||||
|
|
||||||
|
override def setErrorStream(err: OutputStream): Unit = {
|
||||||
|
this.err = err
|
||||||
|
}
|
||||||
|
|
||||||
|
override def setExitCallback(callback: ExitCallback): Unit = {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/main/scala/ssh/PublicKeyAuthenticator.scala
Normal file
23
src/main/scala/ssh/PublicKeyAuthenticator.scala
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import org.apache.sshd.server.PublickeyAuthenticator
|
||||||
|
import org.apache.sshd.server.session.ServerSession
|
||||||
|
import java.security.PublicKey
|
||||||
|
import service.SshKeyService
|
||||||
|
import servlet.Database
|
||||||
|
import javax.servlet.ServletContext
|
||||||
|
|
||||||
|
class PublicKeyAuthenticator(context: ServletContext) extends PublickeyAuthenticator with SshKeyService {
|
||||||
|
|
||||||
|
override def authenticate(username: String, key: PublicKey, session: ServerSession): Boolean = {
|
||||||
|
Database(context) withTransaction {
|
||||||
|
getPublicKeys(username).exists { sshKey =>
|
||||||
|
SshUtil.str2PublicKey(sshKey.publicKey) match {
|
||||||
|
case Some(publicKey) => key.equals(publicKey)
|
||||||
|
case _ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
68
src/main/scala/ssh/SshServerListener.scala
Normal file
68
src/main/scala/ssh/SshServerListener.scala
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import javax.servlet.{ServletContext, ServletContextEvent, ServletContextListener}
|
||||||
|
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import util.Directory
|
||||||
|
import service.SystemSettingsService
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
object SshServer {
|
||||||
|
private val logger = LoggerFactory.getLogger(SshServer.getClass)
|
||||||
|
private val server = org.apache.sshd.SshServer.setUpDefaultServer()
|
||||||
|
private val active = new AtomicBoolean(false)
|
||||||
|
|
||||||
|
private def configure(context: ServletContext, port: Int, baseUrl: String) = {
|
||||||
|
server.setPort(port)
|
||||||
|
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(s"${Directory.GitBucketHome}/gitbucket.ser"))
|
||||||
|
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(context))
|
||||||
|
server.setCommandFactory(new GitCommandFactory(context, baseUrl))
|
||||||
|
server.setShellFactory(new NoShell)
|
||||||
|
}
|
||||||
|
|
||||||
|
def start(context: ServletContext, port: Int, baseUrl: String) = {
|
||||||
|
if(active.compareAndSet(false, true)){
|
||||||
|
configure(context, port, baseUrl)
|
||||||
|
server.start()
|
||||||
|
logger.info(s"Start SSH Server Listen on ${server.getPort}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def stop() = {
|
||||||
|
if(active.compareAndSet(true, false)){
|
||||||
|
server.stop(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def isActive = active.get
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Start a SSH Server Daemon
|
||||||
|
*
|
||||||
|
* How to use:
|
||||||
|
* git clone ssh://username@host_or_ip:29418/owner/repository_name.git
|
||||||
|
*/
|
||||||
|
class SshServerListener extends ServletContextListener with SystemSettingsService {
|
||||||
|
|
||||||
|
override def contextInitialized(sce: ServletContextEvent): Unit = {
|
||||||
|
val settings = loadSystemSettings()
|
||||||
|
if(settings.ssh){
|
||||||
|
if(settings.baseUrl.isEmpty){
|
||||||
|
// TODO use logger?
|
||||||
|
println("Could not start SshServer because the baseUrl is not configured.")
|
||||||
|
} else {
|
||||||
|
SshServer.start(sce.getServletContext,
|
||||||
|
settings.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
|
||||||
|
settings.baseUrl.get)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def contextDestroyed(sce: ServletContextEvent): Unit = {
|
||||||
|
if(loadSystemSettings().ssh){
|
||||||
|
SshServer.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
33
src/main/scala/ssh/SshUtil.scala
Normal file
33
src/main/scala/ssh/SshUtil.scala
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import java.security.PublicKey
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.apache.commons.codec.binary.Base64
|
||||||
|
import org.eclipse.jgit.lib.Constants
|
||||||
|
import org.apache.sshd.common.util.{KeyUtils, Buffer}
|
||||||
|
|
||||||
|
object SshUtil {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(SshUtil.getClass)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def fingerPrint(key: String): String = KeyUtils.getFingerPrint(str2PublicKey(key).get)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -45,7 +45,7 @@ class GitBucketLinkRender(context: app.Context, repository: service.RepositorySe
|
|||||||
(text, text)
|
(text, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
val url = repository.url.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/wiki/" + StringUtil.urlEncode(page)
|
val url = repository.httpUrl.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/wiki/" + StringUtil.urlEncode(page)
|
||||||
|
|
||||||
if(getWikiPage(repository.owner, repository.name, page).isDefined){
|
if(getWikiPage(repository.owner, repository.name, page).isDefined){
|
||||||
new Rendering(url, label)
|
new Rendering(url, label)
|
||||||
@@ -104,7 +104,7 @@ class GitBucketHtmlSerializer(
|
|||||||
if(!enableWikiLink || url.startsWith("http://") || url.startsWith("https://")){
|
if(!enableWikiLink || url.startsWith("http://") || url.startsWith("https://")){
|
||||||
url
|
url
|
||||||
} else {
|
} else {
|
||||||
repository.url.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/wiki/_blob/" + url
|
repository.httpUrl.replaceFirst("/git/", "/").replaceFirst("\\.git$", "") + "/wiki/_blob/" + url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,77 +1,66 @@
|
|||||||
@(account: Option[model.Account], info: Option[Any])(implicit context: app.Context)
|
@(account: model.Account, settings: service.SystemSettingsService.SystemSettings, info: Option[Any])(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@import util.AccountUtil
|
@import util.AccountUtil
|
||||||
@html.main((if(account.isDefined) "Edit your profile" else "Create your account")){
|
@html.main("Edit your profile"){
|
||||||
@helper.html.information(info)
|
<div class="row-fluid">
|
||||||
@if(account.isDefined && AccountUtil.hasLdapDummyMailAddress(account.get)) {
|
<div class="span3">
|
||||||
|
@menu("profile", settings.ssh)
|
||||||
|
</div>
|
||||||
|
<div class="span9">
|
||||||
|
@helper.html.information(info)
|
||||||
|
@if(AccountUtil.hasLdapDummyMailAddress(account)) {
|
||||||
<div class="alert alert-danger">Please register your mail address.</div>
|
<div class="alert alert-danger">Please register your mail address.</div>
|
||||||
}
|
}
|
||||||
@if(account.isDefined){
|
<form action="@url(account.userName)/_edit" method="POST" validate="true">
|
||||||
<h3>Edit your profile</h3>
|
<div class="box">
|
||||||
} else {
|
<div class="box-header">Profile</div>
|
||||||
<h3>Create your account</h3>
|
<div class="box-content">
|
||||||
}
|
<div class="row-fluid">
|
||||||
<form action="@if(account.isDefined){@url(account.get.userName)/_edit}else{@path/register}" method="POST" validate="true">
|
<div class="span6">
|
||||||
<div class="row-fluid">
|
@if(account.password.nonEmpty){
|
||||||
<div class="span6">
|
<fieldset>
|
||||||
@if(account.isEmpty){
|
<label for="password" class="strong">
|
||||||
<fieldset>
|
Password (input to change password):
|
||||||
<label for="userName" class="strong">Username:</label>
|
</label>
|
||||||
<input type="text" name="userName" id="userName" value=""/>
|
<input type="password" name="password" id="password" value=""/>
|
||||||
<span id="error-userName" class="error"></span>
|
<span id="error-password" class="error"></span>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
}
|
}
|
||||||
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
<fieldset>
|
||||||
<fieldset>
|
<label for="fullName" class="strong">Full Name:</label>
|
||||||
<label for="password" class="strong">
|
<input type="text" name="fullName" id="fullName" value="@account.fullName"/>
|
||||||
Password
|
<span id="error-fullName" class="error"></span>
|
||||||
@if(account.nonEmpty){
|
</fieldset>
|
||||||
(input to change password)
|
<fieldset>
|
||||||
}
|
<label for="mailAddress" class="strong">Mail Address:</label>
|
||||||
:
|
<input type="text" name="mailAddress" id="mailAddress" value="@if(!AccountUtil.hasLdapDummyMailAddress(account)){@account.mailAddress}"/>
|
||||||
</label>
|
<span id="error-mailAddress" class="error"></span>
|
||||||
<input type="password" name="password" id="password" value=""/>
|
</fieldset>
|
||||||
<span id="error-password" class="error"></span>
|
<fieldset>
|
||||||
</fieldset>
|
<label for="url" class="strong">URL (optional):</label>
|
||||||
}
|
<input type="text" name="url" id="url" style="width: 300px;" value="@account.url"/>
|
||||||
<fieldset>
|
<span id="error-url" class="error"></span>
|
||||||
<label for="fullName" class="strong">Full Name:</label>
|
</fieldset>
|
||||||
<input type="text" name="fullName" id="fullName" value="@account.map(_.fullName)"/>
|
</div>
|
||||||
<span id="error-fullName" class="error"></span>
|
<div class="span6">
|
||||||
</fieldset>
|
<fieldset>
|
||||||
<fieldset>
|
<label for="avatar" class="strong">Image (optional):</label>
|
||||||
<label for="mailAddress" class="strong">Mail Address:</label>
|
@helper.html.uploadavatar(Some(account))
|
||||||
<input type="text" name="mailAddress" id="mailAddress" value="@if(account.isDefined && !AccountUtil.hasLdapDummyMailAddress(account.get)){@account.map(_.mailAddress)}"/>
|
</fieldset>
|
||||||
<span id="error-mailAddress" class="error"></span>
|
</div>
|
||||||
</fieldset>
|
</div>
|
||||||
<fieldset>
|
<div style="margin-top: 20px;">
|
||||||
<label for="url" class="strong">URL (optional):</label>
|
<div class="pull-right">
|
||||||
<input type="text" name="url" id="url" style="width: 400px;" value="@account.map(_.url)"/>
|
<a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
|
||||||
<span id="error-url" class="error"></span>
|
</div>
|
||||||
</fieldset>
|
<input type="submit" class="btn btn-success" value="Save"/>
|
||||||
</div>
|
@if(!AccountUtil.hasLdapDummyMailAddress(account)){<a href="@url(account.userName)" class="btn">Cancel</a>}
|
||||||
<div class="span6">
|
</div>
|
||||||
<fieldset>
|
|
||||||
<label for="avatar" class="strong">Image (optional):</label>
|
|
||||||
@helper.html.uploadavatar(account)
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="margin">
|
|
||||||
@if(account.isDefined){
|
|
||||||
<div class="pull-right">
|
|
||||||
<a href="@path/@account.get.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
|
|
||||||
</div>
|
|
||||||
<input type="submit" class="btn btn-success" value="Save"/>
|
|
||||||
@if(!AccountUtil.hasLdapDummyMailAddress(account.get)){
|
|
||||||
<a href="@url(account.get.userName)" class="btn">Cancel</a>
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
<input type="submit" class="btn btn-success" value="Create account"/>
|
|
||||||
}
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
@@ -79,4 +68,4 @@ $(function(){
|
|||||||
return confirm('Once you delete your account, there is no going back.\nAre you sure?');
|
return confirm('Once you delete your account, there is no going back.\nAre you sure?');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
14
src/main/twirl/account/menu.scala.html
Normal file
14
src/main/twirl/account/menu.scala.html
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@(active: String, ssh: Boolean)(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
<div class="box">
|
||||||
|
<ul class="nav nav-tabs nav-stacked side-menu">
|
||||||
|
<li@if(active=="profile"){ class="active"}>
|
||||||
|
<a href="@path/@loginAccount.get.userName/_edit">Profile</a>
|
||||||
|
</li>
|
||||||
|
@if(ssh){
|
||||||
|
<li@if(active=="ssh"){ class="active"}>
|
||||||
|
<a href="@path/@loginAccount.get.userName/_ssh">SSH Keys</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
48
src/main/twirl/account/register.scala.html
Normal file
48
src/main/twirl/account/register.scala.html
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
@()(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
@html.main("Create your account"){
|
||||||
|
<h3>Create your account</h3>
|
||||||
|
<form action="@path/register" method="POST" validate="true">
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span6">
|
||||||
|
<fieldset>
|
||||||
|
<label for="userName" class="strong">Username:</label>
|
||||||
|
<input type="text" name="userName" id="userName" value=""/>
|
||||||
|
<span id="error-userName" class="error"></span>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="password" class="strong">
|
||||||
|
Password:
|
||||||
|
</label>
|
||||||
|
<input type="password" name="password" id="password" value=""/>
|
||||||
|
<span id="error-password" class="error"></span>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="fullName" class="strong">Full Name:</label>
|
||||||
|
<input type="text" name="fullName" id="fullName" value=""/>
|
||||||
|
<span id="error-fullName" class="error"></span>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="mailAddress" class="strong">Mail Address:</label>
|
||||||
|
<input type="text" name="mailAddress" id="mailAddress" value=""/>
|
||||||
|
<span id="error-mailAddress" class="error"></span>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="url" class="strong">URL (optional):</label>
|
||||||
|
<input type="text" name="url" id="url" style="width: 400px;" value=""/>
|
||||||
|
<span id="error-url" class="error"></span>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div class="span6">
|
||||||
|
<fieldset>
|
||||||
|
<label for="avatar" class="strong">Image (optional):</label>
|
||||||
|
@helper.html.uploadavatar(None)
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<fieldset class="margin">
|
||||||
|
<input type="submit" class="btn btn-success" value="Create account"/>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
45
src/main/twirl/account/ssh.scala.html
Normal file
45
src/main/twirl/account/ssh.scala.html
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
@(account: model.Account, settings: service.SystemSettingsService.SystemSettings, sshKeys: List[model.SshKey])(implicit context: app.Context)
|
||||||
|
@import context._
|
||||||
|
@import view.helpers._
|
||||||
|
@html.main("SSH Keys"){
|
||||||
|
<div class="row-fluid">
|
||||||
|
<div class="span3">
|
||||||
|
@menu("ssh", settings.ssh)
|
||||||
|
</div>
|
||||||
|
<div class="span9">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">SSH Keys</div>
|
||||||
|
<div class="box-content">
|
||||||
|
@if(sshKeys.isEmpty){
|
||||||
|
No keys
|
||||||
|
}
|
||||||
|
@sshKeys.zipWithIndex.map { case (key, i) =>
|
||||||
|
@if(i != 0){
|
||||||
|
<hr>
|
||||||
|
}
|
||||||
|
<strong>@key.title</strong> (@_root_.ssh.SshUtil.fingerPrint(key.publicKey))
|
||||||
|
<a href="@path/@account.userName/_ssh/delete/@key.sshKeyId" class="btn btn-mini btn-danger pull-right">Delete</a>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form method="POST" action="@path/@account.userName/_ssh" validate="true">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header">Add an SSH Key</div>
|
||||||
|
<div class="box-content">
|
||||||
|
<fieldset>
|
||||||
|
<label for="title" class="strong">Title</label>
|
||||||
|
<div><span id="error-title" class="error"></span></div>
|
||||||
|
<input type="text" name="title" id="title" style="width: 400px;"/>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<label for="publicKey" class="strong">Key</label>
|
||||||
|
<div><span id="error-publicKey" class="error"></span></div>
|
||||||
|
<textarea name="publicKey" id="publicKey" style="width: 600px; height: 250px;"></textarea>
|
||||||
|
</fieldset>
|
||||||
|
<input type="submit" class="btn btn-success" value="Add"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -22,9 +22,10 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<input type="text" name="baseUrl" id="baseUrl" style="width: 400px" value="@settings.baseUrl"/>
|
<input type="text" name="baseUrl" id="baseUrl" style="width: 400px" value="@settings.baseUrl"/>
|
||||||
|
<span id="error-baseUrl" class="error"></span>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<p>
|
<p class="muted">
|
||||||
The base URL is used for redirect, notification email, git repository URL box and more.
|
The base URL is used for redirect, notification email, git repository URL box and more.
|
||||||
If the base URL is empty, GitBucket generates URL from request information.
|
If the base URL is empty, GitBucket generates URL from request information.
|
||||||
You can use this property to adjust URL difference between the reverse proxy and GitBucket.
|
You can use this property to adjust URL difference between the reverse proxy and GitBucket.
|
||||||
@@ -56,6 +57,29 @@
|
|||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
|
<!-- SSH access -->
|
||||||
|
<!--====================================================================-->
|
||||||
|
<hr>
|
||||||
|
<label class="strong">SSH access</label>
|
||||||
|
<fieldset>
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" id="ssh" name="ssh"@if(settings.ssh){ checked}/>
|
||||||
|
Enable SSH access to git repository
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<div class="form-horizontal ssh">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="sshPort">SSH Port</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" id="sshPort" name="sshPort" value="@settings.sshPort"/>
|
||||||
|
<span id="error-sshPort" class="error"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="muted">
|
||||||
|
Base URL is required if SSH access is enabled.
|
||||||
|
</p>
|
||||||
|
<!--====================================================================-->
|
||||||
<!-- Authentication -->
|
<!-- Authentication -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
@@ -213,6 +237,10 @@
|
|||||||
}
|
}
|
||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
|
$('#ssh').change(function(){
|
||||||
|
$('.ssh input').prop('disabled', !$(this).prop('checked'));
|
||||||
|
}).change();
|
||||||
|
|
||||||
$('#notification').change(function(){
|
$('#notification').change(function(){
|
||||||
$('.notification input').prop('disabled', !$(this).prop('checked'));
|
$('.notification input').prop('disabled', !$(this).prop('checked'));
|
||||||
}).change();
|
}).change();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<div class="input-prepend">
|
<div class="input-prepend">
|
||||||
<a href="@path/@repository.owner/@repository.name/fork" class="btn" style="margin-bottom: 10px;">Fork</a>
|
<a href="@path/@repository.owner/@repository.name/fork" class="btn" style="margin-bottom: 10px;">Fork</a>
|
||||||
<span class="add-on"><a href="@url(repository)/network/members">@repository.forkedCount</a></span>
|
<span class="add-on count"><a href="@url(repository)/network/members">@repository.forkedCount</a></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
@(id: String, value: String)(html: Html)
|
@(id: String, value: String, prepend: Boolean = false)(html: Html)
|
||||||
<div class="input-append">
|
<div class="input-append@if(prepend){ input-prepend}">
|
||||||
@html
|
@html
|
||||||
<span id="@id" class="add-on btn" data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="icon-check"></i></span>
|
<span id="@id" class="add-on btn" data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="icon-check"></i></span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
pathList: List[String],
|
pathList: List[String],
|
||||||
content: util.JGitUtil.ContentInfo,
|
content: util.JGitUtil.ContentInfo,
|
||||||
latestCommit: util.JGitUtil.CommitInfo)(implicit context: app.Context)
|
latestCommit: util.JGitUtil.CommitInfo,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@html.header("code", repository)
|
@html.header("code", repository)
|
||||||
@tab(branch, repository, "files")
|
@tab(branch, repository, "files", settings)
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
|
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> /
|
||||||
@pathList.zipWithIndex.map { case (section, i) =>
|
@pathList.zipWithIndex.map { case (section, i) =>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
@(branchInfo: Seq[(String, java.util.Date)],
|
@(branchInfo: Seq[(String, java.util.Date)],
|
||||||
hasWritePermission: Boolean,
|
hasWritePermission: Boolean,
|
||||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@html.header("code", repository)
|
@html.header("code", repository)
|
||||||
@tab(repository.repository.defaultBranch, repository, "branches", true)
|
@tab(repository.repository.defaultBranch, repository, "branches", settings, true)
|
||||||
<h1>Branches</h1>
|
<h1>Branches</h1>
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -4,13 +4,14 @@
|
|||||||
tags: List[String],
|
tags: List[String],
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
diffs: Seq[util.JGitUtil.DiffInfo],
|
diffs: Seq[util.JGitUtil.DiffInfo],
|
||||||
oldCommitId: Option[String])(implicit context: app.Context)
|
oldCommitId: Option[String],
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@import util.Implicits._
|
@import util.Implicits._
|
||||||
@html.main(commit.shortMessage, Some(repository)){
|
@html.main(commit.shortMessage, Some(repository)){
|
||||||
@html.header("code", repository)
|
@html.header("code", repository)
|
||||||
@tab(commitId, repository, "commits")
|
@tab(commitId, repository, "commits", settings)
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
commits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
commits: Seq[Seq[util.JGitUtil.CommitInfo]],
|
||||||
page: Int,
|
page: Int,
|
||||||
hasNext: Boolean)(implicit context: app.Context)
|
hasNext: Boolean,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@html.header("code", repository)
|
@html.header("code", repository)
|
||||||
@tab(branch, repository, if(pathList.isEmpty) "commits" else "files")
|
@tab(branch, repository, if(pathList.isEmpty) "commits" else "files", settings)
|
||||||
<div class="head">
|
<div class="head">
|
||||||
@if(pathList.isEmpty){
|
@if(pathList.isEmpty){
|
||||||
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> / Commit History
|
<a href="@url(repository)/tree/@encodeRefName(branch)">@repository.name</a> / Commit History
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
pathList: List[String],
|
pathList: List[String],
|
||||||
latestCommit: util.JGitUtil.CommitInfo,
|
latestCommit: util.JGitUtil.CommitInfo,
|
||||||
files: List[util.JGitUtil.FileInfo],
|
files: List[util.JGitUtil.FileInfo],
|
||||||
readme: Option[(util.JGitUtil.FileInfo, String)])(implicit context: app.Context)
|
readme: Option[(util.JGitUtil.FileInfo, String)],
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@html.header("code", repository)
|
@html.header("code", repository)
|
||||||
@tab(branch, repository, "files")
|
@tab(branch, repository, "files", settings)
|
||||||
<div class="head">
|
<div class="head">
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@defining(repository.commitCount){ commitCount =>
|
@defining(repository.commitCount){ commitCount =>
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ touch README.md
|
|||||||
git init
|
git init
|
||||||
git add README.md
|
git add README.md
|
||||||
git commit -m "first commit"
|
git commit -m "first commit"
|
||||||
git remote add origin @repository.url
|
git remote add origin @repository.httpUrl
|
||||||
git push -u origin master
|
git push -u origin master
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<h3 style="margin-top: 30px;">Push an existing repository from the command line</h3>
|
<h3 style="margin-top: 30px;">Push an existing repository from the command line</h3>
|
||||||
<pre>
|
<pre>
|
||||||
git remote add origin @repository.url
|
git remote add origin @repository.httpUrl
|
||||||
git push -u origin master
|
git push -u origin master
|
||||||
</pre>
|
</pre>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
@(id: String, repository: service.RepositoryService.RepositoryInfo, active: String,
|
@(id: String, repository: service.RepositoryService.RepositoryInfo, active: String,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings,
|
||||||
hideBranchPulldown: Boolean = false)(implicit context: app.Context)
|
hideBranchPulldown: Boolean = false)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@@ -22,8 +23,15 @@
|
|||||||
<li@if(active=="branches"){ class="active"}><a href="@url(repository)/branches">Branches@if(repository.branchList.length > 0){ <span class="badge">@repository.branchList.length</span>}</a></li>
|
<li@if(active=="branches"){ class="active"}><a href="@url(repository)/branches">Branches@if(repository.branchList.length > 0){ <span class="badge">@repository.branchList.length</span>}</a></li>
|
||||||
<li@if(active=="tags" ){ class="active"}><a href="@url(repository)/tags">Tags@if(repository.tags.length > 0){ <span class="badge">@repository.tags.length</span>}</a></li>
|
<li@if(active=="tags" ){ class="active"}><a href="@url(repository)/tags">Tags@if(repository.tags.length > 0){ <span class="badge">@repository.tags.length</span>}</a></li>
|
||||||
<li class="pull-right">
|
<li class="pull-right">
|
||||||
@helper.html.copy("repository-url-copy", repository.url){
|
@helper.html.copy("repository-url-copy", repository.httpUrl, true){
|
||||||
<input type="text" value="@repository.url" id="repository-url" readonly>
|
@if(settings.ssh){
|
||||||
|
<div class="btn-group add-on" data-toggle="buttons-radio" style="padding: 0px; border-width: 0px;">
|
||||||
|
<button type="button" class="btn active" id="repository-url-http">HTTP</button><button type="button" class="btn" id="repository-url-ssh">SSH</button>
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<span class="add-on">HTTP</span>
|
||||||
|
}
|
||||||
|
<input type="text" value="@repository.httpUrl" id="repository-url" style="width: 150px;" readonly>
|
||||||
}
|
}
|
||||||
</li>
|
</li>
|
||||||
<li class="pull-right" style="margin-right: 8px;">
|
<li class="pull-right" style="margin-right: 8px;">
|
||||||
@@ -32,3 +40,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@if(settings.ssh){
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('#repository-url-http').click(function(){
|
||||||
|
$('#repository-url').val('@repository.httpUrl');
|
||||||
|
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
|
||||||
|
});
|
||||||
|
$('#repository-url-ssh').click(function(){
|
||||||
|
$('#repository-url').val('@repository.sshUrl(settings.sshPort.getOrElse(service.SystemSettingsService.DefaultSshPort))');
|
||||||
|
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
@(repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
@(repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
@html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||||
@html.header("code", repository)
|
@html.header("code", repository)
|
||||||
@tab(repository.repository.defaultBranch, repository, "tags", true)
|
@tab(repository.repository.defaultBranch, repository, "tags", settings, true)
|
||||||
<h1>Tags</h1>
|
<h1>Tags</h1>
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
diffs: Seq[util.JGitUtil.DiffInfo],
|
diffs: Seq[util.JGitUtil.DiffInfo],
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
hasWritePermission: Boolean,
|
hasWritePermission: Boolean,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings,
|
||||||
info: Option[Any])(implicit context: app.Context)
|
info: Option[Any])(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@@ -11,7 +12,7 @@
|
|||||||
@html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
|
@html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@helper.html.information(info)
|
@helper.html.information(info)
|
||||||
@html.header("wiki", repository)
|
@html.header("wiki", repository)
|
||||||
@tab("history", repository)
|
@tab("history", repository, settings)
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li>
|
<li>
|
||||||
<h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1>
|
<h1 class="wiki-title"><span class="muted">Compare Revisions</span></h1>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
@(pageName: String,
|
@(pageName: String,
|
||||||
page: Option[service.WikiService.WikiPageInfo],
|
page: Option[service.WikiService.WikiPageInfo],
|
||||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${if(pageName.isEmpty) "New Page" else pageName} - ${repository.owner}/${repository.name}", Some(repository)){
|
@html.main(s"${if(pageName.isEmpty) "New Page" else pageName} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@html.header("wiki", repository)
|
@html.header("wiki", repository)
|
||||||
@tab("", repository)
|
@tab("", repository, settings)
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li>
|
<li>
|
||||||
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
<h1 class="wiki-title"><span class="muted">Editing</span> @if(pageName.isEmpty){New Page} else {@pageName}</h1>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
@(pageName: Option[String],
|
@(pageName: Option[String],
|
||||||
commits: List[util.JGitUtil.CommitInfo],
|
commits: List[util.JGitUtil.CommitInfo],
|
||||||
repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"History - ${repository.owner}/${repository.name}", Some(repository)){
|
@html.main(s"History - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@html.header("wiki", repository)
|
@html.header("wiki", repository)
|
||||||
@tab(if(pageName.isEmpty) "history" else "", repository)
|
@tab(if(pageName.isEmpty) "history" else "", repository, settings)
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li>
|
<li>
|
||||||
<h1 class="wiki-title">
|
<h1 class="wiki-title">
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
@(pageName: String,
|
@(pageName: String,
|
||||||
page: service.WikiService.WikiPageInfo,
|
page: service.WikiService.WikiPageInfo,
|
||||||
repository: service.RepositoryService.RepositoryInfo,
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
hasWritePermission: Boolean)(implicit context: app.Context)
|
hasWritePermission: Boolean,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){
|
@html.main(s"${pageName} - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@html.header("wiki", repository)
|
@html.header("wiki", repository)
|
||||||
@tab((if(pageName == "Home") "home" else ""), repository)
|
@tab((if(pageName == "Home") "home" else ""), repository, settings)
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li>
|
<li>
|
||||||
<h1 class="wiki-title">@pageName</h1>
|
<h1 class="wiki-title">@pageName</h1>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
@(pages: List[String], repository: service.RepositoryService.RepositoryInfo, hasWritePermission: Boolean)(implicit context: app.Context)
|
@(pages: List[String],
|
||||||
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
hasWritePermission: Boolean,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
@html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){
|
@html.main(s"Pages - ${repository.owner}/${repository.name}", Some(repository)){
|
||||||
@html.header("wiki", repository)
|
@html.header("wiki", repository)
|
||||||
@tab("pages", repository)
|
@tab("pages", repository, settings)
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li>
|
<li>
|
||||||
<h1 class="wiki-title"><span class="muted">Pages</span></h1>
|
<h1 class="wiki-title"><span class="muted">Pages</span></h1>
|
||||||
|
|||||||
@@ -1,15 +1,37 @@
|
|||||||
@(active: String, repository: service.RepositoryService.RepositoryInfo)(implicit context: app.Context)
|
@(active: String,
|
||||||
|
repository: service.RepositoryService.RepositoryInfo,
|
||||||
|
settings: service.SystemSettingsService.SystemSettings)(implicit context: app.Context)
|
||||||
@import context._
|
@import context._
|
||||||
|
@import service.WikiService._
|
||||||
@import view.helpers._
|
@import view.helpers._
|
||||||
<ul class="nav nav-tabs">
|
<ul class="nav nav-tabs">
|
||||||
<li@if(active == "home" ){ class="active"}><a href="@url(repository)/wiki">Home</a></li>
|
<li@if(active == "home" ){ class="active"}><a href="@url(repository)/wiki">Home</a></li>
|
||||||
<li@if(active == "pages" ){ class="active"}><a href="@url(repository)/wiki/_pages">Pages</a></li>
|
<li@if(active == "pages" ){ class="active"}><a href="@url(repository)/wiki/_pages">Pages</a></li>
|
||||||
<li@if(active == "history"){ class="active"}><a href="@url(repository)/wiki/_history">Wiki History</a></li>
|
<li@if(active == "history"){ class="active"}><a href="@url(repository)/wiki/_history">Wiki History</a></li>
|
||||||
<li class="pull-right">
|
<li class="pull-right">
|
||||||
@defining(repository.url.replaceFirst("\\.git$", ".wiki.git")){ repositoryUrl =>
|
@helper.html.copy("repository-url-copy", httpUrl(repository), true){
|
||||||
@helper.html.copy("repository-url-copy", repositoryUrl){
|
@if(settings.ssh){
|
||||||
<input type="text" value="@repositoryUrl" readonly id="repository-url">
|
<div class="btn-group add-on" data-toggle="buttons-radio" style="padding: 0px; border-width: 0px;">
|
||||||
|
<button type="button" class="btn active" id="repository-url-http">HTTP</button><button type="button" class="btn" id="repository-url-ssh">SSH</button>
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<span class="add-on">HTTP</span>
|
||||||
}
|
}
|
||||||
|
<input type="text" value="@httpUrl(repository)" readonly id="repository-url">
|
||||||
}
|
}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@if(settings.ssh){
|
||||||
|
<script>
|
||||||
|
$(function(){
|
||||||
|
$('#repository-url-http').click(function(){
|
||||||
|
$('#repository-url').val('@httpUrl(repository)');
|
||||||
|
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
|
||||||
|
});
|
||||||
|
$('#repository-url-ssh').click(function(){
|
||||||
|
$('#repository-url').val('@sshUrl(repository, settings)');
|
||||||
|
$('#repository-url-copy').attr('data-clipboard-text', $('#repository-url').val());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
<listener-class>servlet.AutoUpdateListener</listener-class>
|
<listener-class>servlet.AutoUpdateListener</listener-class>
|
||||||
</listener>
|
</listener>
|
||||||
|
|
||||||
|
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
<!-- Scalatra configuration -->
|
<!-- Scalatra configuration -->
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
@@ -25,6 +26,13 @@
|
|||||||
<listener-class>org.scalatra.servlet.ScalatraListener</listener-class>
|
<listener-class>org.scalatra.servlet.ScalatraListener</listener-class>
|
||||||
</listener>
|
</listener>
|
||||||
|
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<!-- SSH Daemon initialized -->
|
||||||
|
<!-- ===================================================================== -->
|
||||||
|
<listener>
|
||||||
|
<listener-class>ssh.SshServerListener</listener-class>
|
||||||
|
</listener>
|
||||||
|
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
<!-- HTTP interface for Git repositories -->
|
<!-- HTTP interface for Git repositories -->
|
||||||
<!-- ===================================================================== -->
|
<!-- ===================================================================== -->
|
||||||
|
|||||||
@@ -80,19 +80,13 @@ table.global-nav th a:link, table.global-nav th a:hover, table.global-nav th a:v
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.input-prepend span.add-on {
|
div.input-prepend span.count {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
-webkit-border-radius: 0 4px 4px 0;
|
-webkit-border-radius: 0 4px 4px 0;
|
||||||
-moz-border-radius: 0 4px 4px 0;
|
-moz-border-radius: 0 4px 4px 0;
|
||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
div.input-prepend span.add-on a {
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ======================================================================== */
|
/* ======================================================================== */
|
||||||
/* General Styles */
|
/* General Styles */
|
||||||
/* ======================================================================== */
|
/* ======================================================================== */
|
||||||
|
|||||||
40
src/test/scala/ssh/GitCommandSpec.scala
Normal file
40
src/test/scala/ssh/GitCommandSpec.scala
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package ssh
|
||||||
|
|
||||||
|
import org.specs2.mutable._
|
||||||
|
import org.specs2.mock.Mockito
|
||||||
|
import org.apache.sshd.server.command.UnknownCommand
|
||||||
|
import javax.servlet.ServletContext
|
||||||
|
|
||||||
|
class GitCommandFactorySpec extends Specification with Mockito {
|
||||||
|
|
||||||
|
val factory = new GitCommandFactory(mock[ServletContext], "http://localhost:8080")
|
||||||
|
|
||||||
|
"createCommand" should {
|
||||||
|
"returns GitReceivePack when command is git-receive-pack" in {
|
||||||
|
factory.createCommand("git-receive-pack '/owner/repo.git'").isInstanceOf[GitReceivePack] must beTrue
|
||||||
|
factory.createCommand("git-receive-pack '/owner/repo.wiki.git'").isInstanceOf[GitReceivePack] must beTrue
|
||||||
|
|
||||||
|
}
|
||||||
|
"returns GitUploadPack when command is git-upload-pack" in {
|
||||||
|
factory.createCommand("git-upload-pack '/owner/repo.git'").isInstanceOf[GitUploadPack] must beTrue
|
||||||
|
factory.createCommand("git-upload-pack '/owner/repo.wiki.git'").isInstanceOf[GitUploadPack] must beTrue
|
||||||
|
|
||||||
|
}
|
||||||
|
"returns UnknownCommand when command is not git-(upload|receive)-pack" in {
|
||||||
|
factory.createCommand("git- '/owner/repo.git'").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
factory.createCommand("git-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
factory.createCommand("git-a-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
factory.createCommand("git-up-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
factory.createCommand("\ngit-upload-pack '/owner/repo.git'").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
}
|
||||||
|
"returns UnknownCommand when git command has no valid arguments" in {
|
||||||
|
// must be: git-upload-pack '/owner/repository_name.git'
|
||||||
|
factory.createCommand("git-upload-pack").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
factory.createCommand("git-upload-pack /owner/repo.git").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
factory.createCommand("git-upload-pack 'owner/repo.git'").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
factory.createCommand("git-upload-pack '/ownerrepo.git'").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
factory.createCommand("git-upload-pack '/owner/repo.wiki'").isInstanceOf[UnknownCommand] must beTrue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -84,6 +84,8 @@ class AvatarImageProviderSpec extends Specification {
|
|||||||
allowAccountRegistration = false,
|
allowAccountRegistration = false,
|
||||||
gravatar = useGravatar,
|
gravatar = useGravatar,
|
||||||
notification = false,
|
notification = false,
|
||||||
|
ssh = false,
|
||||||
|
sshPort = None,
|
||||||
smtp = None,
|
smtp = None,
|
||||||
ldapAuthentication = false,
|
ldapAuthentication = false,
|
||||||
ldap = None)
|
ldap = None)
|
||||||
|
|||||||
Reference in New Issue
Block a user