mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-08 14:35:52 +01:00
Merge pull request #591 from marklacroix/anon-access
(refs #274) Add option to deny anonymous (i.e. unauthorized) access
This commit is contained in:
@@ -14,6 +14,7 @@ class ScalatraBootstrap extends LifeCycle {
|
|||||||
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
||||||
|
|
||||||
// Register controllers
|
// Register controllers
|
||||||
|
context.mount(new AnonymousAccessController, "/*")
|
||||||
context.mount(new IndexController, "/")
|
context.mount(new IndexController, "/")
|
||||||
context.mount(new SearchController, "/")
|
context.mount(new SearchController, "/")
|
||||||
context.mount(new FileUploadController, "/upload")
|
context.mount(new FileUploadController, "/upload")
|
||||||
|
|||||||
14
src/main/scala/app/AnonymousAccessController.scala
Normal file
14
src/main/scala/app/AnonymousAccessController.scala
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
class AnonymousAccessController extends AnonymousAccessControllerBase
|
||||||
|
|
||||||
|
trait AnonymousAccessControllerBase extends ControllerBase {
|
||||||
|
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||||
|
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||||
|
!context.currentPath.startsWith("/register")) {
|
||||||
|
Unauthorized()
|
||||||
|
} else {
|
||||||
|
pass()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
||||||
"information" -> trim(label("Information", optional(text()))),
|
"information" -> trim(label("Information", optional(text()))),
|
||||||
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
||||||
|
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
|
||||||
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
|
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
|
||||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||||
"notification" -> trim(label("Notification", boolean())),
|
"notification" -> trim(label("Notification", boolean())),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ trait SystemSettingsService {
|
|||||||
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
||||||
settings.information.foreach(x => props.setProperty(Information, x))
|
settings.information.foreach(x => props.setProperty(Information, x))
|
||||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||||
|
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
|
||||||
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
|
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
|
||||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||||
props.setProperty(Notification, settings.notification.toString)
|
props.setProperty(Notification, settings.notification.toString)
|
||||||
@@ -65,6 +66,7 @@ trait SystemSettingsService {
|
|||||||
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
||||||
getOptionValue[String](props, Information, None),
|
getOptionValue[String](props, Information, None),
|
||||||
getValue(props, AllowAccountRegistration, false),
|
getValue(props, AllowAccountRegistration, false),
|
||||||
|
getValue(props, AllowAnonymousAccess, true),
|
||||||
getValue(props, IsCreateRepoOptionPublic, true),
|
getValue(props, IsCreateRepoOptionPublic, true),
|
||||||
getValue(props, Gravatar, true),
|
getValue(props, Gravatar, true),
|
||||||
getValue(props, Notification, false),
|
getValue(props, Notification, false),
|
||||||
@@ -113,6 +115,7 @@ object SystemSettingsService {
|
|||||||
baseUrl: Option[String],
|
baseUrl: Option[String],
|
||||||
information: Option[String],
|
information: Option[String],
|
||||||
allowAccountRegistration: Boolean,
|
allowAccountRegistration: Boolean,
|
||||||
|
allowAnonymousAccess: Boolean,
|
||||||
isCreateRepoOptionPublic: Boolean,
|
isCreateRepoOptionPublic: Boolean,
|
||||||
gravatar: Boolean,
|
gravatar: Boolean,
|
||||||
notification: Boolean,
|
notification: Boolean,
|
||||||
@@ -158,6 +161,7 @@ object SystemSettingsService {
|
|||||||
private val BaseURL = "base_url"
|
private val BaseURL = "base_url"
|
||||||
private val Information = "information"
|
private val Information = "information"
|
||||||
private val AllowAccountRegistration = "allow_account_registration"
|
private val AllowAccountRegistration = "allow_account_registration"
|
||||||
|
private val AllowAnonymousAccess = "allow_anonymous_access"
|
||||||
private val IsCreateRepoOptionPublic = "is_create_repository_option_public"
|
private val IsCreateRepoOptionPublic = "is_create_repository_option_public"
|
||||||
private val Gravatar = "gravatar"
|
private val Gravatar = "gravatar"
|
||||||
private val Notification = "notification"
|
private val Notification = "notification"
|
||||||
|
|||||||
@@ -28,33 +28,45 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
|||||||
override def setCharacterEncoding(encoding: String) = {}
|
override def setCharacterEncoding(encoding: String) = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isUpdating = request.getRequestURI.endsWith("/git-receive-pack") || "service=git-receive-pack".equals(request.getQueryString)
|
||||||
|
|
||||||
|
val settings = loadSystemSettings()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
defining(request.paths){ case Array(_, repositoryOwner, repositoryName, _*) =>
|
defining(request.paths){
|
||||||
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
|
case Array(_, repositoryOwner, repositoryName, _*) =>
|
||||||
case Some(repository) => {
|
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", ""), "") match {
|
||||||
if(!request.getRequestURI.endsWith("/git-receive-pack") &&
|
case Some(repository) => {
|
||||||
!"service=git-receive-pack".equals(request.getQueryString) && !repository.repository.isPrivate){
|
if(!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess){
|
||||||
chain.doFilter(req, wrappedResponse)
|
chain.doFilter(req, wrappedResponse)
|
||||||
} else {
|
} else {
|
||||||
request.getHeader("Authorization") match {
|
request.getHeader("Authorization") match {
|
||||||
case null => requireAuth(response)
|
case null => requireAuth(response)
|
||||||
case auth => decodeAuthHeader(auth).split(":") match {
|
case auth => decodeAuthHeader(auth).split(":") match {
|
||||||
case Array(username, password) => getWritableUser(username, password, repository) match {
|
case Array(username, password) => {
|
||||||
case Some(account) => {
|
authenticate(settings, username, password) match {
|
||||||
request.setAttribute(Keys.Request.UserName, account.userName)
|
case Some(account) => {
|
||||||
chain.doFilter(req, wrappedResponse)
|
if(isUpdating && hasWritePermission(repository.owner, repository.name, Some(account))){
|
||||||
|
request.setAttribute(Keys.Request.UserName, account.userName)
|
||||||
|
}
|
||||||
|
chain.doFilter(req, wrappedResponse)
|
||||||
|
}
|
||||||
|
case None => requireAuth(response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case None => requireAuth(response)
|
case _ => requireAuth(response)
|
||||||
}
|
}
|
||||||
case _ => requireAuth(response)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case None => {
|
||||||
|
logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.")
|
||||||
|
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case None => {
|
case _ => {
|
||||||
logger.debug(s"Repository ${repositoryOwner}/${repositoryName} is not found.")
|
logger.debug(s"Not enough path arguments: ${request.paths}")
|
||||||
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -65,13 +77,6 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def getWritableUser(username: String, password: String, repository: RepositoryService.RepositoryInfo)
|
|
||||||
(implicit session: Session): Option[Account] =
|
|
||||||
authenticate(loadSystemSettings(), username, password) match {
|
|
||||||
case x @ Some(account) if(hasWritePermission(repository.owner, repository.name, x)) => x
|
|
||||||
case _ => None
|
|
||||||
}
|
|
||||||
|
|
||||||
private def requireAuth(response: HttpServletResponse): Unit = {
|
private def requireAuth(response: HttpServletResponse): Unit = {
|
||||||
response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"")
|
response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"")
|
||||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
|
||||||
|
|||||||
@@ -66,6 +66,21 @@
|
|||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
|
<!-- Anonymous access -->
|
||||||
|
<!--====================================================================-->
|
||||||
|
<hr>
|
||||||
|
<label class="strong">Anonymous access</label>
|
||||||
|
<fieldset>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" name="allowAnonymousAccess" value="true"@if(settings.allowAnonymousAccess){ checked}>
|
||||||
|
<span class="strong">Allow</span> - Anyone can view public repositories, user/group profiles.
|
||||||
|
</label>
|
||||||
|
<label class="radio">
|
||||||
|
<input type="radio" name="allowAnonymousAccess" value="false"@if(!settings.allowAnonymousAccess){ checked}>
|
||||||
|
<span class="strong">Deny</span> - Users must authenticate before viewing any information
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<!--====================================================================-->
|
||||||
<!-- Services -->
|
<!-- Services -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ class AvatarImageProviderSpec extends Specification with Mockito {
|
|||||||
baseUrl = None,
|
baseUrl = None,
|
||||||
information = None,
|
information = None,
|
||||||
allowAccountRegistration = false,
|
allowAccountRegistration = false,
|
||||||
|
allowAnonymousAccess = true,
|
||||||
isCreateRepoOptionPublic = true,
|
isCreateRepoOptionPublic = true,
|
||||||
gravatar = useGravatar,
|
gravatar = useGravatar,
|
||||||
notification = false,
|
notification = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user