diff --git a/src/main/scala/servlet/AutoUpdateListener.scala b/src/main/scala/servlet/AutoUpdateListener.scala new file mode 100644 index 0000000..aa48a00 --- /dev/null +++ b/src/main/scala/servlet/AutoUpdateListener.scala @@ -0,0 +1,118 @@ +package servlet + +import java.io.File +import java.sql.Connection +import org.apache.commons.io.FileUtils +import javax.servlet.ServletContextEvent +import org.apache.commons.io.IOUtils +import org.slf4j.LoggerFactory +import util.Directory + +object AutoUpdate { + + /** + * Version of GitBucket + * + * @param majorVersion the major version + * @param minorVersion the minor version + */ + case class Version(majorVersion: Int, minorVersion: Int){ + + private val logger = LoggerFactory.getLogger(classOf[servlet.AutoUpdate.Version]) + + /** + * Execute update/MAJOR_MINOR.sql to update schema to this version. + * If corresponding SQL file does not exist, this method do nothing. + */ + def update(conn: Connection): Unit = { + val sqlPath = "update/%d_%d.sql".format(majorVersion, minorVersion) + val in = Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath) + if(in != null){ + val sql = IOUtils.toString(in, "UTF-8") + val stmt = conn.createStatement() + try { + logger.debug(sqlPath + "=" + sql) + stmt.executeUpdate(sql) + } finally { + stmt.close() + } + } + } + + /** + * MAJOR.MINOR + */ + val versionString = "%d.%d".format(majorVersion, minorVersion) + } + + /** + * The history of versions. A head of this sequence is the current BitBucket version. + */ + val versions = Seq( + Version(1, 0) + ) + + /** + * The head version of BitBucket. + */ + val headVersion = versions.head + + /** + * The version file (GITBUCKET_HOME/version). + */ + val versionFile = new File(Directory.GitBucketHome, "version") + + /** + * Returns the current version from the version file. + */ + def getCurrentVersion(): Version = { + if(versionFile.exists){ + FileUtils.readFileToString(versionFile, "UTF-8").split("\\.") match { + case Array(majorVersion, minorVersion) => { + versions.find { v => + v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt + }.getOrElse(Version(0, 0)) + } + case _ => Version(0, 0) + } + } else { + Version(0, 0) + } + + } + +} + +/** + * Start H2 database and update schema automatically. + */ +class AutoUpdateListener extends org.h2.server.web.DbStarter { + import AutoUpdate._ + private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener]) + + override def contextInitialized(event: ServletContextEvent): Unit = { + super.contextInitialized(event) + logger.debug("H2 started") + + logger.debug("Start schema update") + val conn = getConnection() + try { + val currentVersion = getCurrentVersion() + if(currentVersion == headVersion){ + logger.debug("No update") + } else { + versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn)) + FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8") + conn.commit() + logger.debug("Updated from " + currentVersion.versionString + " to " + headVersion.versionString) + } + } catch { + case ex: Throwable => { + logger.error("Failed to schema update", ex) + conn.rollback() + } + } + logger.debug("End schema update") + } + +} \ No newline at end of file diff --git a/src/main/scala/servlet/BasicAuthenticationFilter.scala b/src/main/scala/servlet/BasicAuthenticationFilter.scala new file mode 100644 index 0000000..d097c9f --- /dev/null +++ b/src/main/scala/servlet/BasicAuthenticationFilter.scala @@ -0,0 +1,52 @@ +package servlet + +import javax.servlet._ +import javax.servlet.http._ + +/** + * Provides BASIC Authentication for [[app.GitRepositoryServlet]]. + */ +class BasicAuthenticationFilter extends Filter { + + def init(config: FilterConfig) = {} + + def destroy(): Unit = {} + + def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { + val request = req.asInstanceOf[HttpServletRequest] + val response = res.asInstanceOf[HttpServletResponse] + val session = request.getSession + + try { + session.getAttribute("USER_INFO") match { + case null => request.getHeader("Authorization") match { + case null => requireAuth(response) + case auth => decodeAuthHeader(auth).split(":") match { + // TODO authenticate using registered user info + case Array(username, password) if(username == "gitbucket" && password == "password") => { + session.setAttribute("USER_INFO", "gitbucket") + chain.doFilter(req, res) + } + case _ => requireAuth(response) + } + } + case user => chain.doFilter(req, res) + } + } catch { + case _: Exception => requireAuth(response) + } + } + + private def requireAuth(response: HttpServletResponse): Unit = { + response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"") + response.sendError(HttpServletResponse.SC_UNAUTHORIZED) + } + + private def decodeAuthHeader(header: String): String = { + try { + new String(new sun.misc.BASE64Decoder().decodeBuffer(header.substring(6))) + } catch { + case _: Throwable => "" + } + } +} \ No newline at end of file diff --git a/src/main/scala/servlet/GitRepositoryServlet.scala b/src/main/scala/servlet/GitRepositoryServlet.scala new file mode 100644 index 0000000..77dd293 --- /dev/null +++ b/src/main/scala/servlet/GitRepositoryServlet.scala @@ -0,0 +1,37 @@ +package servlet + +import java.io._ +import javax.servlet._ +import javax.servlet.http._ +import org.eclipse.jgit.http.server.GitServlet +import org.slf4j.LoggerFactory +import util.Directory + +/** + * Provides Git repository via HTTP. + * + * This servlet provides only Git repository functionality. + * Authentication is provided by [[app.BasicAuthenticationFilter]]. + */ +class GitRepositoryServlet extends GitServlet { + + private val logger = LoggerFactory.getLogger(classOf[GitRepositoryServlet]) + + // TODO are there any other ways...? + override def init(config: ServletConfig): Unit = { + super.init(new ServletConfig(){ + def getInitParameter(name: String): String = name match { + case "base-path" => Directory.RepositoryHome + case "export-all" => "true" + case name => config.getInitParameter(name) + } + def getInitParameterNames(): java.util.Enumeration[String] = { + config.getInitParameterNames + } + + def getServletContext(): ServletContext = config.getServletContext + def getServletName(): String = config.getServletName + }); + } + +} diff --git a/src/main/scala/servlet/TransactionFilter.scala b/src/main/scala/servlet/TransactionFilter.scala new file mode 100644 index 0000000..cfc21ee --- /dev/null +++ b/src/main/scala/servlet/TransactionFilter.scala @@ -0,0 +1,30 @@ +package servlet + +import javax.servlet._ +import org.slf4j.LoggerFactory +import javax.servlet.http.HttpServletRequest + +/** + * Controls the transaction with the open session in view pattern. + */ +class TransactionFilter extends Filter { + + private val logger = LoggerFactory.getLogger(classOf[TransactionFilter]) + + def init(config: FilterConfig) = {} + + def destroy(): Unit = {} + + def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { + if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){ + // assets don't need transaction + chain.doFilter(req, res) + } else { + // TODO begin transaction! + logger.debug("TODO begin transaction") + chain.doFilter(req, res) + logger.debug("TODO end transaction") + } + } + +} \ No newline at end of file diff --git a/src/main/scala/util/AutoUpdate.scala b/src/main/scala/util/AutoUpdate.scala deleted file mode 100644 index 6fa1843..0000000 --- a/src/main/scala/util/AutoUpdate.scala +++ /dev/null @@ -1,118 +0,0 @@ -package util - -import java.io.File -import java.sql.Connection -import org.apache.commons.io.FileUtils -import javax.servlet.ServletContextEvent -import org.apache.commons.io.IOUtils -import org.slf4j.Logger -import org.slf4j.LoggerFactory - -object AutoUpdate { - - /** - * Version of GitBucket - * - * @param majorVersion the major version - * @param minorVersion the minor version - */ - case class Version(majorVersion: Int, minorVersion: Int){ - - private val logger = LoggerFactory.getLogger(classOf[Version]) - - /** - * Execute update/MAJOR_MINOR.sql to update schema to this version. - * If corresponding SQL file does not exist, this method do nothing. - */ - def update(conn: Connection): Unit = { - val sqlPath = "update/%d_%d.sql".format(majorVersion, minorVersion) - val in = Thread.currentThread.getContextClassLoader.getResourceAsStream(sqlPath) - if(in != null){ - val sql = IOUtils.toString(in, "UTF-8") - val stmt = conn.createStatement() - try { - logger.debug(sqlPath + "=" + sql) - stmt.executeUpdate(sql) - } finally { - stmt.close() - } - } - } - - /** - * MAJOR.MINOR - */ - val versionString = "%d.%d".format(majorVersion, minorVersion) - } - - /** - * The history of versions. A head of this sequence is the current BitBucket version. - */ - val versions = Seq( - Version(1, 0) - ) - - /** - * The head version of BitBucket. - */ - val headVersion = versions.head - - /** - * The version file (GITBUCKET_HOME/version). - */ - val versionFile = new File(Directory.GitBucketHome, "version") - - /** - * Returns the current version from the version file. - */ - def getCurrentVersion(): Version = { - if(versionFile.exists){ - FileUtils.readFileToString(versionFile, "UTF-8").split("\\.") match { - case Array(majorVersion, minorVersion) => { - versions.find { v => - v.majorVersion == majorVersion.toInt && v.minorVersion == minorVersion.toInt - }.getOrElse(Version(0, 0)) - } - case _ => Version(0, 0) - } - } else { - Version(0, 0) - } - - } - -} - -/** - * Start H2 database and update schema automatically. - */ -class AutoUpdateListener extends org.h2.server.web.DbStarter { - import AutoUpdate._ - private val logger = LoggerFactory.getLogger(classOf[AutoUpdateListener]) - - override def contextInitialized(event: ServletContextEvent): Unit = { - super.contextInitialized(event) - logger.debug("H2 started") - - logger.debug("Start schema update") - val conn = getConnection() - try { - val currentVersion = getCurrentVersion() - if(currentVersion == headVersion){ - logger.debug("No update") - } else { - versions.takeWhile(_ != currentVersion).reverse.foreach(_.update(conn)) - FileUtils.writeStringToFile(versionFile, headVersion.versionString, "UTF-8") - conn.commit() - logger.debug("Updated from " + currentVersion.versionString + " to " + headVersion.versionString) - } - } catch { - case ex: Throwable => { - logger.error("Failed to schema update", ex) - conn.rollback() - } - } - logger.debug("End schema update") - } - -} \ No newline at end of file diff --git a/src/main/scala/util/BasicAuthenticationFilter.scala b/src/main/scala/util/BasicAuthenticationFilter.scala deleted file mode 100644 index c81c221..0000000 --- a/src/main/scala/util/BasicAuthenticationFilter.scala +++ /dev/null @@ -1,52 +0,0 @@ -package util - -import javax.servlet._ -import javax.servlet.http._ - -/** - * Provides BASIC Authentication for [[app.GitRepositoryServlet]]. - */ -class BasicAuthenticationFilter extends Filter { - - def init(config: FilterConfig) = {} - - def destroy(): Unit = {} - - def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { - val request = req.asInstanceOf[HttpServletRequest] - val response = res.asInstanceOf[HttpServletResponse] - val session = request.getSession - - try { - session.getAttribute("USER_INFO") match { - case null => request.getHeader("Authorization") match { - case null => requireAuth(response) - case auth => decodeAuthHeader(auth).split(":") match { - // TODO authenticate using registered user info - case Array(username, password) if(username == "gitbucket" && password == "password") => { - session.setAttribute("USER_INFO", "gitbucket") - chain.doFilter(req, res) - } - case _ => requireAuth(response) - } - } - case user => chain.doFilter(req, res) - } - } catch { - case _: Exception => requireAuth(response) - } - } - - private def requireAuth(response: HttpServletResponse): Unit = { - response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"") - response.sendError(HttpServletResponse.SC_UNAUTHORIZED) - } - - private def decodeAuthHeader(header: String): String = { - try { - new String(new sun.misc.BASE64Decoder().decodeBuffer(header.substring(6))) - } catch { - case _: Throwable => "" - } - } -} \ No newline at end of file diff --git a/src/main/scala/util/GitRepositoryServlet.scala b/src/main/scala/util/GitRepositoryServlet.scala deleted file mode 100644 index 7f1461a..0000000 --- a/src/main/scala/util/GitRepositoryServlet.scala +++ /dev/null @@ -1,36 +0,0 @@ -package util - -import java.io._ -import javax.servlet._ -import javax.servlet.http._ -import org.eclipse.jgit.http.server.GitServlet -import org.slf4j.LoggerFactory - -/** - * Provides Git repository via HTTP. - * - * This servlet provides only Git repository functionality. - * Authentication is provided by [[app.BasicAuthenticationFilter]]. - */ -class GitRepositoryServlet extends GitServlet { - - private val logger = LoggerFactory.getLogger(classOf[GitRepositoryServlet]) - - // TODO are there any other ways...? - override def init(config: ServletConfig): Unit = { - super.init(new ServletConfig(){ - def getInitParameter(name: String): String = name match { - case "base-path" => Directory.RepositoryHome - case "export-all" => "true" - case name => config.getInitParameter(name) - } - def getInitParameterNames(): java.util.Enumeration[String] = { - config.getInitParameterNames - } - - def getServletContext(): ServletContext = config.getServletContext - def getServletName(): String = config.getServletName - }); - } - -} diff --git a/src/main/scala/util/TransactionFilter.scala b/src/main/scala/util/TransactionFilter.scala deleted file mode 100644 index 499a62f..0000000 --- a/src/main/scala/util/TransactionFilter.scala +++ /dev/null @@ -1,30 +0,0 @@ -package util - -import javax.servlet._ -import org.slf4j.LoggerFactory -import javax.servlet.http.HttpServletRequest - -/** - * Controls the transaction with the open session in view pattern. - */ -class TransactionFilter extends Filter { - - private val logger = LoggerFactory.getLogger(classOf[TransactionFilter]) - - def init(config: FilterConfig) = {} - - def destroy(): Unit = {} - - def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { - if(req.asInstanceOf[HttpServletRequest].getRequestURI().startsWith("/assets/")){ - // assets don't need transaction - chain.doFilter(req, res) - } else { - // TODO begin transaction! - logger.debug("TODO begin transaction") - chain.doFilter(req, res) - logger.debug("TODO end transaction") - } - } - -} \ No newline at end of file diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index cb33996..a051a67 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -13,7 +13,7 @@ GitRepositoryServlet - util.GitRepositoryServlet + servlet.GitRepositoryServlet @@ -23,7 +23,7 @@ TransactionFilter - util.TransactionFilter + servlet.TransactionFilter @@ -33,7 +33,7 @@ BasicAuthenticationFilter - util.BasicAuthenticationFilter + servlet.BasicAuthenticationFilter @@ -45,7 +45,7 @@ - util.AutoUpdateListener + servlet.AutoUpdateListener