Merge branch 'master' into fix-slick-stackoverflow

This commit is contained in:
Naoki Takezoe
2018-08-12 01:51:09 +09:00
11 changed files with 181 additions and 31 deletions

View File

@@ -4,8 +4,9 @@ import com.typesafe.sbt.pgp.PgpKeys._
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.27.0"
val ScalatraVersion = "2.6.1"
val JettyVersion = "9.4.7.v20170914"
val ScalatraVersion = "2.6.3"
val JettyVersion = "9.4.11.v20180605"
val JgitVersion = "5.0.2.201807311906-r"
lazy val root = (project in file("."))
.enablePlugins(SbtTwirl, ScalatraPlugin)
@@ -30,8 +31,8 @@ resolvers ++= Seq(
)
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "5.0.1.201806211838-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "5.0.1.201806211838-r",
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
"org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,

View File

@@ -1 +1 @@
sbt.version=1.1.6
sbt.version=1.2.0

View File

@@ -94,9 +94,16 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
),
"skinName" -> trim(label("AdminLTE skin name", text(required))),
"showMailAddress" -> trim(label("Show mail address", boolean())),
"pluginNetworkInstall" -> new SingleValueType[Boolean] {
override def convert(value: String, messages: Messages): Boolean = context.settings.pluginNetworkInstall
}
"pluginNetworkInstall" -> trim(label("Network plugin installation", boolean())),
"proxy" -> optionalIfNotChecked(
"useProxy",
mapping(
"host" -> trim(label("Proxy host", text(required))),
"port" -> trim(label("Proxy port", number())),
"user" -> trim(label("Keystore", optional(text()))),
"password" -> trim(label("Keystore", optional(text())))
)(Proxy.apply)
)
)(SystemSettings.apply).verifying { settings =>
Vector(
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
@@ -380,11 +387,6 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
})
post("/admin/plugins/_reload")(adminOnly {
// Update configuration
val pluginNetworkInstall = params.get("pluginNetworkInstall").map(_.toBoolean).getOrElse(false)
saveSystemSettings(context.settings.copy(pluginNetworkInstall = pluginNetworkInstall))
// Reload plugins
PluginRegistry.reload(request.getServletContext(), loadSystemSettings(), request2Session(request).conn)
flash += "info" -> "All plugins were reloaded."
redirect("/admin/plugins")

View File

@@ -6,8 +6,8 @@ import java.nio.file.{Files, Paths, StandardWatchEventKinds}
import java.util.Base64
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ConcurrentHashMap
import javax.servlet.ServletContext
import javax.servlet.ServletContext
import com.github.zafarkhaja.semver.Version
import gitbucket.core.controller.{Context, ControllerBase}
import gitbucket.core.model.{Account, Issue}
@@ -18,10 +18,12 @@ import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.DatabaseConfig
import gitbucket.core.util.Directory._
import gitbucket.core.util.HttpClientUtil._
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
import io.github.gitbucket.solidbase.model.Module
import org.apache.commons.io.FileUtils
import org.apache.http.client.methods.HttpGet
import org.apache.sshd.server.Command
import org.slf4j.LoggerFactory
import play.twirl.api.Html
@@ -253,8 +255,17 @@ object PluginRegistry {
})
.foreach(_.delete())
val in = url.openStream()
FileUtils.copyToFile(in, new File(PluginHome, new File(url.getFile).getName))
withHttpClient(settings.pluginProxy) { httpClient =>
val httpGet = new HttpGet(url.toString)
try {
val response = httpClient.execute(httpGet)
val in = response.getEntity.getContent
FileUtils.copyToFile(in, new File(PluginHome, new File(url.getFile).getName))
} finally {
httpGet.releaseConnection()
}
}
instance = new PluginRegistry()
initialize(context, settings, conn)
}

View File

