diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala index a235d78..69168ec 100644 --- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala +++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala @@ -94,9 +94,16 @@ ), "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 @@ }) 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") diff --git a/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala b/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala index fb0d39e..6964732 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRegistry.scala @@ -6,8 +6,8 @@ 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.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 @@ }) .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) } diff --git a/src/main/scala/gitbucket/core/plugin/PluginRepository.scala b/src/main/scala/gitbucket/core/plugin/PluginRepository.scala index 1ef9be9..1e72ca8 100644 --- a/src/main/scala/gitbucket/core/plugin/PluginRepository.scala +++ b/src/main/scala/gitbucket/core/plugin/PluginRepository.scala @@ -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 @@ 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 diff --git a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala index e424762..a4df2bb 100644 --- a/src/main/scala/gitbucket/core/service/SystemSettingsService.scala +++ b/src/main/scala/gitbucket/core/service/SystemSettingsService.scala @@ -70,6 +70,16 @@ 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 @@ getOptionValue(props, SmtpFromName, None) ) ) - } else { - None - }, + } else None, getValue(props, LdapAuthentication, false), if (getValue(props, LdapAuthentication, false)) { Some( @@ -133,9 +141,7 @@ getOptionValue(props, LdapKeystore, None) ) ) - } else { - None - }, + } else None, getValue(props, OidcAuthentication, false), if (getValue(props, OidcAuthentication, false)) { Some( @@ -151,7 +157,17 @@ }, 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 @@ oidc: Option[OIDC], skinName: String, showMailAddress: Boolean, - pluginNetworkInstall: Boolean + pluginNetworkInstall: Boolean, + pluginProxy: Option[Proxy] ) { def baseUrl(request: HttpServletRequest): String = @@ -249,6 +266,13 @@ 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 @@ 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 { diff --git a/src/main/scala/gitbucket/core/util/HttpClientUtil.scala b/src/main/scala/gitbucket/core/util/HttpClientUtil.scala new file mode 100644 index 0000000..b01d435 --- /dev/null +++ b/src/main/scala/gitbucket/core/util/HttpClientUtil.scala @@ -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() + } + } + +} diff --git a/src/main/twirl/gitbucket/core/admin/plugins.scala.html b/src/main/twirl/gitbucket/core/admin/plugins.scala.html index f95ad25..afe2f83 100644 --- a/src/main/twirl/gitbucket/core/admin/plugins.scala.html +++ b/src/main/twirl/gitbucket/core/admin/plugins.scala.html @@ -3,8 +3,6 @@ @gitbucket.core.admin.html.menu("plugins") { @gitbucket.core.helper.html.information(info)