diff --git a/src/main/scala/ScalatraBootstrap.scala b/src/main/scala/ScalatraBootstrap.scala index 30c507a..fbb488a 100644 --- a/src/main/scala/ScalatraBootstrap.scala +++ b/src/main/scala/ScalatraBootstrap.scala @@ -1,7 +1,7 @@ import gitbucket.core.controller._ import gitbucket.core.plugin.PluginRegistry -import gitbucket.core.servlet.{AccessTokenAuthenticationFilter, BasicAuthenticationFilter, Database, TransactionFilter} +import gitbucket.core.servlet.{ApiAuthenticationFilter, GitAuthenticationFilter, Database, TransactionFilter} import gitbucket.core.util.Directory import java.util.EnumSet @@ -15,10 +15,10 @@ // Register TransactionFilter and BasicAuthenticationFilter at first context.addFilter("transactionFilter", new TransactionFilter) context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") - context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter) - context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*") - context.addFilter("accessTokenAuthenticationFilter", new AccessTokenAuthenticationFilter) - context.getFilterRegistration("accessTokenAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*") + context.addFilter("gitAuthenticationFilter", new GitAuthenticationFilter) + context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*") + context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter) + context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*") // Register controllers context.mount(new AnonymousAccessController, "/*") diff --git a/src/main/scala/gitbucket/core/servlet/AccessTokenAuthenticationFilter.scala b/src/main/scala/gitbucket/core/servlet/AccessTokenAuthenticationFilter.scala deleted file mode 100644 index ab294ed..0000000 --- a/src/main/scala/gitbucket/core/servlet/AccessTokenAuthenticationFilter.scala +++ /dev/null @@ -1,49 +0,0 @@ -package gitbucket.core.servlet - -import javax.servlet._ -import javax.servlet.http.{HttpServletRequest, HttpServletResponse} - -import gitbucket.core.model.Account -import gitbucket.core.service.SystemSettingsService.SystemSettings -import gitbucket.core.service.{AccessTokenService, AccountService, SystemSettingsService} -import gitbucket.core.util.{AuthUtil, Keys} -import org.scalatra.servlet.ServletApiImplicits._ -import org.scalatra._ - - -class AccessTokenAuthenticationFilter extends Filter with AccessTokenService with AccountService with SystemSettingsService { - private val tokenHeaderPrefix = "token " - - override def init(filterConfig: FilterConfig): Unit = {} - - override def destroy(): Unit = {} - - override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { - implicit val request = req.asInstanceOf[HttpServletRequest] - implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session] - val response = res.asInstanceOf[HttpServletResponse] - Option(request.getHeader("Authorization")).map{ - case auth if auth.startsWith("token ") => AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(Unit) - case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(Unit) - case _ => Left(Unit) - }.orElse{ - Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_)) - } match { - case Some(Right(account)) => request.setAttribute(Keys.Session.LoginAccount, account); chain.doFilter(req, res) - case None => chain.doFilter(req, res) - case Some(Left(_)) => { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) - response.setContentType("application/json; charset=utf-8") - val w = response.getWriter() - w.print("""{ "message": "Bad credentials" }""") - w.close() - } - } - } - - def doBasicAuth(auth: String, settings: SystemSettings, request: HttpServletRequest): Option[Account] = { - implicit val session = request.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session] - val Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2) - authenticate(settings, username, password) - } -} diff --git a/src/main/scala/gitbucket/core/servlet/ApiAuthenticationFilter.scala b/src/main/scala/gitbucket/core/servlet/ApiAuthenticationFilter.scala new file mode 100644 index 0000000..871c292 --- /dev/null +++ b/src/main/scala/gitbucket/core/servlet/ApiAuthenticationFilter.scala @@ -0,0 +1,48 @@ +package gitbucket.core.servlet + +import javax.servlet._ +import javax.servlet.http.{HttpServletRequest, HttpServletResponse} + +import gitbucket.core.model.Account +import gitbucket.core.service.SystemSettingsService.SystemSettings +import gitbucket.core.service.{AccessTokenService, AccountService, SystemSettingsService} +import gitbucket.core.util.{AuthUtil, Keys} +import org.scalatra.servlet.ServletApiImplicits._ +import org.scalatra._ + + +class ApiAuthenticationFilter extends Filter with AccessTokenService with AccountService with SystemSettingsService { + + override def init(filterConfig: FilterConfig): Unit = {} + + override def destroy(): Unit = {} + + override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = { + implicit val request = req.asInstanceOf[HttpServletRequest] + implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session] + val response = res.asInstanceOf[HttpServletResponse] + Option(request.getHeader("Authorization")).map{ + case auth if auth.startsWith("token ") => AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(()) + case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(()) + case _ => Left(()) + }.orElse{ + Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_)) + } match { + case Some(Right(account)) => request.setAttribute(Keys.Session.LoginAccount, account); chain.doFilter(req, res) + case None => chain.doFilter(req, res) + case Some(Left(_)) => { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED) + response.setContentType("application/json; charset=utf-8") + val w = response.getWriter() + w.print("""{ "message": "Bad credentials" }""") + w.close() + } + } + } + + def doBasicAuth(auth: String, settings: SystemSettings, request: HttpServletRequest): Option[Account] = { + implicit val session = request.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session] + val Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2) + authenticate(settings, username, password) + } +} diff --git a/src/main/scala/gitbucket/core/servlet/BasicAuthenticationFilter.scala b/src/main/scala/gitbucket/core/servlet/BasicAuthenticationFilter.scala deleted file mode 100644 index cca1a64..0000000 --- a/src/main/scala/gitbucket/core/servlet/BasicAuthenticationFilter.scala +++ /dev/null @@ -1,111 +0,0 @@ -package gitbucket.core.servlet - -import javax.servlet._ -import javax.servlet.http._ -import gitbucket.core.plugin.{GitRepositoryFilter, GitRepositoryRouting, PluginRegistry} -import gitbucket.core.service.SystemSettingsService.SystemSettings -import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService} -import gitbucket.core.util.{Keys, Implicits, AuthUtil} -import org.slf4j.LoggerFactory -import Implicits._ - -/** - * Provides BASIC Authentication for [[GitRepositoryServlet]]. - */ -class BasicAuthenticationFilter extends Filter with RepositoryService with AccountService with SystemSettingsService { - - private val logger = LoggerFactory.getLogger(classOf[BasicAuthenticationFilter]) - - 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 wrappedResponse = new HttpServletResponseWrapper(response){ - override def setCharacterEncoding(encoding: String) = {} - } - - val isUpdating = request.getRequestURI.endsWith("/git-receive-pack") || "service=git-receive-pack".equals(request.getQueryString) - val settings = loadSystemSettings() - - try { - PluginRegistry().getRepositoryRouting(request.gitRepositoryPath).map { case GitRepositoryRouting(_, _, filter) => - // served by plug-ins - pluginRepository(request, wrappedResponse, chain, settings, isUpdating, filter) - - }.getOrElse { - // default repositories - defaultRepository(request, wrappedResponse, chain, settings, isUpdating) - } - } catch { - case ex: Exception => { - logger.error("error", ex) - AuthUtil.requireAuth(response) - } - } - } - - private def pluginRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, - settings: SystemSettings, isUpdating: Boolean, filter: GitRepositoryFilter): Unit = { - implicit val r = request - - val account = for { - auth <- Option(request.getHeader("Authorization")) - Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2) - account <- authenticate(settings, username, password) - } yield { - request.setAttribute(Keys.Request.UserName, account.userName) - account - } - - if(filter.filter(request.gitRepositoryPath, account.map(_.userName), settings, isUpdating)){ - chain.doFilter(request, response) - } else { - AuthUtil.requireAuth(response) - } - } - - private def defaultRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, - settings: SystemSettings, isUpdating: Boolean): Unit = { - implicit val r = request - - request.paths match { - case Array(_, repositoryOwner, repositoryName, _*) => - getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match { - case Some(repository) => { - if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){ - chain.doFilter(request, response) - } else { - val passed = for { - auth <- Option(request.getHeader("Authorization")) - Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2) - account <- authenticate(settings, username, password) - } yield if(isUpdating || repository.repository.isPrivate){ - if(hasWritePermission(repository.owner, repository.name, Some(account))){ - request.setAttribute(Keys.Request.UserName, account.userName) - true - } else false - } else true - - if(passed.getOrElse(false)){ - chain.doFilter(request, response) - } else { - AuthUtil.requireAuth(response) - } - } - } - case None => { - logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.") - response.sendError(HttpServletResponse.SC_NOT_FOUND) - } - } - case _ => { - logger.debug(s"Not enough path arguments: ${request.paths}") - response.sendError(HttpServletResponse.SC_NOT_FOUND) - } - } - } -} \ No newline at end of file diff --git a/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala b/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala new file mode 100644 index 0000000..c079eb8 --- /dev/null +++ b/src/main/scala/gitbucket/core/servlet/GitAuthenticationFilter.scala @@ -0,0 +1,111 @@ +package gitbucket.core.servlet + +import javax.servlet._ +import javax.servlet.http._ +import gitbucket.core.plugin.{GitRepositoryFilter, GitRepositoryRouting, PluginRegistry} +import gitbucket.core.service.SystemSettingsService.SystemSettings +import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService} +import gitbucket.core.util.{Keys, Implicits, AuthUtil} +import org.slf4j.LoggerFactory +import Implicits._ + +/** + * Provides BASIC Authentication for [[GitRepositoryServlet]]. + */ +class GitAuthenticationFilter extends Filter with RepositoryService with AccountService with SystemSettingsService { + + private val logger = LoggerFactory.getLogger(classOf[GitAuthenticationFilter]) + + 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 wrappedResponse = new HttpServletResponseWrapper(response){ + override def setCharacterEncoding(encoding: String) = {} + } + + val isUpdating = request.getRequestURI.endsWith("/git-receive-pack") || "service=git-receive-pack".equals(request.getQueryString) + val settings = loadSystemSettings() + + try { + PluginRegistry().getRepositoryRouting(request.gitRepositoryPath).map { case GitRepositoryRouting(_, _, filter) => + // served by plug-ins + pluginRepository(request, wrappedResponse, chain, settings, isUpdating, filter) + + }.getOrElse { + // default repositories + defaultRepository(request, wrappedResponse, chain, settings, isUpdating) + } + } catch { + case ex: Exception => { + logger.error("error", ex) + AuthUtil.requireAuth(response) + } + } + } + + private def pluginRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, + settings: SystemSettings, isUpdating: Boolean, filter: GitRepositoryFilter): Unit = { + implicit val r = request + + val account = for { + auth <- Option(request.getHeader("Authorization")) + Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2) + account <- authenticate(settings, username, password) + } yield { + request.setAttribute(Keys.Request.UserName, account.userName) + account + } + + if(filter.filter(request.gitRepositoryPath, account.map(_.userName), settings, isUpdating)){ + chain.doFilter(request, response) + } else { + AuthUtil.requireAuth(response) + } + } + + private def defaultRepository(request: HttpServletRequest, response: HttpServletResponse, chain: FilterChain, + settings: SystemSettings, isUpdating: Boolean): Unit = { + implicit val r = request + + request.paths match { + case Array(_, repositoryOwner, repositoryName, _*) => + getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match { + case Some(repository) => { + if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){ + chain.doFilter(request, response) + } else { + val passed = for { + auth <- Option(request.getHeader("Authorization")) + Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2) + account <- authenticate(settings, username, password) + } yield if(isUpdating || repository.repository.isPrivate){ + if(hasWritePermission(repository.owner, repository.name, Some(account))){ + request.setAttribute(Keys.Request.UserName, account.userName) + true + } else false + } else true + + if(passed.getOrElse(false)){ + chain.doFilter(request, response) + } else { + AuthUtil.requireAuth(response) + } + } + } + case None => { + logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.") + response.sendError(HttpServletResponse.SC_NOT_FOUND) + } + } + case _ => { + logger.debug(s"Not enough path arguments: ${request.paths}") + response.sendError(HttpServletResponse.SC_NOT_FOUND) + } + } + } +} \ No newline at end of file diff --git a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala index 047e5ed..1ceb76a 100644 --- a/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala +++ b/src/main/scala/gitbucket/core/servlet/GitRepositoryServlet.scala @@ -27,7 +27,7 @@ * Provides Git repository via HTTP. * * This servlet provides only Git repository functionality. - * Authentication is provided by [[BasicAuthenticationFilter]]. + * Authentication is provided by [[GitAuthenticationFilter]]. */ class GitRepositoryServlet extends GitServlet with SystemSettingsService {