@@ -1,7 +1,12 @@
package gitbucket.core.plugin
import gitbucket.core.controller.Context
import gitbucket.core.util.SyntaxSugars.using
import gitbucket.core.util.HttpClientUtil._
import org.json4s._
import org.apache.commons.io.IOUtils
import org.apache.http.client.methods.HttpGet
import org.slf4j.LoggerFactory
object PluginRepository {
@@ -12,18 +17,28 @@ object PluginRepository {
org.json4s.jackson.JsonMethods.parse(json).extract[Seq[PluginMetadata]]
}
def getPlugins(): Seq[PluginMetadata] = {
def getPlugins()(implicit context: Context): Seq[PluginMetadata] = {
try {
val url = new java.net.URL("https://plugins.gitbucket-community.org/releases/plugins.json")
val str = IOUtils.toString(url, "UTF-8")
parsePluginJson(str)
withHttpClient(context.settings.pluginProxy) { httpClient =>
val httpGet = new HttpGet(url.toString)
try {
val response = httpClient.execute(httpGet)
using(response.getEntity.getContent) { in =>
val str = IOUtils.toString(in, "UTF-8")
parsePluginJson(str)
}
} finally {
httpGet.releaseConnection()
}
}
} catch {
case t: Throwable =>
logger.warn("Failed to access to the plugin repository: " + t.toString)
Nil
}
}
}
// Mapped from plugins.json

View File

@@ -70,6 +70,16 @@ trait SystemSettingsService {
props.setProperty(SkinName, settings.skinName.toString)
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
props.setProperty(PluginNetworkInstall, settings.pluginNetworkInstall.toString)
settings.pluginProxy.foreach { proxy =>
props.setProperty(PluginProxyHost, proxy.host)
props.setProperty(PluginProxyPort, proxy.port.toString)
proxy.user.foreach { user =>
props.setProperty(PluginProxyUser, user)
}
proxy.password.foreach { password =>
props.setProperty(PluginProxyPassword, password)
}
}
using(new java.io.FileOutputStream(GitBucketConf)) { out =>
props.store(out, null)
@@ -112,9 +122,7 @@ trait SystemSettingsService {
getOptionValue(props, SmtpFromName, None)
)
)
} else {
None
},
} else None,
getValue(props, LdapAuthentication, false),
if (getValue(props, LdapAuthentication, false)) {
Some(
@@ -133,9 +141,7 @@ trait SystemSettingsService {
getOptionValue(props, LdapKeystore, None)
)
)
} else {
None
},
} else None,
getValue(props, OidcAuthentication, false),
if (getValue(props, OidcAuthentication, false)) {
Some(
@@ -151,7 +157,17 @@ trait SystemSettingsService {
},
getValue(props, SkinName, "skin-blue"),
getValue(props, ShowMailAddress, false),
getValue(props, PluginNetworkInstall, false)
getValue(props, PluginNetworkInstall, false),
if (getValue(props, PluginProxyHost, "").nonEmpty) {
Some(
Proxy(
getValue(props, PluginProxyHost, ""),
getValue(props, PluginProxyPort, 8080),
getOptionValue(props, PluginProxyUser, None),
getOptionValue(props, PluginProxyPassword, None)
)
)
} else None
)
}
}
@@ -181,7 +197,8 @@ object SystemSettingsService {
oidc: Option[OIDC],
skinName: String,
showMailAddress: Boolean,
pluginNetworkInstall: Boolean
pluginNetworkInstall: Boolean,
pluginProxy: Option[Proxy]
) {
def baseUrl(request: HttpServletRequest): String =
@@ -249,6 +266,13 @@ object SystemSettingsService {
fromName: Option[String]
)
case class Proxy(
host: String,
port: Int,
user: Option[String],
password: Option[String],
)
case class SshAddress(host: String, port: Int, genericUser: String)
case class Lfs(serverUrl: Option[String])
@@ -298,6 +322,10 @@ object SystemSettingsService {
private val SkinName = "skinName"
private val ShowMailAddress = "showMailAddress"
private val PluginNetworkInstall = "plugin.networkInstall"
private val PluginProxyHost = "plugin.proxy.host"
private val PluginProxyPort = "plugin.proxy.port"
private val PluginProxyUser = "plugin.proxy.user"
private val PluginProxyPassword = "plugin.proxy.password"
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {

View File

@@ -0,0 +1,35 @@
package gitbucket.core.util
import gitbucket.core.service.SystemSettingsService
import org.apache.http.HttpHost
import org.apache.http.auth.{AuthScope, UsernamePasswordCredentials}
import org.apache.http.impl.client.{BasicCredentialsProvider, CloseableHttpClient, HttpClientBuilder}
object HttpClientUtil {
def withHttpClient[T](proxy: Option[SystemSettingsService.Proxy])(f: CloseableHttpClient => T): T = {
val builder = HttpClientBuilder.create.useSystemProperties
proxy.foreach { proxy =>
builder.setProxy(new HttpHost(proxy.host, proxy.port))
for (user <- proxy.user; password <- proxy.password) {
val credential = new BasicCredentialsProvider()
credential.setCredentials(
new AuthScope(proxy.host, proxy.port),
new UsernamePasswordCredentials(user, password)
)
builder.setDefaultCredentialsProvider(credential)
}
}
val httpClient = builder.build()
try {
f(httpClient)
} finally {
httpClient.close()
}
}
}

View File

@@ -3,8 +3,6 @@
@gitbucket.core.admin.html.menu("plugins") {
@gitbucket.core.helper.html.information(info)
<form action="@context.path/admin/plugins/_reload" method="POST" class="pull-right">
<input type="checkbox" name="pluginNetworkInstall" id="pluginNetworkInstall" value="true" @if(context.settings.pluginNetworkInstall){checked}>
<label for="pluginNetworkInstall">Install plugin from <a href="https://plugins.gitbucket-community.org" target="_blank">plugins.gitbucket-community.org</a></label>
<input type="submit" value="Reload plugins" class="btn btn-default">
</form>
<h1 class="system-settings-title">Plugins</h1>

View File

@@ -8,6 +8,7 @@
<ul class="nav nav-tabs fill-width" id="pullreq-tab">
<li><a href="#system">System settings</a></li>
<li><a href="#authentication">Authentication</a></li>
<li><a href="#plugins">Plugins</a></li>
</ul>
<div class="tab-content fill-width" style="padding-top: 20px;">
<div class="tab-pane" id="system">
@@ -16,6 +17,9 @@
<div class="tab-pane" id="authentication">
@settings_authentication(info)
</div>
<div class="tab-pane" id="plugins">
@settings_plugins(info)
</div>
</div>
<hr>
<div class="align-right" style="margin-top: 20px;">
@@ -30,6 +34,9 @@ $(function(){
if(location.hash == '#authentication'){
$('li:has(a[href="#authentication"])').addClass('active');
$('div#authentication').addClass('active');
} else if(location.hash == '#plugins'){
$('li:has(a[href="#plugins"])').addClass('active');
$('div#plugins').addClass('active');
} else {
$('li:has(a[href="#system"])').addClass('active');
$('div#system').addClass('active');

View File

@@ -0,0 +1,52 @@
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
<label class="strong">Plugin repositories</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="pluginNetworkInstall" name="pluginNetworkInstall"@if(context.settings.pluginNetworkInstall){ checked} />
<a href="https://plugins.gitbucket-community.org" target="_blank">plugins.gitbucket-community.org</a>
</label>
</fieldset>
<hr>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="useProxy" name="useProxy"@if(context.settings.pluginProxy.isDefined){ checked} />
Use proxy
</label>
</fieldset>
<div class="proxy">
<div class="form-group">
<label class="control-label col-md-2" for="proxyHost">Proxy host</label>
<div class="col-md-10">
<input type="text" id="proxyHost" name="proxy.host" class="form-control" value="@context.settings.pluginProxy.map(_.host)"/>
<span id="error-proxy_host" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="proxyPort">Proxy port</label>
<div class="col-md-10">
<input type="text" id="proxyPort" name="proxy.port" class="form-control input-mini" value="@context.settings.pluginProxy.map(_.port)"/>
<span id="error-proxy_port" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="proxyUser">Proxy user</label>
<div class="col-md-10">
<input type="text" id="proxyUser" name="proxy.user" class="form-control" value="@context.settings.pluginProxy.map(_.user)"/>
<span id="error-proxy_user" class="error"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-2" for="proxyPassword">Proxy password</label>
<div class="col-md-10">
<input type="text" id="proxyPassword" name="proxy.password" class="form-control" value="@context.settings.pluginProxy.map(_.password)"/>
<span id="error-proxy_password" class="error"></span>
</div>
</div>
</div>
<script>
$(function(){
$('#useProxy').change(function(){
$('.proxy input').prop('disabled', !$(this).prop('checked'));
}).change();
});
</script>

View File

@@ -138,7 +138,8 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
oidc = None,
skinName = "skin-blue",
showMailAddress = false,
pluginNetworkInstall = false
pluginNetworkInstall = false,
pluginProxy = None
)
/**