diff --git a/project/build.scala b/project/build.scala index dbdff71..8a4844c 100644 --- a/project/build.scala +++ b/project/build.scala @@ -44,7 +44,6 @@ "org.apache.sshd" % "apache-sshd" % "0.11.0", "com.typesafe.slick" %% "slick" % "2.1.0", "com.novell.ldap" % "jldap" % "2009-10-07", - "org.quartz-scheduler" % "quartz" % "2.2.1", "com.h2database" % "h2" % "1.4.180", "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime", "org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided", diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index 25dbf5b..568f9b0 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -1,4 +1,4 @@ -import _root_.servlet.{PluginActionInvokeFilter, BasicAuthenticationFilter, TransactionFilter} +import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter} import app._ //import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider import org.scalatra._ @@ -10,7 +10,6 @@ // Register TransactionFilter and BasicAuthenticationFilter at first context.addFilter("transactionFilter", new TransactionFilter) context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") - context.addFilter("pluginActionInvokeFilter", new PluginActionInvokeFilter) context.getFilterRegistration("pluginActionInvokeFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter) context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*") diff --git a/src/main/scala/app/SystemSettingsController.scala b/src/main/scala/app/SystemSettingsController.scala index 57e4eb3..f323347 100644 --- a/src/main/scala/app/SystemSettingsController.scala +++ b/src/main/scala/app/SystemSettingsController.scala @@ -3,15 +3,8 @@ import service.{AccountService, SystemSettingsService} import SystemSettingsService._ import util.AdminAuthenticator -import util.Directory._ -import util.ControlUtil._ import jp.sf.amateras.scalatra.forms._ import ssh.SshServer -import org.apache.commons.io.FileUtils -import java.io.FileInputStream -import plugin.{Plugin, PluginSystem} -import org.scalatra.Ok -import util.Implicits._ class SystemSettingsController extends SystemSettingsControllerBase with AccountService with AdminAuthenticator @@ -85,118 +78,4 @@ redirect("/admin/system") }) - get("/admin/plugins")(adminOnly { - if(enablePluginSystem){ - val installedPlugins = plugin.PluginSystem.plugins - val updatablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "updatable") - admin.plugins.html.installed(installedPlugins, updatablePlugins) - } else NotFound - }) - - post("/admin/plugins/_update", pluginForm)(adminOnly { form => - if(enablePluginSystem){ - deletePlugins(form.pluginIds) - installPlugins(form.pluginIds) - redirect("/admin/plugins") - } else NotFound - }) - - post("/admin/plugins/_delete", pluginForm)(adminOnly { form => - if(enablePluginSystem){ - deletePlugins(form.pluginIds) - redirect("/admin/plugins") - } else NotFound - }) - - get("/admin/plugins/available")(adminOnly { - if(enablePluginSystem){ - val installedPlugins = plugin.PluginSystem.plugins - val availablePlugins = getAvailablePlugins(installedPlugins).filter(_.status == "available") - admin.plugins.html.available(availablePlugins) - } else NotFound - }) - - post("/admin/plugins/_install", pluginForm)(adminOnly { form => - if(enablePluginSystem){ - installPlugins(form.pluginIds) - redirect("/admin/plugins") - } else NotFound - }) - - get("/admin/plugins/console")(adminOnly { - if(enablePluginSystem){ - admin.plugins.html.console() - } else NotFound - }) - - post("/admin/plugins/console")(adminOnly { - if(enablePluginSystem){ - val script = request.getParameter("script") - val result = plugin.ScalaPlugin.eval(script) - Ok() - } else NotFound - }) - - // TODO Move these methods to PluginSystem or Service? - private def deletePlugins(pluginIds: List[String]): Unit = { - pluginIds.foreach { pluginId => - plugin.PluginSystem.uninstall(pluginId) - val dir = new java.io.File(PluginHome, pluginId) - if(dir.exists && dir.isDirectory){ - FileUtils.deleteQuietly(dir) - PluginSystem.uninstall(pluginId) - } - } - } - - private def installPlugins(pluginIds: List[String]): Unit = { - val dir = getPluginCacheDir() - val installedPlugins = plugin.PluginSystem.plugins - getAvailablePlugins(installedPlugins).filter(x => pluginIds.contains(x.id)).foreach { plugin => - val pluginDir = new java.io.File(PluginHome, plugin.id) - if(pluginDir.exists){ - FileUtils.deleteDirectory(pluginDir) - } - FileUtils.copyDirectory(new java.io.File(dir, plugin.repository + "/" + plugin.id), pluginDir) - PluginSystem.installPlugin(plugin.id) - } - } - - private def getAvailablePlugins(installedPlugins: List[Plugin]): List[SystemSettingsControllerBase.AvailablePlugin] = { - val repositoryRoot = getPluginCacheDir() - - if(repositoryRoot.exists && repositoryRoot.isDirectory){ - PluginSystem.repositories.flatMap { repo => - val repoDir = new java.io.File(repositoryRoot, repo.id) - if(repoDir.exists && repoDir.isDirectory){ - repoDir.listFiles.filter(d => d.isDirectory && !d.getName.startsWith(".")).map { plugin => - val propertyFile = new java.io.File(plugin, "plugin.properties") - val properties = new java.util.Properties() - if(propertyFile.exists && propertyFile.isFile){ - using(new FileInputStream(propertyFile)){ in => - properties.load(in) - } - } - SystemSettingsControllerBase.AvailablePlugin( - repository = repo.id, - id = properties.getProperty("id"), - version = properties.getProperty("version"), - author = properties.getProperty("author"), - url = properties.getProperty("url"), - description = properties.getProperty("description"), - status = installedPlugins.find(_.id == properties.getProperty("id")) match { - case Some(x) if(PluginSystem.isUpdatable(x.version, properties.getProperty("version")))=> "updatable" - case Some(x) => "installed" - case None => "available" - }) - } - } else Nil - } - } else Nil - } -} - -object SystemSettingsControllerBase { - case class AvailablePlugin(repository: String, id: String, version: String, - author: String, url: String, description: String, status: String) } diff --git a/src/main/scala/plugin/Plugin.scala b/src/main/scala/plugin/Plugin.scala deleted file mode 100644 index f4a7c1a..0000000 --- a/src/main/scala/plugin/Plugin.scala +++ /dev/null @@ -1,22 +0,0 @@ -package plugin - -import plugin.PluginSystem._ -import java.sql.Connection - -trait Plugin { - val id: String - val version: String - val author: String - val url: String - val description: String - - def repositoryMenus : List[RepositoryMenu] - def globalMenus : List[GlobalMenu] - def repositoryActions : List[RepositoryAction] - def globalActions : List[Action] - def javaScripts : List[JavaScript] -} - -object PluginConnectionHolder { - val threadLocal = new ThreadLocal[Connection] -} \ No newline at end of file diff --git a/src/main/scala/plugin/PluginSystem.scala b/src/main/scala/plugin/PluginSystem.scala deleted file mode 100644 index 153ab6a..0000000 --- a/src/main/scala/plugin/PluginSystem.scala +++ /dev/null @@ -1,194 +0,0 @@ -package plugin - -import javax.servlet.http.{HttpServletResponse, HttpServletRequest} -import org.slf4j.LoggerFactory -import java.util.concurrent.atomic.AtomicBoolean -import util.Directory._ -import util.ControlUtil._ -import org.apache.commons.io.{IOUtils, FileUtils} -import Security._ -import service.PluginService -import model.Profile._ -import profile.simple._ -import java.io.FileInputStream -import java.sql.Connection -import app.Context -import service.RepositoryService.RepositoryInfo - -/** - * Provides extension points to plug-ins. - */ -object PluginSystem extends PluginService { - - private val logger = LoggerFactory.getLogger(PluginSystem.getClass) - - private val initialized = new AtomicBoolean(false) - private val pluginsMap = scala.collection.mutable.Map[String, Plugin]() - private val repositoriesList = scala.collection.mutable.ListBuffer[PluginRepository]() - - def install(plugin: Plugin): Unit = { - pluginsMap.put(plugin.id, plugin) - } - - def plugins: List[Plugin] = pluginsMap.values.toList - - def uninstall(id: String)(implicit session: Session): Unit = { - pluginsMap.remove(id) - - // Delete from PLUGIN table - deletePlugin(id) - - // Drop tables - val pluginDir = new java.io.File(PluginHome) - val sqlFile = new java.io.File(pluginDir, s"${id}/sql/drop.sql") - if(sqlFile.exists){ - val sql = IOUtils.toString(new FileInputStream(sqlFile), "UTF-8") - using(session.conn.createStatement()){ stmt => - stmt.executeUpdate(sql) - } - } - } - - def repositories: List[PluginRepository] = repositoriesList.toList - - /** - * Initializes the plugin system. Load scripts from GITBUCKET_HOME/plugins. - */ - def init()(implicit session: Session): Unit = { - if(initialized.compareAndSet(false, true)){ - // Load installed plugins - val pluginDir = new java.io.File(PluginHome) - if(pluginDir.exists && pluginDir.isDirectory){ - pluginDir.listFiles.filter(f => f.isDirectory && !f.getName.startsWith(".")).foreach { dir => - installPlugin(dir.getName) - } - } - // Add default plugin repositories - repositoriesList += PluginRepository("central", "https://github.com/takezoe/gitbucket_plugins.git") - } - } - - // TODO Method name seems to not so good. - def installPlugin(id: String)(implicit session: Session): Unit = { - val pluginHome = new java.io.File(PluginHome) - val pluginDir = new java.io.File(pluginHome, id) - - val scalaFile = new java.io.File(pluginDir, "plugin.scala") - if(scalaFile.exists && scalaFile.isFile){ - val properties = new java.util.Properties() - using(new java.io.FileInputStream(new java.io.File(pluginDir, "plugin.properties"))){ in => - properties.load(in) - } - - val pluginId = properties.getProperty("id") - val version = properties.getProperty("version") - val author = properties.getProperty("author") - val url = properties.getProperty("url") - val description = properties.getProperty("description") - - val source = s""" - |val id = "${pluginId}" - |val version = "${version}" - |val author = "${author}" - |val url = "${url}" - |val description = "${description}" - """.stripMargin + FileUtils.readFileToString(scalaFile, "UTF-8") - - try { - // Compile and eval Scala source code - ScalaPlugin.eval(pluginDir.listFiles.filter(_.getName.endsWith(".scala.html")).map { file => - ScalaPlugin.compileTemplate( - id.replace("-", ""), - file.getName.stripSuffix(".scala.html"), - IOUtils.toString(new FileInputStream(file))) - }.mkString("\n") + source) - - // Migrate database - val plugin = getPlugin(pluginId) - if(plugin.isEmpty){ - registerPlugin(model.Plugin(pluginId, version)) - migrate(session.conn, pluginId, "0.0") - } else { - updatePlugin(model.Plugin(pluginId, version)) - migrate(session.conn, pluginId, plugin.get.version) - } - } catch { - case e: Throwable => logger.warn(s"Error in plugin loading for ${scalaFile.getAbsolutePath}", e) - } - } - } - - // TODO Should PluginSystem provide a way to migrate resources other than H2? - private def migrate(conn: Connection, pluginId: String, current: String): Unit = { - val pluginDir = new java.io.File(PluginHome) - - // TODO Is ot possible to use this migration system in GitBucket migration? - val dim = current.split("\\.") - val currentVersion = Version(dim(0).toInt, dim(1).toInt) - - val sqlDir = new java.io.File(pluginDir, s"${pluginId}/sql") - if(sqlDir.exists && sqlDir.isDirectory){ - sqlDir.listFiles.filter(_.getName.endsWith(".sql")).map { file => - val array = file.getName.replaceFirst("\\.sql", "").split("_") - Version(array(0).toInt, array(1).toInt) - } - .sorted.reverse.takeWhile(_ > currentVersion) - .reverse.foreach { version => - val sqlFile = new java.io.File(pluginDir, s"${pluginId}/sql/${version.major}_${version.minor}.sql") - val sql = IOUtils.toString(new FileInputStream(sqlFile), "UTF-8") - using(conn.createStatement()){ stmt => - stmt.executeUpdate(sql) - } - } - } - } - - case class Version(major: Int, minor: Int) extends Ordered[Version] { - - override def compare(that: Version): Int = { - if(major != that.major){ - major.compare(that.major) - } else{ - minor.compare(that.minor) - } - } - - def displayString: String = major + "." + minor - } - - def repositoryMenus : List[RepositoryMenu] = pluginsMap.values.flatMap(_.repositoryMenus).toList - def globalMenus : List[GlobalMenu] = pluginsMap.values.flatMap(_.globalMenus).toList - def repositoryActions : List[RepositoryAction] = pluginsMap.values.flatMap(_.repositoryActions).toList - def globalActions : List[Action] = pluginsMap.values.flatMap(_.globalActions).toList - def javaScripts : List[JavaScript] = pluginsMap.values.flatMap(_.javaScripts).toList - - // Case classes to hold plug-ins information internally in GitBucket - case class PluginRepository(id: String, url: String) - case class GlobalMenu(label: String, url: String, icon: String, condition: Context => Boolean) - case class RepositoryMenu(label: String, name: String, url: String, icon: String, condition: Context => Boolean) - case class Action(method: String, path: String, security: Security, function: (HttpServletRequest, HttpServletResponse, Context) => Any) - case class RepositoryAction(method: String, path: String, security: Security, function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any) - case class Button(label: String, href: String) - case class JavaScript(filter: String => Boolean, script: String) - - /** - * Checks whether the plugin is updatable. - */ - def isUpdatable(oldVersion: String, newVersion: String): Boolean = { - if(oldVersion == newVersion){ - false - } else { - val dim1 = oldVersion.split("\\.").map(_.toInt) - val dim2 = newVersion.split("\\.").map(_.toInt) - dim1.zip(dim2).foreach { case (a, b) => - if(a < b){ - return true - } else if(a > b){ - return false - } - } - return false - } - } - -} diff --git a/src/main/scala/plugin/PluginUpdateJob.scala b/src/main/scala/plugin/PluginUpdateJob.scala deleted file mode 100644 index f041e64..0000000 --- a/src/main/scala/plugin/PluginUpdateJob.scala +++ /dev/null @@ -1,66 +0,0 @@ -package plugin - -import util.Directory._ -import org.eclipse.jgit.api.Git -import org.slf4j.LoggerFactory -import org.quartz.{Scheduler, JobExecutionContext, Job} -import org.quartz.JobBuilder._ -import org.quartz.TriggerBuilder._ -import org.quartz.SimpleScheduleBuilder._ - -class PluginUpdateJob extends Job { - - private val logger = LoggerFactory.getLogger(classOf[PluginUpdateJob]) - private var failedCount = 0 - - /** - * Clone or pull all plugin repositories - * - * TODO Support plugin repository access through the proxy server - */ - override def execute(context: JobExecutionContext): Unit = { - try { - if(failedCount > 3){ - logger.error("Skip plugin information updating because failed count is over limit") - } else { - logger.info("Start plugin information updating") - PluginSystem.repositories.foreach { repository => - logger.info(s"Updating ${repository.id}: ${repository.url}...") - val dir = getPluginCacheDir() - val repo = new java.io.File(dir, repository.id) - if(repo.exists){ - // pull if the repository is already cloned - Git.open(repo).pull().call() - } else { - // clone if the repository is not exist - Git.cloneRepository().setURI(repository.url).setDirectory(repo).call() - } - } - logger.info("End plugin information updating") - } - } catch { - case e: Exception => { - failedCount = failedCount + 1 - logger.error("Failed to update plugin information", e) - } - } - } -} - -object PluginUpdateJob { - - def schedule(scheduler: Scheduler): Unit = { - val job = newJob(classOf[PluginUpdateJob]) - .withIdentity("pluginUpdateJob") - .build() - - val trigger = newTrigger() - .withIdentity("pluginUpdateTrigger") - .startNow() - .withSchedule(simpleSchedule().withIntervalInHours(24).repeatForever()) - .build() - - scheduler.scheduleJob(job, trigger) - } - -} \ No newline at end of file diff --git a/src/main/scala/plugin/ScalaPlugin.scala b/src/main/scala/plugin/ScalaPlugin.scala deleted file mode 100644 index 484e621..0000000 --- a/src/main/scala/plugin/ScalaPlugin.scala +++ /dev/null @@ -1,77 +0,0 @@ -package plugin - -import scala.collection.mutable.ListBuffer -import javax.servlet.http.{HttpServletResponse, HttpServletRequest} -import app.Context -import plugin.PluginSystem._ -import plugin.PluginSystem.RepositoryMenu -import plugin.Security._ -import service.RepositoryService.RepositoryInfo -import scala.reflect.runtime.currentMirror -import scala.tools.reflect.ToolBox -import play.twirl.compiler.TwirlCompiler -import scala.io.Codec - -// TODO This is a sample implementation for Scala based plug-ins. -class ScalaPlugin(val id: String, val version: String, - val author: String, val url: String, val description: String) extends Plugin { - - private val repositoryMenuList = ListBuffer[RepositoryMenu]() - private val globalMenuList = ListBuffer[GlobalMenu]() - private val repositoryActionList = ListBuffer[RepositoryAction]() - private val globalActionList = ListBuffer[Action]() - private val javaScriptList = ListBuffer[JavaScript]() - - def repositoryMenus : List[RepositoryMenu] = repositoryMenuList.toList - def globalMenus : List[GlobalMenu] = globalMenuList.toList - def repositoryActions : List[RepositoryAction] = repositoryActionList.toList - def globalActions : List[Action] = globalActionList.toList - def javaScripts : List[JavaScript] = javaScriptList.toList - - def addRepositoryMenu(label: String, name: String, url: String, icon: String)(condition: (Context) => Boolean): Unit = { - repositoryMenuList += RepositoryMenu(label, name, url, icon, condition) - } - - def addGlobalMenu(label: String, url: String, icon: String)(condition: (Context) => Boolean): Unit = { - globalMenuList += GlobalMenu(label, url, icon, condition) - } - - def addGlobalAction(method: String, path: String, security: Security = All())(function: (HttpServletRequest, HttpServletResponse, Context) => Any): Unit = { - globalActionList += Action(method, path, security, function) - } - - def addRepositoryAction(method: String, path: String, security: Security = All())(function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any): Unit = { - repositoryActionList += RepositoryAction(method, path, security, function) - } - - def addJavaScript(filter: String => Boolean, script: String): Unit = { - javaScriptList += JavaScript(filter, script) - } - -} - -object ScalaPlugin { - - def define(id: String, version: String, author: String, url: String, description: String) - = new ScalaPlugin(id, version, author, url, description) - - def eval(source: String): Any = { - val toolbox = currentMirror.mkToolBox() - val tree = toolbox.parse(source) - toolbox.eval(tree) - } - - def compileTemplate(packageName: String, name: String, source: String): String = { - val result = TwirlCompiler.parseAndGenerateCodeNewParser( - Array(packageName, name), - source.getBytes("UTF-8"), - Codec(scala.util.Properties.sourceEncoding), - "", - "play.twirl.api.HtmlFormat.Appendable", - "play.twirl.api.HtmlFormat", - "", - false) - - result.replaceFirst("package .*", "") - } -} diff --git a/src/main/scala/plugin/Security.scala b/src/main/scala/plugin/Security.scala deleted file mode 100644 index c409020..0000000 --- a/src/main/scala/plugin/Security.scala +++ /dev/null @@ -1,36 +0,0 @@ -package plugin - -/** - * Defines enum case classes to specify permission for actions which is provided by plugin. - */ -object Security { - - sealed trait Security - - /** - * All users and guests - */ - case class All() extends Security - - /** - * Only signed-in users - */ - case class Login() extends Security - - /** - * Only repository owner and collaborators - */ - case class Member() extends Security - - /** - * Only repository owner and managers of group repository - */ - case class Owner() extends Security - - /** - * Only administrators - */ - case class Admin() extends Security - -} - diff --git a/src/main/scala/plugin/package.scala b/src/main/scala/plugin/package.scala deleted file mode 100644 index 15fbad9..0000000 --- a/src/main/scala/plugin/package.scala +++ /dev/null @@ -1,56 +0,0 @@ -import java.sql.PreparedStatement -import play.twirl.api.Html -import util.ControlUtil._ -import scala.collection.mutable.ListBuffer - -package object plugin { - - case class Redirect(path: String) - case class Fragment(html: Html) - case class RawData(contentType: String, content: Array[Byte]) - - object db { - // TODO labelled place holder support - def select(sql: String, params: Any*): Seq[Map[String, String]] = { - defining(PluginConnectionHolder.threadLocal.get){ conn => - using(conn.prepareStatement(sql)){ stmt => - setParams(stmt, params: _*) - using(stmt.executeQuery()){ rs => - val list = new ListBuffer[Map[String, String]]() - while(rs.next){ - defining(rs.getMetaData){ meta => - val map = Range(1, meta.getColumnCount + 1).map { i => - val name = meta.getColumnName(i) - (name, rs.getString(name)) - }.toMap - list += map - } - } - list - } - } - } - } - - // TODO labelled place holder support - def update(sql: String, params: Any*): Int = { - defining(PluginConnectionHolder.threadLocal.get){ conn => - using(conn.prepareStatement(sql)){ stmt => - setParams(stmt, params: _*) - stmt.executeUpdate() - } - } - } - - private def setParams(stmt: PreparedStatement, params: Any*): Unit = { - params.zipWithIndex.foreach { case (p, i) => - p match { - case x: String => stmt.setString(i + 1, x) - case x: Int => stmt.setInt(i + 1, x) - case x: Boolean => stmt.setBoolean(i + 1, x) - } - } - } - } - -} diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala index 0a842b0..3fbcce0 100644 --- a/src/main/scala/servlet/AutoUpdateListener.scala +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -11,8 +11,6 @@ import util.JDBCUtil._ import org.eclipse.jgit.api.Git import util.Directory -import plugin.PluginUpdateJob -import service.SystemSettingsService object AutoUpdate { @@ -197,11 +195,10 @@ * Update database schema automatically in the context initializing. */ class AutoUpdateListener extends ServletContextListener { - import org.quartz.impl.StdSchedulerFactory import AutoUpdate._ private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener]) - private val scheduler = StdSchedulerFactory.getDefaultScheduler +// private val scheduler = StdSchedulerFactory.getDefaultScheduler override def contextInitialized(event: ServletContextEvent): Unit = { val dataDir = event.getServletContext.getInitParameter("gitbucket.home") @@ -236,31 +233,9 @@ } logger.debug("End schema update") } - - if(SystemSettingsService.enablePluginSystem){ - getDatabase(context).withSession { implicit session => - logger.debug("Starting plugin system...") - try { - plugin.PluginSystem.init() - - scheduler.start() - PluginUpdateJob.schedule(scheduler) - logger.debug("PluginUpdateJob is started.") - - logger.debug("Plugin system is initialized.") - } catch { - case ex: Throwable => { - logger.error("Failed to initialize plugin system", ex) - ex.printStackTrace() - throw ex - } - } - } - } } def contextDestroyed(sce: ServletContextEvent): Unit = { - scheduler.shutdown() } private def getConnection(servletContext: ServletContext): Connection = diff --git a/src/main/scala/servlet/PluginActionInvokeFilter.scala b/src/main/scala/servlet/PluginActionInvokeFilter.scala deleted file mode 100644 index b74ac73..0000000 --- a/src/main/scala/servlet/PluginActionInvokeFilter.scala +++ /dev/null @@ -1,192 +0,0 @@ -package servlet - -import javax.servlet._ -import javax.servlet.http.{HttpServletResponse, HttpServletRequest} -import org.apache.commons.io.IOUtils -import play.twirl.api.Html -import service.{AccountService, RepositoryService, SystemSettingsService} -import model.{Account, Session} -import util.{JGitUtil, Keys} -import plugin.{RawData, Fragment, PluginConnectionHolder, Redirect} -import service.RepositoryService.RepositoryInfo -import plugin.Security._ - -class PluginActionInvokeFilter extends Filter with SystemSettingsService with RepositoryService with AccountService { - - def init(config: FilterConfig) = {} - - def destroy(): Unit = {} - - def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { - (req, res) match { - case (request: HttpServletRequest, response: HttpServletResponse) => { - Database(req.getServletContext) withTransaction { implicit session => - val path = request.getRequestURI.substring(request.getServletContext.getContextPath.length) - if(!processGlobalAction(path, request, response) && !processRepositoryAction(path, request, response)){ - chain.doFilter(req, res) - } - } - } - } - } - - private def processGlobalAction(path: String, request: HttpServletRequest, response: HttpServletResponse) - (implicit session: Session): Boolean = { - plugin.PluginSystem.globalActions.find(x => - x.method.toLowerCase == request.getMethod.toLowerCase && path.matches(x.path) - ).map { action => - val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account] - val systemSettings = loadSystemSettings() - implicit val context = app.Context(systemSettings, Option(loginAccount), request) - - if(authenticate(action.security, context)){ - val result = try { - PluginConnectionHolder.threadLocal.set(session.conn) - action.function(request, response, context) - } finally { - PluginConnectionHolder.threadLocal.remove() - } - processActionResult(result, request, response, context) - } else { - // TODO NotFound or Error? - } - true - } getOrElse false - } - - private def processRepositoryAction(path: String, request: HttpServletRequest, response: HttpServletResponse) - (implicit session: Session): Boolean = { - val elements = path.split("/") - if(elements.length > 3){ - val owner = elements(1) - val name = elements(2) - val remain = elements.drop(3).mkString("/", "/", "") - - val loginAccount = request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account] - val systemSettings = loadSystemSettings() - implicit val context = app.Context(systemSettings, Option(loginAccount), request) - - getRepository(owner, name, systemSettings.baseUrl(request)).flatMap { repository => - plugin.PluginSystem.repositoryActions.find(x => remain.matches(x.path)).map { action => - if(authenticate(action.security, context, repository)){ - val result = try { - PluginConnectionHolder.threadLocal.set(session.conn) - action.function(request, response, context, repository) - } finally { - PluginConnectionHolder.threadLocal.remove() - } - processActionResult(result, request, response, context) - } else { - // TODO NotFound or Error? - } - true - } - } getOrElse false - } else false - } - - private def processActionResult(result: Any, request: HttpServletRequest, response: HttpServletResponse, - context: app.Context): Unit = { - result match { - case null|None => renderError(request, response, context, 404) - case x: String => renderGlobalHtml(request, response, context, x) - case Some(x: String) => renderGlobalHtml(request, response, context, x) - case x: Html => renderGlobalHtml(request, response, context, x.toString) - case Some(x: Html) => renderGlobalHtml(request, response, context, x.toString) - case x: Fragment => renderFragmentHtml(request, response, context, x.html.toString) - case Some(x: Fragment) => renderFragmentHtml(request, response, context, x.html.toString) - case x: RawData => renderRawData(request, response, context, x) - case Some(x: RawData) => renderRawData(request, response, context, x) - case x: Redirect => response.sendRedirect(x.path) - case Some(x: Redirect) => response.sendRedirect(x.path) - case x: AnyRef => renderJson(request, response, x) - } - } - - /** - * Authentication for global action - */ - private def authenticate(security: Security, context: app.Context)(implicit session: Session): Boolean = { - // Global Action - security match { - case All() => true - case Login() => context.loginAccount.isDefined - case Admin() => context.loginAccount.exists(_.isAdmin) - case _ => false // TODO throw Exception? - } - } - - /** - * Authenticate for repository action - */ - private def authenticate(security: Security, context: app.Context, repository: RepositoryInfo)(implicit session: Session): Boolean = { - if(repository.repository.isPrivate){ - // Private Repository - security match { - case Admin() => context.loginAccount.exists(_.isAdmin) - case Owner() => context.loginAccount.exists { account => - account.userName == repository.owner || - getGroupMembers(repository.owner).exists(m => m.userName == account.userName && m.isManager) - } - case _ => context.loginAccount.exists { account => - account.isAdmin || account.userName == repository.owner || - getCollaborators(repository.owner, repository.name).contains(account.userName) - } - } - } else { - // Public Repository - security match { - case All() => true - case Login() => context.loginAccount.isDefined - case Owner() => context.loginAccount.exists { account => - account.userName == repository.owner || - getGroupMembers(repository.owner).exists(m => m.userName == account.userName && m.isManager) - } - case Member() => context.loginAccount.exists { account => - account.userName == repository.owner || - getCollaborators(repository.owner, repository.name).contains(account.userName) - } - case Admin() => context.loginAccount.exists(_.isAdmin) - } - } - } - - private def renderError(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, error: Int): Unit = { - response.sendError(error) - } - - private def renderGlobalHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, body: String): Unit = { - response.setContentType("text/html; charset=UTF-8") - val html = _root_.html.main("GitBucket", None)(Html(body))(context) - IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream) - } - - private def renderRepositoryHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, repository: RepositoryInfo, body: String): Unit = { - response.setContentType("text/html; charset=UTF-8") - val html = _root_.html.main("GitBucket", None)(_root_.html.menu("", repository)(Html(body))(context))(context) // TODO specify active side menu - IOUtils.write(html.toString.getBytes("UTF-8"), response.getOutputStream) - } - - private def renderFragmentHtml(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, body: String): Unit = { - response.setContentType("text/html; charset=UTF-8") - IOUtils.write(body.getBytes("UTF-8"), response.getOutputStream) - } - - private def renderRawData(request: HttpServletRequest, response: HttpServletResponse, context: app.Context, rawData: RawData): Unit = { - response.setContentType(rawData.contentType) - IOUtils.write(rawData.content, response.getOutputStream) - } - - private def renderJson(request: HttpServletRequest, response: HttpServletResponse, obj: AnyRef): Unit = { - import org.json4s._ - import org.json4s.jackson.Serialization - import org.json4s.jackson.Serialization.write - implicit val formats = Serialization.formats(NoTypeHints) - - val json = write(obj) - - response.setContentType("application/json; charset=UTF-8") - IOUtils.write(json.getBytes("UTF-8"), response.getOutputStream) - } - -} diff --git a/src/main/twirl/admin/plugins/available.scala.html b/src/main/twirl/admin/plugins/available.scala.html deleted file mode 100644 index fcf37a0..0000000 --- a/src/main/twirl/admin/plugins/available.scala.html +++ /dev/null @@ -1,37 +0,0 @@ -@(plugins: List[app.SystemSettingsControllerBase.AvailablePlugin])(implicit context: app.Context) -@import context._ -@import view.helpers._ -@html.main("Plugins"){ - @admin.html.menu("plugins"){ - @tab("available") -
- - - - - - - - @plugins.zipWithIndex.map { case (plugin, i) => - - - - - - - } -
IDVersionProviderDescription
- - @plugin.id - @plugin.version@plugin.author@plugin.description
- -
- } -} - diff --git a/src/main/twirl/admin/plugins/console.scala.html b/src/main/twirl/admin/plugins/console.scala.html deleted file mode 100644 index 3c4158e..0000000 --- a/src/main/twirl/admin/plugins/console.scala.html +++ /dev/null @@ -1,37 +0,0 @@ -@()(implicit context: app.Context) -@import context._ -@import view.helpers._ -@html.main("JavaScript Console"){ - @admin.html.menu("plugins"){ - @tab("console") -
-
-
JavaScript Console
-
-
-
-
-
- -
-
- } -} - - \ No newline at end of file diff --git a/src/main/twirl/admin/plugins/installed.scala.html b/src/main/twirl/admin/plugins/installed.scala.html deleted file mode 100644 index f85c149..0000000 --- a/src/main/twirl/admin/plugins/installed.scala.html +++ /dev/null @@ -1,47 +0,0 @@ -@(plugins: List[plugin.Plugin], - updatablePlugins: List[app.SystemSettingsControllerBase.AvailablePlugin])(implicit context: app.Context) -@import context._ -@import view.helpers._ -@html.main("Plugins"){ - @admin.html.menu("plugins"){ - @tab("installed") -
- - - - - - - - @plugins.zipWithIndex.map { case (plugin, i) => - - - - - - - } -
IDVersionProviderDescription
- - @plugin.id - - @plugin.version - @updatablePlugins.find(_.id == plugin.id).map { x => - (@x.version is available) - } - @plugin.author@plugin.description
- - -
- } -} - diff --git a/src/main/twirl/admin/plugins/tab.scala.html b/src/main/twirl/admin/plugins/tab.scala.html deleted file mode 100644 index 2e9d1ac..0000000 --- a/src/main/twirl/admin/plugins/tab.scala.html +++ /dev/null @@ -1,9 +0,0 @@ -@(active: String)(implicit context: app.Context) -@import context._ - diff --git a/src/main/twirl/main.scala.html b/src/main/twirl/main.scala.html index fa60811..f16ab19 100644 --- a/src/main/twirl/main.scala.html +++ b/src/main/twirl/main.scala.html @@ -60,21 +60,11 @@
  • New group
  • - @plugin.PluginSystem.globalMenus.map { menu => - @if(menu.condition(context)){ - @if(menu.icon.nonEmpty){} else {@menu.label} - } - } @if(loginAccount.get.isAdmin){ } } else { - @plugin.PluginSystem.globalMenus.map { menu => - @if(menu.condition(context)){ - @if(menu.icon.nonEmpty){} else {@menu.label} - } - } Sign in } @@ -88,9 +78,6 @@ $('#search').submit(function(){ return $.trim($(this).find('input[name=query]').val()) != ''; }); - @plugin.PluginSystem.javaScripts.filter(_.filter(context.currentPath)).map { js => - @Html(js.script) - } }); diff --git a/src/main/twirl/menu.scala.html b/src/main/twirl/menu.scala.html index 7ac1dc8..43c34c6 100644 --- a/src/main/twirl/menu.scala.html +++ b/src/main/twirl/menu.scala.html @@ -65,11 +65,6 @@ @sidemenu("/issues", "issues", "Issues", repository.issueCount) @sidemenu("/pulls" , "pulls" , "Pull Requests", repository.pullCount) @sidemenu("/wiki" , "wiki" , "Wiki") - @plugin.PluginSystem.repositoryMenus.map { menu => - @if(menu.condition(context)){ - @sidemenuPlugin(menu.url, menu.label, menu.label, menu.icon) - } - } @if(loginAccount.isDefined && (loginAccount.get.isAdmin || repository.managers.contains(loginAccount.get.userName))){ @sidemenu("/settings", "settings", "Settings") }