diff --git a/src/main/resources/update/gitbucket-core_4.7.sql b/src/main/resources/update/gitbucket-core_4.7.sql
new file mode 100644
index 0000000..ef13c70
--- /dev/null
+++ b/src/main/resources/update/gitbucket-core_4.7.sql
@@ -0,0 +1,2 @@
+-- DELETE COLLABORATORS IN GROUP REPOSITORIES
+DELETE FROM COLLABORATOR WHERE USER_NAME IN (SELECT USER_NAME FROM ACCOUNT WHERE GROUP_ACCOUNT = TRUE)
diff --git a/src/main/resources/update/gitbucket-core_4.7.xml b/src/main/resources/update/gitbucket-core_4.7.xml
new file mode 100644
index 0000000..a128800
--- /dev/null
+++ b/src/main/resources/update/gitbucket-core_4.7.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+ ENABLE_WIKI = FALSE
+
+
+
+ ENABLE_WIKI = TRUE AND ALLOW_WIKI_EDITING = FALSE
+
+
+
+ ENABLE_WIKI = TRUE AND ALLOW_WIKI_EDITING = TRUE
+
+
+
+ ENABLE_ISSUES = FALSE
+
+
+
+ ENABLE_ISSUES = TRUE
+
+
+
+
+
diff --git a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
index 6830d2d..c04a687 100644
--- a/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
+++ b/src/main/scala/gitbucket/core/GitBucketCoreModule.scala
@@ -18,5 +18,9 @@
new Version("4.5.0"),
new Version("4.6.0",
new LiquibaseMigration("update/gitbucket-core_4.6.xml")
+ ),
+ new Version("4.7.0",
+ new LiquibaseMigration("update/gitbucket-core_4.7.xml"),
+ new SqlMigration("update/gitbucket-core_4.7.sql")
)
)
diff --git a/src/main/scala/gitbucket/core/api/ApiUser.scala b/src/main/scala/gitbucket/core/api/ApiUser.scala
index 7259c12..9b3dc9d 100644
--- a/src/main/scala/gitbucket/core/api/ApiUser.scala
+++ b/src/main/scala/gitbucket/core/api/ApiUser.scala
@@ -30,7 +30,7 @@
def apply(user: Account): ApiUser = ApiUser(
login = user.userName,
email = user.mailAddress,
- `type` = if(user.isGroupAccount){ "Organization" }else{ "User" },
+ `type` = if(user.isGroupAccount){ "Organization" } else { "User" },
site_admin = user.isAdmin,
created_at = user.registeredDate
)
diff --git a/src/main/scala/gitbucket/core/controller/AccountController.scala b/src/main/scala/gitbucket/core/controller/AccountController.scala
index fa46b78..bfb9ccd 100644
--- a/src/main/scala/gitbucket/core/controller/AccountController.scala
+++ b/src/main/scala/gitbucket/core/controller/AccountController.scala
@@ -319,13 +319,13 @@
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
- // Update COLLABORATOR for group repositories
- getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
- removeCollaborators(form.groupName, repositoryName)
- members.foreach { case (userName, isManager) =>
- addCollaborator(form.groupName, repositoryName, userName)
- }
- }
+// // Update COLLABORATOR for group repositories
+// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
+// removeCollaborators(form.groupName, repositoryName)
+// members.foreach { case (userName, isManager) =>
+// addCollaborator(form.groupName, repositoryName, userName)
+// }
+// }
updateImage(form.groupName, form.fileId, form.clearImage)
redirect(s"/${form.groupName}")
@@ -402,13 +402,13 @@
parentUserName = Some(repository.owner)
)
- // Add collaborators for group repository
- val ownerAccount = getAccountByUserName(accountName).get
- if(ownerAccount.isGroupAccount){
- getGroupMembers(accountName).foreach { member =>
- addCollaborator(accountName, repository.name, member.userName)
- }
- }
+// // Add collaborators for group repository
+// val ownerAccount = getAccountByUserName(accountName).get
+// if(ownerAccount.isGroupAccount){
+// getGroupMembers(accountName).foreach { member =>
+// addCollaborator(accountName, repository.name, member.userName)
+// }
+// }
// Insert default labels
insertDefaultLabels(accountName, repository.name)
diff --git a/src/main/scala/gitbucket/core/controller/ApiController.scala b/src/main/scala/gitbucket/core/controller/ApiController.scala
index 692a1f9..eddd941 100644
--- a/src/main/scala/gitbucket/core/controller/ApiController.scala
+++ b/src/main/scala/gitbucket/core/controller/ApiController.scala
@@ -35,7 +35,7 @@
with GroupManagerAuthenticator
with ReferrerAuthenticator
with ReadableUsersAuthenticator
- with CollaboratorsAuthenticator
+ with WritableUsersAuthenticator
trait ApiControllerBase extends ControllerBase {
self: RepositoryService
@@ -52,7 +52,7 @@
with GroupManagerAuthenticator
with ReferrerAuthenticator
with ReadableUsersAuthenticator
- with CollaboratorsAuthenticator =>
+ with WritableUsersAuthenticator =>
/**
* https://developer.github.com/v3/#root-endpoint
@@ -177,7 +177,8 @@
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
*/
get("/api/v3/repos/:owner/:repo/collaborators") (referrersOnly { repository =>
- JsonFormat(getCollaborators(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get)))
+ // TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
+ JsonFormat(getCollaboratorUserNames(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get)))
})
/**
@@ -327,7 +328,7 @@
* Create a label
* https://developer.github.com/v3/issues/labels/#create-a-label
*/
- post("/api/v3/repos/:owner/:repository/labels")(collaboratorsOnly { repository =>
+ post("/api/v3/repos/:owner/:repository/labels")(writableUsersOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
@@ -352,7 +353,7 @@
* Update a label
* https://developer.github.com/v3/issues/labels/#update-a-label
*/
- patch("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
+ patch("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
(for{
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
@@ -378,7 +379,7 @@
* Delete a label
* https://developer.github.com/v3/issues/labels/#delete-a-label
*/
- delete("/api/v3/repos/:owner/:repository/labels/:labelName")(collaboratorsOnly { repository =>
+ delete("/api/v3/repos/:owner/:repository/labels/:labelName")(writableUsersOnly { repository =>
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
deleteLabel(repository.owner, repository.name, label.labelId)
@@ -466,7 +467,7 @@
/**
* https://developer.github.com/v3/repos/statuses/#create-a-status
*/
- post("/api/v3/repos/:owner/:repo/statuses/:sha")(collaboratorsOnly { repository =>
+ post("/api/v3/repos/:owner/:repo/statuses/:sha")(writableUsersOnly { repository =>
(for{
ref <- params.get("sha")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
diff --git a/src/main/scala/gitbucket/core/controller/IndexController.scala b/src/main/scala/gitbucket/core/controller/IndexController.scala
index 6bc2751..0d69ebe 100644
--- a/src/main/scala/gitbucket/core/controller/IndexController.scala
+++ b/src/main/scala/gitbucket/core/controller/IndexController.scala
@@ -108,18 +108,29 @@
*/
get("/_user/proposals")(usersOnly {
contentType = formats("json")
+ val user = params("user").toBoolean
+ val group = params("group").toBoolean
org.json4s.jackson.Serialization.write(
- Map("options" -> getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray)
+ Map("options" -> (
+ getAllUsers(false)
+ .withFilter { t => (user, group) match {
+ case (true, true) => true
+ case (true, false) => !t.isGroupAccount
+ case (false, true) => t.isGroupAccount
+ case (false, false) => false
+ }}.map { t => t.userName }
+ ))
)
})
/**
- * JSON API for checking user existence.
+ * JSON API for checking user or group existence.
+ * Returns a single string which is any of "group", "user" or "".
*/
post("/_user/existence")(usersOnly {
getAccountByUserName(params("userName")).map { account =>
- if(params.get("userOnly").isDefined) !account.isGroupAccount else true
- } getOrElse false
+ if(account.isGroupAccount) "group" else "user"
+ } getOrElse ""
})
// TODO Move to RepositoryViwerController?
diff --git a/src/main/scala/gitbucket/core/controller/IssuesController.scala b/src/main/scala/gitbucket/core/controller/IssuesController.scala
index 6ddcd40..90aca13 100644
--- a/src/main/scala/gitbucket/core/controller/IssuesController.scala
+++ b/src/main/scala/gitbucket/core/controller/IssuesController.scala
@@ -2,24 +2,24 @@
import gitbucket.core.issues.html
import gitbucket.core.service.IssuesService._
+import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util._
import gitbucket.core.view
import gitbucket.core.view.Markdown
-
import io.github.gitbucket.scalatra.forms._
import org.scalatra.Ok
class IssuesController extends IssuesControllerBase
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
- with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with WebHookIssueCommentService
trait IssuesControllerBase extends ControllerBase {
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService with HandleCommentService
- with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService =>
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with WebHookIssueCommentService =>
case class IssueCreateForm(title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
@@ -67,72 +67,77 @@
_,
getComments(owner, name, issueId.toInt),
getIssueLabels(owner, name, issueId.toInt),
- (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).exists(_.isGroupAccount)) Nil else List(owner))).sorted,
+ getAssignableUserNames(owner, name),
getMilestonesWithIssueCount(owner, name),
getLabels(owner, name),
- hasWritePermission(owner, name, context.loginAccount),
+ isEditable(repository),
+ isManageable(repository),
repository)
} getOrElse NotFound()
}
})
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
- defining(repository.owner, repository.name){ case (owner, name) =>
- html.create(
- (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).exists(_.isGroupAccount)) Nil else List(owner))).sorted,
+ if(isEditable(repository)){ // TODO Should this check is provided by authenticator?
+ defining(repository.owner, repository.name){ case (owner, name) =>
+ html.create(
+ getAssignableUserNames(owner, name),
getMilestones(owner, name),
getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount),
repository)
- }
+ }
+ } else Unauthorized()
})
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
- defining(repository.owner, repository.name){ case (owner, name) =>
- val writable = hasWritePermission(owner, name, context.loginAccount)
- val userName = context.loginAccount.get.userName
+ if(isEditable(repository)){ // TODO Should this check is provided by authenticator?
+ defining(repository.owner, repository.name){ case (owner, name) =>
+ val manageable = isManageable(repository)
+ val userName = context.loginAccount.get.userName
- // insert issue
- val issueId = createIssue(owner, name, userName, form.title, form.content,
- if(writable) form.assignedUserName else None,
- if(writable) form.milestoneId else None)
+ // insert issue
+ val issueId = createIssue(owner, name, userName, form.title, form.content,
+ if (manageable) form.assignedUserName else None,
+ if (manageable) form.milestoneId else None)
- // insert labels
- if(writable){
- form.labelNames.map { value =>
- val labels = getLabels(owner, name)
- value.split(",").foreach { labelName =>
- labels.find(_.labelName == labelName).map { label =>
- registerIssueLabel(owner, name, issueId, label.labelId)
+ // insert labels
+ if (manageable) {
+ form.labelNames.map { value =>
+ val labels = getLabels(owner, name)
+ value.split(",").foreach { labelName =>
+ labels.find(_.labelName == labelName).map { label =>
+ registerIssueLabel(owner, name, issueId, label.labelId)
+ }
}
}
}
- }
- // record activity
- recordCreateIssueActivity(owner, name, userName, issueId, form.title)
+ // record activity
+ recordCreateIssueActivity(owner, name, userName, issueId, form.title)
- getIssue(owner, name, issueId.toString).foreach { issue =>
- // extract references and create refer comment
- createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
+ getIssue(owner, name, issueId.toString).foreach { issue =>
+ // extract references and create refer comment
+ createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
- // call web hooks
- callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
+ // call web hooks
+ callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
- // notifications
- Notifier().toNotify(repository, issue, form.content.getOrElse("")){
- Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
+ // notifications
+ Notifier().toNotify(repository, issue, form.content.getOrElse("")) {
+ Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
+ }
}
- }
- redirect(s"/${owner}/${name}/issues/${issueId}")
- }
+ redirect(s"/${owner}/${name}/issues/${issueId}")
+ }
+ } else Unauthorized()
})
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
getIssue(owner, name, params("id")).map { issue =>
- if(isEditable(owner, name, issue.openedUserName)){
+ if(isEditableContent(owner, name, issue.openedUserName)){
// update issue
updateIssue(owner, name, issue.issueId, title, issue.content)
// extract references and create refer comment
@@ -147,7 +152,7 @@
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
getIssue(owner, name, params("id")).map { issue =>
- if(isEditable(owner, name, issue.openedUserName)){
+ if(isEditableContent(owner, name, issue.openedUserName)){
// update issue
updateIssue(owner, name, issue.issueId, issue.title, content)
// extract references and create refer comment
@@ -161,7 +166,7 @@
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
- val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
+ val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
@@ -171,7 +176,7 @@
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
- val actionOpt = params.get("action").filter(_ => isEditable(issue.userName, issue.repositoryName, issue.openedUserName))
+ val actionOpt = params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
@@ -182,7 +187,7 @@
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
getComment(owner, name, params("id")).map { comment =>
- if(isEditable(owner, name, comment.commentedUserName)){
+ if(isEditableContent(owner, name, comment.commentedUserName)){
updateComment(comment.commentId, form.content)
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
} else Unauthorized()
@@ -193,7 +198,7 @@
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) =>
getComment(owner, name, params("id")).map { comment =>
- if(isEditable(owner, name, comment.commentedUserName)){
+ if(isEditableContent(owner, name, comment.commentedUserName)){
Ok(deleteComment(comment.commentId))
} else Unauthorized()
} getOrElse NotFound()
@@ -202,7 +207,7 @@
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
getIssue(repository.owner, repository.name, params("id")) map { x =>
- if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
+ if(isEditableContent(x.userName, x.repositoryName, x.openedUserName)){
params.get("dataType") collect {
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
} getOrElse {
@@ -218,7 +223,7 @@
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
- hasWritePermission = isEditable(x.userName, x.repositoryName, x.openedUserName)
+ hasWritePermission = true
)
)
)
@@ -229,7 +234,7 @@
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
getComment(repository.owner, repository.name, params("id")) map { x =>
- if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
+ if(isEditableContent(x.userName, x.repositoryName, x.commentedUserName)){
params.get("dataType") collect {
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
} getOrElse {
@@ -244,7 +249,7 @@
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
- hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName)
+ hasWritePermission = true
)
)
)
@@ -253,32 +258,32 @@
} getOrElse NotFound()
})
- ajaxPost("/:owner/:repository/issues/new/label")(collaboratorsOnly { repository =>
+ ajaxPost("/:owner/:repository/issues/new/label")(writableUsersOnly { repository =>
val labelNames = params("labelNames").split(",")
val labels = getLabels(repository.owner, repository.name).filter(x => labelNames.contains(x.labelName))
html.labellist(labels)
})
- ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
+ ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
defining(params("id").toInt){ issueId =>
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
})
- ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
+ ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
defining(params("id").toInt){ issueId =>
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
})
- ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
+ ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
Ok("updated")
})
- ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
+ ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository =>
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
milestoneId("milestoneId").map { milestoneId =>
getMilestonesWithIssueCount(repository.owner, repository.name)
@@ -288,7 +293,7 @@
} getOrElse Ok()
})
- post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
+ post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
defining(params.get("value")){ action =>
action match {
case Some("open") => executeBatch(repository) { issueId =>
@@ -306,7 +311,7 @@
}
})
- post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
+ post("/:owner/:repository/issues/batchedit/label")(writableUsersOnly { repository =>
params("value").toIntOpt.map{ labelId =>
executeBatch(repository) { issueId =>
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
@@ -316,7 +321,7 @@
} getOrElse NotFound()
})
- post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
+ post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
defining(assignedUserName("value")){ value =>
executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value)
@@ -324,7 +329,7 @@
}
})
- post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
+ post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
defining(milestoneId("value")){ value =>
executeBatch(repository) {
updateMilestoneId(repository.owner, repository.name, _, value)
@@ -346,9 +351,6 @@
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
- private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
- hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
-
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
params("checked").split(',') map(_.toInt) foreach execute
params("from") match {
@@ -359,8 +361,7 @@
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
defining(repository.owner, repository.name){ case (owner, repoName) =>
- val page = IssueSearchCondition.page(request)
- val sessionKey = Keys.Session.Issues(owner, repoName)
+ val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
@@ -369,18 +370,41 @@
"issues",
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page,
- if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
- (getCollaborators(owner, repoName) :+ owner).sorted
- } else {
- getCollaborators(owner, repoName)
- },
+ getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
condition,
repository,
- hasWritePermission(owner, repoName, context.loginAccount))
+ isEditable(repository),
+ isManageable(repository))
}
}
+
+ /**
+ * Tests whether an logged-in user can manage issues.
+ */
+ private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
+ hasWritePermission(repository.owner, repository.name, context.loginAccount)
+ }
+
+ /**
+ * Tests whether an logged-in user can post issues.
+ */
+ private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
+ repository.repository.options.issuesOption match {
+ case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount)
+ case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount)
+ case "DISABLE" => false
+ }
+ }
+
+ /**
+ * Tests whether an issue or a comment is editable by a logged-in user.
+ */
+ private def isEditableContent(owner: String, repository: String, author: String)(implicit context: Context): Boolean = {
+ hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
+ }
+
}
diff --git a/src/main/scala/gitbucket/core/controller/LabelsController.scala b/src/main/scala/gitbucket/core/controller/LabelsController.scala
index 9b17841..ab658eb 100644
--- a/src/main/scala/gitbucket/core/controller/LabelsController.scala
+++ b/src/main/scala/gitbucket/core/controller/LabelsController.scala
@@ -2,7 +2,7 @@
import gitbucket.core.issues.labels.html
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
-import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
+import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.Implicits._
import io.github.gitbucket.scalatra.forms._
import org.scalatra.i18n.Messages
@@ -10,11 +10,11 @@
class LabelsController extends LabelsControllerBase
with LabelsService with IssuesService with RepositoryService with AccountService
-with ReferrerAuthenticator with CollaboratorsAuthenticator
+with ReferrerAuthenticator with WritableUsersAuthenticator
trait LabelsControllerBase extends ControllerBase {
self: LabelsService with IssuesService with RepositoryService
- with ReferrerAuthenticator with CollaboratorsAuthenticator =>
+ with ReferrerAuthenticator with WritableUsersAuthenticator =>
case class LabelForm(labelName: String, color: String)
@@ -32,11 +32,11 @@
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
- ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
+ ajaxGet("/:owner/:repository/issues/labels/new")(writableUsersOnly { repository =>
html.edit(None, repository)
})
- ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(collaboratorsOnly { (form, repository) =>
+ ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(writableUsersOnly { (form, repository) =>
val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
html.label(
getLabel(repository.owner, repository.name, labelId).get,
@@ -46,13 +46,13 @@
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
- ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
+ ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(writableUsersOnly { repository =>
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
html.edit(Some(label), repository)
} getOrElse NotFound()
})
- ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(collaboratorsOnly { (form, repository) =>
+ ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(writableUsersOnly { (form, repository) =>
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
html.label(
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
@@ -62,7 +62,7 @@
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
- ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
+ ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(writableUsersOnly { repository =>
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
Ok()
})
diff --git a/src/main/scala/gitbucket/core/controller/MilestonesController.scala b/src/main/scala/gitbucket/core/controller/MilestonesController.scala
index 323b114..f75ea60 100644
--- a/src/main/scala/gitbucket/core/controller/MilestonesController.scala
+++ b/src/main/scala/gitbucket/core/controller/MilestonesController.scala
@@ -2,17 +2,17 @@
import gitbucket.core.issues.milestones.html
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
-import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
+import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
import gitbucket.core.util.Implicits._
import io.github.gitbucket.scalatra.forms._
class MilestonesController extends MilestonesControllerBase
with MilestonesService with RepositoryService with AccountService
- with ReferrerAuthenticator with CollaboratorsAuthenticator
+ with ReferrerAuthenticator with WritableUsersAuthenticator
trait MilestonesControllerBase extends ControllerBase {
self: MilestonesService with RepositoryService
- with ReferrerAuthenticator with CollaboratorsAuthenticator =>
+ with ReferrerAuthenticator with WritableUsersAuthenticator =>
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
@@ -30,22 +30,22 @@
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
- get("/:owner/:repository/issues/milestones/new")(collaboratorsOnly {
+ get("/:owner/:repository/issues/milestones/new")(writableUsersOnly {
html.edit(None, _)
})
- post("/:owner/:repository/issues/milestones/new", milestoneForm)(collaboratorsOnly { (form, repository) =>
+ post("/:owner/:repository/issues/milestones/new", milestoneForm)(writableUsersOnly { (form, repository) =>
createMilestone(repository.owner, repository.name, form.title, form.description, form.dueDate)
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
})
- get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository =>
+ get("/:owner/:repository/issues/milestones/:milestoneId/edit")(writableUsersOnly { repository =>
params("milestoneId").toIntOpt.map{ milestoneId =>
html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
} getOrElse NotFound()
})
- post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(collaboratorsOnly { (form, repository) =>
+ post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(writableUsersOnly { (form, repository) =>
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
@@ -54,7 +54,7 @@
} getOrElse NotFound()
})
- get("/:owner/:repository/issues/milestones/:milestoneId/close")(collaboratorsOnly { repository =>
+ get("/:owner/:repository/issues/milestones/:milestoneId/close")(writableUsersOnly { repository =>
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
closeMilestone(milestone)
@@ -63,7 +63,7 @@
} getOrElse NotFound()
})
- get("/:owner/:repository/issues/milestones/:milestoneId/open")(collaboratorsOnly { repository =>
+ get("/:owner/:repository/issues/milestones/:milestoneId/open")(writableUsersOnly { repository =>
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
openMilestone(milestone)
@@ -72,7 +72,7 @@
} getOrElse NotFound()
})
- get("/:owner/:repository/issues/milestones/:milestoneId/delete")(collaboratorsOnly { repository =>
+ get("/:owner/:repository/issues/milestones/:milestoneId/delete")(writableUsersOnly { repository =>
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
diff --git a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
index f3bd241..548da42 100644
--- a/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
+++ b/src/main/scala/gitbucket/core/controller/PullRequestsController.scala
@@ -6,6 +6,7 @@
import gitbucket.core.service.MergeService
import gitbucket.core.service.IssuesService._
import gitbucket.core.service.PullRequestService._
+import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
@@ -14,28 +15,26 @@
import gitbucket.core.util._
import gitbucket.core.view
import gitbucket.core.view.helpers
-
import io.github.gitbucket.scalatra.forms._
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.PersonIdent
-import org.slf4j.LoggerFactory
import scala.collection.JavaConverters._
class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
- with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
+ with CommitsService with ActivityService with WebHookPullRequestService
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator
with CommitStatusService with MergeService with ProtectedBranchService
trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
- with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
+ with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator
with CommitStatusService with MergeService with ProtectedBranchService =>
- private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
-
val pullRequestForm = mapping(
"title" -> trim(label("Title" , text(required, maxlength(100)))),
"content" -> trim(label("Content", optional(text()))),
@@ -94,12 +93,13 @@
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
.sortWith((a, b) => a.registeredDate before b.registeredDate),
getIssueLabels(owner, name, issueId),
- (getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
+ getAssignableUserNames(owner, name),
getMilestonesWithIssueCount(owner, name),
getLabels(owner, name),
commits,
diffs,
- hasWritePermission(owner, name, context.loginAccount),
+ isEditable(repository),
+ isManageable(repository),
repository,
flash.toMap.map(f => f._1 -> f._2.toString))
}
@@ -141,7 +141,7 @@
} getOrElse NotFound()
})
- get("/:owner/:repository/pull/:id/delete/*")(collaboratorsOnly { repository =>
+ get("/:owner/:repository/pull/:id/delete/*")(writableUsersOnly { repository =>
params("id").toIntOpt.map { issueId =>
val branchName = multiParams("splat").head
val userName = context.loginAccount.get.userName
@@ -225,7 +225,7 @@
}) getOrElse NotFound()
})
- post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
+ post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
params("id").toIntOpt.flatMap { issueId =>
val owner = repository.owner
val name = repository.name
@@ -375,7 +375,7 @@
originRepository,
forkedRepository,
hasWritePermission(originRepository.owner, originRepository.name, context.loginAccount),
- (getCollaborators(originRepository.owner, originRepository.name) ::: (if(getAccountByUserName(originRepository.owner).get.isGroupAccount) Nil else List(originRepository.owner))).sorted,
+ getAssignableUserNames(originRepository.owner, originRepository.name),
getMilestones(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name)
)
@@ -389,7 +389,7 @@
}) getOrElse NotFound()
})
- ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(collaboratorsOnly { forkedRepository =>
+ ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(writableUsersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
@@ -419,64 +419,68 @@
}) getOrElse NotFound()
})
- post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
+ post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
- val writable = hasWritePermission(owner, name, context.loginAccount)
- val loginUserName = context.loginAccount.get.userName
+ val manageable = isManageable(repository)
+ val editable = isEditable(repository)
- val issueId = createIssue(
- owner = repository.owner,
- repository = repository.name,
- loginUser = loginUserName,
- title = form.title,
- content = form.content,
- assignedUserName = if(writable) form.assignedUserName else None,
- milestoneId = if(writable) form.milestoneId else None,
- isPullRequest = true)
+ if(editable) {
+ val loginUserName = context.loginAccount.get.userName
- createPullRequest(
- originUserName = repository.owner,
- originRepositoryName = repository.name,
- issueId = issueId,
- originBranch = form.targetBranch,
- requestUserName = form.requestUserName,
- requestRepositoryName = form.requestRepositoryName,
- requestBranch = form.requestBranch,
- commitIdFrom = form.commitIdFrom,
- commitIdTo = form.commitIdTo)
+ val issueId = createIssue(
+ owner = repository.owner,
+ repository = repository.name,
+ loginUser = loginUserName,
+ title = form.title,
+ content = form.content,
+ assignedUserName = if (manageable) form.assignedUserName else None,
+ milestoneId = if (manageable) form.milestoneId else None,
+ isPullRequest = true)
- // insert labels
- if(writable){
- form.labelNames.map { value =>
- val labels = getLabels(owner, name)
- value.split(",").foreach { labelName =>
- labels.find(_.labelName == labelName).map { label =>
- registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
+ createPullRequest(
+ originUserName = repository.owner,
+ originRepositoryName = repository.name,
+ issueId = issueId,
+ originBranch = form.targetBranch,
+ requestUserName = form.requestUserName,
+ requestRepositoryName = form.requestRepositoryName,
+ requestBranch = form.requestBranch,
+ commitIdFrom = form.commitIdFrom,
+ commitIdTo = form.commitIdTo)
+
+ // insert labels
+ if (manageable) {
+ form.labelNames.map { value =>
+ val labels = getLabels(owner, name)
+ value.split(",").foreach { labelName =>
+ labels.find(_.labelName == labelName).map { label =>
+ registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
+ }
}
}
}
- }
- // fetch requested branch
- fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
+ // fetch requested branch
+ fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
- // record activity
- recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
+ // record activity
+ recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
- // call web hook
- callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
+ // call web hook
+ callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
- getIssue(owner, name, issueId.toString) foreach { issue =>
- // extract references and create refer comment
- createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
+ getIssue(owner, name, issueId.toString) foreach { issue =>
+ // extract references and create refer comment
+ createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""), context.loginAccount.get)
- // notifications
- Notifier().toNotify(repository, issue, form.content.getOrElse("")){
- Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
+ // notifications
+ Notifier().toNotify(repository, issue, form.content.getOrElse("")) {
+ Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
+ }
}
- }
- redirect(s"/${owner}/${name}/pull/${issueId}")
+ redirect(s"/${owner}/${name}/pull/${issueId}")
+ } else Unauthorized()
}
})
@@ -516,8 +520,7 @@
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
defining(repository.owner, repository.name){ case (owner, repoName) =>
- val page = IssueSearchCondition.page(request)
- val sessionKey = Keys.Session.Pulls(owner, repoName)
+ val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
@@ -526,18 +529,33 @@
"pulls",
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
page,
- if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
- (getCollaborators(owner, repoName) :+ owner).sorted
- } else {
- getCollaborators(owner, repoName)
- },
+ getAssignableUserNames(owner, repoName),
getMilestones(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), true, owner -> repoName),
countIssue(condition.copy(state = "closed"), true, owner -> repoName),
condition,
repository,
- hasWritePermission(owner, repoName, context.loginAccount))
+ isEditable(repository),
+ isManageable(repository))
}
+ /**
+ * Tests whether an logged-in user can manage pull requests.
+ */
+ private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
+ hasWritePermission(repository.owner, repository.name, context.loginAccount)
+ }
+
+ /**
+ * Tests whether an logged-in user can post pull requests.
+ */
+ private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
+ repository.repository.options.issuesOption match {
+ case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount)
+ case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount)
+ case "DISABLE" => false
+ }
+ }
+
}
diff --git a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
index 41455cc..5a2ca91 100644
--- a/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
+++ b/src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
@@ -31,10 +31,9 @@
repositoryName: String,
description: Option[String],
isPrivate: Boolean,
- enableIssues: Boolean,
+ issuesOption: String,
externalIssuesUrl: Option[String],
- enableWiki: Boolean,
- allowWikiEditing: Boolean,
+ wikiOption: String,
externalWikiUrl: Option[String],
allowFork: Boolean
)
@@ -43,10 +42,9 @@
"repositoryName" -> trim(label("Repository Name" , text(required, maxlength(40), identifier, renameRepositoryName))),
"description" -> trim(label("Description" , optional(text()))),
"isPrivate" -> trim(label("Repository Type" , boolean())),
- "enableIssues" -> trim(label("Enable Issues" , boolean())),
+ "issuesOption" -> trim(label("Issues Option" , text(required, featureOption))),
"externalIssuesUrl" -> trim(label("External Issues URL", optional(text(maxlength(200))))),
- "enableWiki" -> trim(label("Enable Wiki" , boolean())),
- "allowWikiEditing" -> trim(label("Allow Wiki Editing" , boolean())),
+ "wikiOption" -> trim(label("Wiki Option" , text(required, featureOption))),
"externalWikiUrl" -> trim(label("External Wiki URL" , optional(text(maxlength(200))))),
"allowFork" -> trim(label("Allow Forking" , boolean()))
)(OptionsForm.apply)
@@ -58,12 +56,12 @@
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100))))
)(DefaultBranchForm.apply)
- // for collaborator addition
- case class CollaboratorForm(userName: String)
-
- val collaboratorForm = mapping(
- "userName" -> trim(label("Username", text(required, collaborator)))
- )(CollaboratorForm.apply)
+// // for collaborator addition
+// case class CollaboratorForm(userName: String)
+//
+// val collaboratorForm = mapping(
+// "userName" -> trim(label("Username", text(required, collaborator)))
+// )(CollaboratorForm.apply)
// for web hook url addition
case class WebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])
@@ -109,10 +107,9 @@
repository.repository.parentUserName.map { _ =>
repository.repository.isPrivate
} getOrElse form.isPrivate,
- form.enableIssues,
+ form.issuesOption,
form.externalIssuesUrl,
- form.enableWiki,
- form.allowWikiEditing,
+ form.wikiOption,
form.externalWikiUrl,
form.allowFork
)
@@ -178,22 +175,12 @@
repository)
})
- /**
- * Add the collaborator.
- */
- post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
- if(!getAccountByUserName(repository.owner).get.isGroupAccount){
- addCollaborator(repository.owner, repository.name, form.userName)
- }
- redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
- })
-
- /**
- * Add the collaborator.
- */
- get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
- if(!getAccountByUserName(repository.owner).get.isGroupAccount){
- removeCollaborator(repository.owner, repository.name, params("name"))
+ post("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
+ val collaborators = params("collaborators")
+ removeCollaborators(repository.owner, repository.name)
+ collaborators.split(",").withFilter(_.nonEmpty).map { collaborator =>
+ val userName :: permission :: Nil = collaborator.split(":").toList
+ addCollaborator(repository.owner, repository.name, userName, permission)
}
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
})
@@ -397,20 +384,20 @@
}
}
- /**
- * Provides Constraint to validate the collaborator name.
- */
- private def collaborator: Constraint = new Constraint(){
- override def validate(name: String, value: String, messages: Messages): Option[String] =
- getAccountByUserName(value) match {
- case None => Some("User does not exist.")
- case Some(x) if(x.isGroupAccount)
- => Some("User does not exist.")
- case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
- => Some("User can access this repository already.")
- case _ => None
- }
- }
+// /**
+// * Provides Constraint to validate the collaborator name.
+// */
+// private def collaborator: Constraint = new Constraint(){
+// override def validate(name: String, value: String, messages: Messages): Option[String] =
+// getAccountByUserName(value) match {
+// case None => Some("User does not exist.")
+//// case Some(x) if(x.isGroupAccount)
+//// => Some("User does not exist.")
+// case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
+// => Some(value + " is repository owner.") // TODO also group members?
+// case _ => None
+// }
+// }
/**
* Duplicate check for the rename repository name.
@@ -425,6 +412,15 @@
}
/**
+ *
+ */
+ private def featureOption: Constraint = new Constraint(){
+ override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
+ if(Seq("DISABLE", "PRIVATE", "PUBLIC").contains(value)) None else Some("Option is invalid.")
+ }
+
+
+ /**
* Provides Constraint to validate the repository transfer user.
*/
private def transferUser: Constraint = new Constraint(){
diff --git a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
index 63b904e..d575c68 100644
--- a/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
+++ b/src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
@@ -31,7 +31,7 @@
class RepositoryViewerController extends RepositoryViewerControllerBase
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
- with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with CommitStatusService
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService
/**
@@ -39,7 +39,7 @@
*/
trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
- with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
+ with ReadableUsersAuthenticator with ReferrerAuthenticator with WritableUsersAuthenticator with PullRequestService with CommitStatusService
with WebHookPullRequestService with WebHookPullRequestReviewCommentService with ProtectedBranchService =>
ArchiveCommand.registerFormat("zip", new ZipFormat)
@@ -157,7 +157,7 @@
}
})
- get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
+ get("/:owner/:repository/new/*")(writableUsersOnly { repository =>
val (branch, path) = repository.splitPath(multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
@@ -165,7 +165,7 @@
protectedBranch)
})
- get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
+ get("/:owner/:repository/edit/*")(writableUsersOnly { repository =>
val (branch, path) = repository.splitPath(multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
@@ -181,7 +181,7 @@
}
})
- get("/:owner/:repository/remove/*")(collaboratorsOnly { repository =>
+ get("/:owner/:repository/remove/*")(writableUsersOnly { repository =>
val (branch, path) = repository.splitPath(multiParams("splat").head)
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
@@ -194,7 +194,7 @@
}
})
- post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
+ post("/:owner/:repository/create", editorForm)(writableUsersOnly { (form, repository) =>
commitFile(
repository = repository,
branch = form.branch,
@@ -211,7 +211,7 @@
}")
})
- post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
+ post("/:owner/:repository/update", editorForm)(writableUsersOnly { (form, repository) =>
commitFile(
repository = repository,
branch = form.branch,
@@ -232,7 +232,7 @@
}")
})
- post("/:owner/:repository/remove", deleteForm)(collaboratorsOnly { (form, repository) =>
+ post("/:owner/:repository/remove", deleteForm)(writableUsersOnly { (form, repository) =>
commitFile(repository, form.branch, form.path, None, Some(form.fileName), "", "",
form.message.getOrElse(s"Delete ${form.fileName}"))
@@ -443,7 +443,7 @@
/**
* Creates a branch.
*/
- post("/:owner/:repository/branches")(collaboratorsOnly { repository =>
+ post("/:owner/:repository/branches")(writableUsersOnly { repository =>
val newBranchName = params.getOrElse("new", halt(400))
val fromBranchName = params.getOrElse("from", halt(400))
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
@@ -461,7 +461,7 @@
/**
* Deletes branch.
*/
- get("/:owner/:repository/delete/*")(collaboratorsOnly { repository =>
+ get("/:owner/:repository/delete/*")(writableUsersOnly { repository =>
val branchName = multiParams("splat").head
val userName = context.loginAccount.get.userName
if(repository.repository.defaultBranch != branchName){
diff --git a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
index 7a68746..bfca858 100644
--- a/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
+++ b/src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
@@ -279,13 +279,13 @@
} else {
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
- // Update COLLABORATOR for group repositories
- getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
- removeCollaborators(form.groupName, repositoryName)
- members.foreach { case (userName, isManager) =>
- addCollaborator(form.groupName, repositoryName, userName)
- }
- }
+// // Update COLLABORATOR for group repositories
+// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
+// removeCollaborators(form.groupName, repositoryName)
+// members.foreach { case (userName, isManager) =>
+// addCollaborator(form.groupName, repositoryName, userName)
+// }
+// }
}
updateImage(form.groupName, form.fileId, form.clearImage)
diff --git a/src/main/scala/gitbucket/core/controller/WikiController.scala b/src/main/scala/gitbucket/core/controller/WikiController.scala
index 29f0569..64d0b27 100644
--- a/src/main/scala/gitbucket/core/controller/WikiController.scala
+++ b/src/main/scala/gitbucket/core/controller/WikiController.scala
@@ -14,10 +14,10 @@
class WikiController extends WikiControllerBase
with WikiService with RepositoryService with AccountService with ActivityService
- with CollaboratorsAuthenticator with ReferrerAuthenticator
+ with ReadableUsersAuthenticator with ReferrerAuthenticator
trait WikiControllerBase extends ControllerBase {
- self: WikiService with RepositoryService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator =>
+ self: WikiService with RepositoryService with ActivityService with ReadableUsersAuthenticator with ReferrerAuthenticator =>
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String, id: String)
@@ -62,7 +62,7 @@
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
- case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository)
+ case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository))
case Left(_) => NotFound()
}
}
@@ -87,7 +87,7 @@
}
})
- get("/:owner/:repository/wiki/:page/_revert/:commitId")(referrersOnly { repository =>
+ get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
if(isEditable(repository)){
val pageName = StringUtil.urlDecode(params("page"))
val Array(from, to) = params("commitId").split("\\.\\.\\.")
@@ -101,7 +101,7 @@
} else Unauthorized()
})
- get("/:owner/:repository/wiki/_revert/:commitId")(referrersOnly { repository =>
+ get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
if(isEditable(repository)){
val Array(from, to) = params("commitId").split("\\.\\.\\.")
@@ -114,14 +114,14 @@
} else Unauthorized()
})
- get("/:owner/:repository/wiki/:page/_edit")(referrersOnly { repository =>
+ get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
if(isEditable(repository)){
val pageName = StringUtil.urlDecode(params("page"))
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
} else Unauthorized()
})
- post("/:owner/:repository/wiki/_edit", editForm)(referrersOnly { (form, repository) =>
+ post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
if(isEditable(repository)){
defining(context.loginAccount.get){ loginAccount =>
saveWikiPage(
@@ -146,13 +146,13 @@
} else Unauthorized()
})
- get("/:owner/:repository/wiki/_new")(referrersOnly { repository =>
+ get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository =>
if(isEditable(repository)){
html.edit("", None, repository)
} else Unauthorized()
})
- post("/:owner/:repository/wiki/_new", newForm)(referrersOnly { (form, repository) =>
+ post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
if(isEditable(repository)){
defining(context.loginAccount.get){ loginAccount =>
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
@@ -170,7 +170,7 @@
} else Unauthorized()
})
- get("/:owner/:repository/wiki/:page/_delete")(referrersOnly { repository =>
+ get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
if(isEditable(repository)){
val pageName = StringUtil.urlDecode(params("page"))
@@ -182,7 +182,7 @@
}
} else Unauthorized()
})
-
+
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
html.pages(getWikiPageList(repository.owner, repository.name), repository, isEditable(repository))
})
@@ -190,7 +190,7 @@
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
JGitUtil.getCommitLog(git, "master") match {
- case Right((logs, hasNext)) => html.history(None, logs, repository)
+ case Right((logs, hasNext)) => html.history(None, logs, repository, isEditable(repository))
case Left(_) => NotFound()
}
}
@@ -240,9 +240,13 @@
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
- private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean =
- repository.repository.options.allowWikiEditing || (
- hasWritePermission(repository.owner, repository.name, context.loginAccount)
- )
+ private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
+ repository.repository.options.wikiOption match {
+// case "ALL" => repository.repository.isPrivate == false || hasReadPermission(repository.owner, repository.name, context.loginAccount)
+ case "PUBLIC" => hasReadPermission(repository.owner, repository.name, context.loginAccount)
+ case "PRIVATE" => hasWritePermission(repository.owner, repository.name, context.loginAccount)
+ case "DISABLE" => false
+ }
+ }
}
diff --git a/src/main/scala/gitbucket/core/model/Collaborator.scala b/src/main/scala/gitbucket/core/model/Collaborator.scala
index 55ae80f..5036e3a 100644
--- a/src/main/scala/gitbucket/core/model/Collaborator.scala
+++ b/src/main/scala/gitbucket/core/model/Collaborator.scala
@@ -7,7 +7,8 @@
class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate {
val collaboratorName = column[String]("COLLABORATOR_NAME")
- def * = (userName, repositoryName, collaboratorName) <> (Collaborator.tupled, Collaborator.unapply)
+ val permission = column[String]("PERMISSION")
+ def * = (userName, repositoryName, collaboratorName, permission) <> (Collaborator.tupled, Collaborator.unapply)
def byPrimaryKey(owner: String, repository: String, collaborator: String) =
byRepository(owner, repository) && (collaboratorName === collaborator.bind)
@@ -17,5 +18,23 @@
case class Collaborator(
userName: String,
repositoryName: String,
- collaboratorName: String
+ collaboratorName: String,
+ permission: String
)
+
+sealed abstract class Permission(val name: String)
+
+object Permission {
+ object ADMIN extends Permission("ADMIN")
+ object WRITE extends Permission("WRITE")
+ object READ extends Permission("READ")
+
+// val values: Vector[Permission] = Vector(ADMIN, WRITE, READ)
+//
+// private val map: Map[String, Permission] = values.map(enum => enum.name -> enum).toMap
+//
+// def apply(name: String): Permission = map(name)
+//
+// def valueOf(name: String): Option[Permission] = map.get(name)
+
+}
\ No newline at end of file
diff --git a/src/main/scala/gitbucket/core/model/Repository.scala b/src/main/scala/gitbucket/core/model/Repository.scala
index c5c61bb..387b8ff 100644
--- a/src/main/scala/gitbucket/core/model/Repository.scala
+++ b/src/main/scala/gitbucket/core/model/Repository.scala
@@ -17,17 +17,16 @@
val originRepositoryName = column[String]("ORIGIN_REPOSITORY_NAME")
val parentUserName = column[String]("PARENT_USER_NAME")
val parentRepositoryName = column[String]("PARENT_REPOSITORY_NAME")
- val enableIssues = column[Boolean]("ENABLE_ISSUES")
+ val issuesOption = column[String]("ISSUES_OPTION")
val externalIssuesUrl = column[String]("EXTERNAL_ISSUES_URL")
- val enableWiki = column[Boolean]("ENABLE_WIKI")
- val allowWikiEditing = column[Boolean]("ALLOW_WIKI_EDITING")
+ val wikiOption = column[String]("WIKI_OPTION")
val externalWikiUrl = column[String]("EXTERNAL_WIKI_URL")
val allowFork = column[Boolean]("ALLOW_FORK")
def * = (
(userName, repositoryName, isPrivate, description.?, defaultBranch,
registeredDate, updatedDate, lastActivityDate, originUserName.?, originRepositoryName.?, parentUserName.?, parentRepositoryName.?),
- (enableIssues, externalIssuesUrl.?, enableWiki, allowWikiEditing, externalWikiUrl.?, allowFork)
+ (issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork)
).shaped <> (
{ case (repository, options) =>
Repository(
@@ -85,10 +84,9 @@
)
case class RepositoryOptions(
- enableIssues: Boolean,
+ issuesOption: String,
externalIssuesUrl: Option[String],
- enableWiki: Boolean,
- allowWikiEditing: Boolean,
+ wikiOption: String,
externalWikiUrl: Option[String],
allowFork: Boolean
)
diff --git a/src/main/scala/gitbucket/core/service/HandleCommentService.scala b/src/main/scala/gitbucket/core/service/HandleCommentService.scala
index 6a1b068..22cfc88 100644
--- a/src/main/scala/gitbucket/core/service/HandleCommentService.scala
+++ b/src/main/scala/gitbucket/core/service/HandleCommentService.scala
@@ -13,7 +13,7 @@
with WebHookService with WebHookIssueCommentService with WebHookPullRequestService =>
/**
- * @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
+ * @see [[https://github.com/gitbucket/gitbucket/wiki/CommentAction]]
*/
def handleComment(issue: Issue, content: Option[String], repository: RepositoryService.RepositoryInfo, actionOpt: Option[String])
(implicit context: Context, s: Session) = {
@@ -54,18 +54,20 @@
// call web hooks
action match {
- case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
- case Some(act) => val webHookAction = act match {
- case "open" => "opened"
- case "reopen" => "reopened"
- case "close" => "closed"
- case _ => act
- }
- if(issue.isPullRequest){
+ case None => commentId.map { commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
+ case Some(act) => {
+ val webHookAction = act match {
+ case "open" => "opened"
+ case "reopen" => "reopened"
+ case "close" => "closed"
+ case _ => act
+ }
+ if (issue.isPullRequest) {
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
} else {
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
}
+ }
}
// notifications
diff --git a/src/main/scala/gitbucket/core/service/IssuesService.scala b/src/main/scala/gitbucket/core/service/IssuesService.scala
index b9566a0..ecbfa55 100644
--- a/src/main/scala/gitbucket/core/service/IssuesService.scala
+++ b/src/main/scala/gitbucket/core/service/IssuesService.scala
@@ -14,7 +14,7 @@
trait IssuesService {
- self: AccountService =>
+ self: AccountService with RepositoryService =>
import IssuesService._
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
@@ -433,6 +433,11 @@
}
}
+ def getAssignableUserNames(owner: String, repository: String)(implicit s: Session): List[String] = {
+ (getCollaboratorUserNames(owner, repository, Seq(Permission.ADMIN, Permission.WRITE)) :::
+ (if (getAccountByUserName(owner).get.isGroupAccount) getGroupMembers(owner).map(_.userName) else List(owner))).sorted
+ }
+
}
object IssuesService {
diff --git a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala
index 90e0afd..04aef50 100644
--- a/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala
+++ b/src/main/scala/gitbucket/core/service/RepositoryCreationService.scala
@@ -21,12 +21,12 @@
// Insert to the database at first
insertRepository(name, owner, description, isPrivate)
- // Add collaborators for group repository
- if(ownerAccount.isGroupAccount){
- getGroupMembers(owner).foreach { member =>
- addCollaborator(owner, name, member.userName)
- }
- }
+// // Add collaborators for group repository
+// if(ownerAccount.isGroupAccount){
+// getGroupMembers(owner).foreach { member =>
+// addCollaborator(owner, name, member.userName)
+// }
+// }
// Insert default labels
insertDefaultLabels(owner, name)
diff --git a/src/main/scala/gitbucket/core/service/RepositoryService.scala b/src/main/scala/gitbucket/core/service/RepositoryService.scala
index 3541c07..26fa159 100644
--- a/src/main/scala/gitbucket/core/service/RepositoryService.scala
+++ b/src/main/scala/gitbucket/core/service/RepositoryService.scala
@@ -1,7 +1,7 @@
package gitbucket.core.service
import gitbucket.core.controller.Context
-import gitbucket.core.model.{Collaborator, Repository, RepositoryOptions, Account}
+import gitbucket.core.model.{Collaborator, Repository, RepositoryOptions, Account, Permission}
import gitbucket.core.model.Profile._
import gitbucket.core.util.JGitUtil
import profile.simple._
@@ -38,10 +38,9 @@
parentUserName = parentUserName,
parentRepositoryName = parentRepositoryName,
options = RepositoryOptions(
- enableIssues = true,
+ issuesOption = "PUBLIC", // TODO DISABLE for the forked repository?
externalIssuesUrl = None,
- enableWiki = true,
- allowWikiEditing = true,
+ wikiOption = "PUBLIC", // TODO DISABLE for the forked repository?
externalWikiUrl = None,
allowFork = true
)
@@ -124,11 +123,8 @@
repositoryName = newRepositoryName
)) :_*)
- if(account.isGroupAccount){
- Collaborators.insertAll(getGroupMembers(newUserName).map(m => Collaborator(newUserName, newRepositoryName, m.userName)) :_*)
- } else {
- Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
- }
+ // TODO Drop transfered owner from collaborators?
+ Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
// Update activity messages
Activities.filter { t =>
@@ -323,12 +319,12 @@
*/
def saveRepositoryOptions(userName: String, repositoryName: String,
description: Option[String], isPrivate: Boolean,
- enableIssues: Boolean, externalIssuesUrl: Option[String],
- enableWiki: Boolean, allowWikiEditing: Boolean, externalWikiUrl: Option[String],
+ issuesOption: String, externalIssuesUrl: Option[String],
+ wikiOption: String, externalWikiUrl: Option[String],
allowFork: Boolean)(implicit s: Session): Unit =
Repositories.filter(_.byRepository(userName, repositoryName))
- .map { r => (r.description.?, r.isPrivate, r.enableIssues, r.externalIssuesUrl.?, r.enableWiki, r.allowWikiEditing, r.externalWikiUrl.?, r.allowFork, r.updatedDate) }
- .update (description, isPrivate, enableIssues, externalIssuesUrl, enableWiki, allowWikiEditing, externalWikiUrl, allowFork, currentDate)
+ .map { r => (r.description.?, r.isPrivate, r.issuesOption, r.externalIssuesUrl.?, r.wikiOption, r.externalWikiUrl.?, r.allowFork, r.updatedDate) }
+ .update (description, isPrivate, issuesOption, externalIssuesUrl, wikiOption, externalWikiUrl, allowFork, currentDate)
def saveRepositoryDefaultBranch(userName: String, repositoryName: String,
defaultBranch: String)(implicit s: Session): Unit =
@@ -337,49 +333,64 @@
.update (defaultBranch)
/**
- * Add collaborator to the repository.
- *
- * @param userName the user name of the repository owner
- * @param repositoryName the repository name
- * @param collaboratorName the collaborator name
+ * Add collaborator (user or group) to the repository.
*/
- def addCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit =
- Collaborators insert Collaborator(userName, repositoryName, collaboratorName)
-
- /**
- * Remove collaborator from the repository.
- *
- * @param userName the user name of the repository owner
- * @param repositoryName the repository name
- * @param collaboratorName the collaborator name
- */
- def removeCollaborator(userName: String, repositoryName: String, collaboratorName: String)(implicit s: Session): Unit =
- Collaborators.filter(_.byPrimaryKey(userName, repositoryName, collaboratorName)).delete
+ def addCollaborator(userName: String, repositoryName: String, collaboratorName: String, permission: String)(implicit s: Session): Unit =
+ Collaborators insert Collaborator(userName, repositoryName, collaboratorName, permission)
/**
* Remove all collaborators from the repository.
- *
- * @param userName the user name of the repository owner
- * @param repositoryName the repository name
*/
def removeCollaborators(userName: String, repositoryName: String)(implicit s: Session): Unit =
Collaborators.filter(_.byRepository(userName, repositoryName)).delete
/**
- * Returns the list of collaborators name which is sorted with ascending order.
- *
- * @param userName the user name of the repository owner
- * @param repositoryName the repository name
- * @return the list of collaborators name
+ * Returns the list of collaborators name (user name or group name) which is sorted with ascending order.
*/
- def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[String] =
- Collaborators.filter(_.byRepository(userName, repositoryName)).sortBy(_.collaboratorName).map(_.collaboratorName).list
+ def getCollaborators(userName: String, repositoryName: String)(implicit s: Session): List[(Collaborator, Boolean)] =
+ Collaborators
+ .innerJoin(Accounts).on(_.collaboratorName === _.userName)
+ .filter { case (t1, t2) => t1.byRepository(userName, repositoryName) }
+ .map { case (t1, t2) => (t1, t2.groupAccount) }
+ .sortBy { case (t1, t2) => t1.collaboratorName }
+ .list
+
+ /**
+ * Returns the list of all collaborator name and permission which is sorted with ascending order.
+ * If a group is added as a collaborator, this method returns users who are belong to that group.
+ */
+ def getCollaboratorUserNames(userName: String, repositoryName: String, filter: Seq[Permission] = Nil)(implicit s: Session): List[String] = {
+ val q1 = Collaborators
+ .innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === false.bind) }
+ .filter { case (t1, t2) => t1.byRepository(userName, repositoryName) }
+ .map { case (t1, t2) => (t1.collaboratorName, t1.permission) }
+
+ val q2 = Collaborators
+ .innerJoin(Accounts).on { case (t1, t2) => (t1.collaboratorName === t2.userName) && (t2.groupAccount === true.bind) }
+ .innerJoin(GroupMembers).on { case ((t1, t2), t3) => t2.userName === t3.groupName }
+ .filter { case ((t1, t2), t3) => t1.byRepository(userName, repositoryName) }
+ .map { case ((t1, t2), t3) => (t3.userName, t1.permission) }
+
+ q1.union(q2).list.filter { x => filter.isEmpty || filter.exists(_.name == x._2) }.map(_._1)
+ }
+
def hasWritePermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
loginAccount match {
case Some(a) if(a.isAdmin) => true
case Some(a) if(a.userName == owner) => true
- case Some(a) if(getCollaborators(owner, repository).contains(a.userName)) => true
+ case Some(a) if(getGroupMembers(owner).exists(_.userName == a.userName)) => true
+ case Some(a) if(getCollaboratorUserNames(owner, repository, Seq(Permission.ADMIN, Permission.WRITE)).contains(a.userName)) => true
+ case _ => false
+ }
+ }
+
+ def hasReadPermission(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
+ loginAccount match {
+ case Some(a) if(a.isAdmin) => true
+ case Some(a) if(a.userName == owner) => true
+ case Some(a) if(getGroupMembers(owner).exists(_.userName == a.userName)) => true
+ case Some(a) if(getCollaboratorUserNames(owner, repository, Seq(Permission.ADMIN, Permission.WRITE, Permission.READ)).contains(a.userName)) => true
case _ => false
}
}
diff --git a/src/main/scala/gitbucket/core/service/RequestCache.scala b/src/main/scala/gitbucket/core/service/RequestCache.scala
index 768a3b4..bd03cd8 100644
--- a/src/main/scala/gitbucket/core/service/RequestCache.scala
+++ b/src/main/scala/gitbucket/core/service/RequestCache.scala
@@ -11,7 +11,7 @@
* It may be called many times in one request, so each method stores
* its result into the cache which available during a request.
*/
-trait RequestCache extends SystemSettingsService with AccountService with IssuesService {
+trait RequestCache extends SystemSettingsService with AccountService with IssuesService with RepositoryService {
private implicit def context2Session(implicit context: Context): Session =
request2Session(context.request)
diff --git a/src/main/scala/gitbucket/core/util/Authenticator.scala b/src/main/scala/gitbucket/core/util/Authenticator.scala
index 0035494..b4cfef5 100644
--- a/src/main/scala/gitbucket/core/util/Authenticator.scala
+++ b/src/main/scala/gitbucket/core/util/Authenticator.scala
@@ -1,11 +1,14 @@
package gitbucket.core.util
import gitbucket.core.controller.ControllerBase
-import gitbucket.core.service.{RepositoryService, AccountService}
+import gitbucket.core.service.{AccountService, RepositoryService}
+import gitbucket.core.model.Permission
import RepositoryService.RepositoryInfo
import Implicits._
import ControlUtil._
+import scala.collection.Searching.search
+
/**
* Allows only oneself and administrators.
*/
@@ -40,9 +43,9 @@
context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(repository.owner == x.userName) => action(repository)
- case Some(x) if(getGroupMembers(repository.owner).exists { member =>
- member.userName == x.userName && member.isManager == true
- }) => action(repository)
+ // TODO Repository management is allowed for only group managers?
+ case Some(x) if(getGroupMembers(repository.owner).exists { m => m.userName == x.userName && m.isManager == true }) => action(repository)
+ case Some(x) if(getCollaboratorUserNames(paths(0), paths(1), Seq(Permission.ADMIN)).contains(x.userName)) => action(repository)
case _ => Unauthorized()
}
} getOrElse NotFound()
@@ -86,32 +89,9 @@
}
/**
- * Allows only collaborators and administrators.
+ * Allows only guests and signed in users who can access the repository.
*/
-trait CollaboratorsAuthenticator { self: ControllerBase with RepositoryService =>
- protected def collaboratorsOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
- protected def collaboratorsOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
-
- private def authenticate(action: (RepositoryInfo) => Any) = {
- {
- defining(request.paths){ paths =>
- getRepository(paths(0), paths(1)).map { repository =>
- context.loginAccount match {
- case Some(x) if(x.isAdmin) => action(repository)
- case Some(x) if(paths(0) == x.userName) => action(repository)
- case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
- case _ => Unauthorized()
- }
- } getOrElse NotFound()
- }
- }
- }
-}
-
-/**
- * Allows only the repository owner (or manager for group repository) and administrators.
- */
-trait ReferrerAuthenticator { self: ControllerBase with RepositoryService =>
+trait ReferrerAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
protected def referrersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
protected def referrersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
@@ -125,7 +105,8 @@
context.loginAccount match {
case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(paths(0) == x.userName) => action(repository)
- case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
+ case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
+ case Some(x) if(getCollaboratorUserNames(paths(0), paths(1)).contains(x.userName)) => action(repository)
case _ => Unauthorized()
}
}
@@ -136,9 +117,9 @@
}
/**
- * Allows only signed in users which can access the repository.
+ * Allows only signed in users who have read permission for the repository.
*/
-trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService =>
+trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
protected def readableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
@@ -150,7 +131,32 @@
case Some(x) if(x.isAdmin) => action(repository)
case Some(x) if(!repository.repository.isPrivate) => action(repository)
case Some(x) if(paths(0) == x.userName) => action(repository)
- case Some(x) if(getCollaborators(paths(0), paths(1)).contains(x.userName)) => action(repository)
+ case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
+ case Some(x) if(getCollaboratorUserNames(paths(0), paths(1)).contains(x.userName)) => action(repository)
+ case _ => Unauthorized()
+ }
+ } getOrElse NotFound()
+ }
+ }
+ }
+}
+
+/**
+ * Allows only signed in users who have write permission for the repository.
+ */
+trait WritableUsersAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
+ protected def writableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
+ protected def writableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
+
+ private def authenticate(action: (RepositoryInfo) => Any) = {
+ {
+ defining(request.paths){ paths =>
+ getRepository(paths(0), paths(1)).map { repository =>
+ context.loginAccount match {
+ case Some(x) if(x.isAdmin) => action(repository)
+ case Some(x) if(paths(0) == x.userName) => action(repository)
+ case Some(x) if(getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
+ case Some(x) if(getCollaboratorUserNames(paths(0), paths(1), Seq(Permission.ADMIN, Permission.WRITE)).contains(x.userName)) => action(repository)
case _ => Unauthorized()
}
} getOrElse NotFound()
diff --git a/src/main/scala/gitbucket/core/util/Notifier.scala b/src/main/scala/gitbucket/core/util/Notifier.scala
index 36ec89d..24a883d 100644
--- a/src/main/scala/gitbucket/core/util/Notifier.scala
+++ b/src/main/scala/gitbucket/core/util/Notifier.scala
@@ -22,8 +22,10 @@
(
// individual repository's owner
issue.userName ::
+ // group members of group repository
+ getGroupMembers(issue.userName).map(_.userName) :::
// collaborators
- getCollaborators(issue.userName, issue.repositoryName) :::
+ getCollaboratorUserNames(issue.userName, issue.repositoryName) :::
// participants
issue.openedUserName ::
getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
diff --git a/src/main/twirl/gitbucket/core/account/group.scala.html b/src/main/twirl/gitbucket/core/account/group.scala.html
index 1bda182..797c669 100644
--- a/src/main/twirl/gitbucket/core/account/group.scala.html
+++ b/src/main/twirl/gitbucket/core/account/group.scala.html
@@ -31,7 +31,7 @@
- @gitbucket.core.helper.html.account("memberName", 200)
+ @gitbucket.core.helper.html.account("memberName", 200, true, false)
@@ -80,15 +80,14 @@
}
// check existence
- $.post('@context.path/_user/existence', {
- 'userName': userName
- }, function(data, status){
- if(data == 'true'){
- addMemberHTML(userName, false);
- } else {
- $('#error-members').text('User does not exist.');
- }
- });
+ $.post('@context.path/_user/existence', { 'userName': userName },
+ function(data, status){
+ if(data == 'user'){
+ addMemberHTML(userName, false);
+ } else {
+ $('#error-members').text('User does not exist.');
+ }
+ });
});
$(document).on('click', '.remove', function(){
diff --git a/src/main/twirl/gitbucket/core/admin/usergroup.scala.html b/src/main/twirl/gitbucket/core/admin/usergroup.scala.html
index ec99a73..b72ae1d 100644
--- a/src/main/twirl/gitbucket/core/admin/usergroup.scala.html
+++ b/src/main/twirl/gitbucket/core/admin/usergroup.scala.html
@@ -34,7 +34,7 @@
- @gitbucket.core.helper.html.account("memberName", 200)
+ @gitbucket.core.helper.html.account("memberName", 200, true, false)
@@ -75,16 +75,14 @@
}
// check existence
- $.post('@context.path/_user/existence', {
- 'userName': userName,
- 'userOnly': true
- }, function(data, status){
- if(data == 'true'){
- addMemberHTML(userName, false);
- } else {
- $('#error-members').text('User does not exist.');
- }
- });
+ $.post('@context.path/_user/existence', { 'userName': userName },
+ function(data, status){
+ if(data == 'user'){
+ addMemberHTML(userName, false);
+ } else {
+ $('#error-members').text('User does not exist.');
+ }
+ });
});
$(document).on('click', '.remove', function(){
diff --git a/src/main/twirl/gitbucket/core/helper/account.scala.html b/src/main/twirl/gitbucket/core/helper/account.scala.html
index e3dc42c..0e14ced 100644
--- a/src/main/twirl/gitbucket/core/helper/account.scala.html
+++ b/src/main/twirl/gitbucket/core/helper/account.scala.html
@@ -1,12 +1,19 @@
-@(id: String, width: Int)(implicit context: gitbucket.core.controller.Context)
+@(id: String, width: Int, user: Boolean, group: Boolean)(implicit context: gitbucket.core.controller.Context)
\ No newline at end of file
diff --git a/src/main/twirl/gitbucket/core/settings/danger.scala.html b/src/main/twirl/gitbucket/core/settings/danger.scala.html
index 60b4007..7df1170 100644
--- a/src/main/twirl/gitbucket/core/settings/danger.scala.html
+++ b/src/main/twirl/gitbucket/core/settings/danger.scala.html
@@ -13,7 +13,7 @@
Transfer this repo to another user or to group.
- @gitbucket.core.helper.html.account("newOwner", 200)
+ @gitbucket.core.helper.html.account("newOwner", 200, true, true)
diff --git a/src/main/twirl/gitbucket/core/settings/options.scala.html b/src/main/twirl/gitbucket/core/settings/options.scala.html
index 7b26310..f978b58 100644
--- a/src/main/twirl/gitbucket/core/settings/options.scala.html
+++ b/src/main/twirl/gitbucket/core/settings/options.scala.html
@@ -39,41 +39,6 @@
-
-
-
+
+
@@ -96,14 +113,13 @@
$(function(){
updateFeatures();
- $('#enableIssues, #enableWiki').click(function(){
+ $('input[name=issuesOption], input[name=wikiOption]').click(function(){
updateFeatures();
});
});
function updateFeatures() {
- $('#externalIssuesUrl').prop('disabled', $('#enableIssues').prop('checked'));
- $('#allowWikiEditing').prop('disabled', !$('#enableWiki').prop('checked'));
- $('#externalWikiUrl').prop('disabled', $('#enableWiki').prop('checked'));
+ $('#externalIssuesUrl').prop('disabled', !$('input[name=issuesOption]').select('[value=DISABLE]').prop('checked'));
+ $('#externalWikiUrl').prop('disabled', !$('input[name=wikiOption]').select('[value=DISABLE]').prop('checked'));
}
diff --git a/src/main/twirl/gitbucket/core/wiki/compare.scala.html b/src/main/twirl/gitbucket/core/wiki/compare.scala.html
index 3745cb7..e2fe163 100644
--- a/src/main/twirl/gitbucket/core/wiki/compare.scala.html
+++ b/src/main/twirl/gitbucket/core/wiki/compare.scala.html
@@ -3,7 +3,7 @@
to: String,
diffs: Seq[gitbucket.core.util.JGitUtil.DiffInfo],
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
- hasWritePermission: Boolean,
+ isEditable: Boolean,
info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"Compare Revisions - ${repository.owner}/${repository.name}", Some(repository)){
@@ -27,7 +27,7 @@
@gitbucket.core.helper.html.diff(diffs, repository, None, None, false, None, false, false)
- @if(hasWritePermission){
+ @if(isEditable){
@if(pageName.isDefined){
Revert Changes
diff --git a/src/main/twirl/gitbucket/core/wiki/edit.scala.html b/src/main/twirl/gitbucket/core/wiki/edit.scala.html
index 5a0dc7d..05b3264 100644
--- a/src/main/twirl/gitbucket/core/wiki/edit.scala.html
+++ b/src/main/twirl/gitbucket/core/wiki/edit.scala.html
@@ -44,7 +44,7 @@
}
}
-