mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-05 04:56:02 +01:00
Reject direct push to branch if branch protection is enabled (#3791)
This commit is contained in:
32
src/main/resources/update/gitbucket-core_4.44.xml
Normal file
32
src/main/resources/update/gitbucket-core_4.44.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<!--================================================================================================-->
|
||||
<!-- PROTECTED_BRANCH -->
|
||||
<!--================================================================================================-->
|
||||
<addColumn tableName="PROTECTED_BRANCH">
|
||||
<column name="REQUIRED_STATUS_CHECK" type="boolean" nullable="false" defaultValue="false"/>
|
||||
<column name="RESTRICTIONS" type="boolean" nullable="false" defaultValue="false"/>
|
||||
</addColumn>
|
||||
|
||||
<sql>
|
||||
UPDATE PROTECTED_BRANCH SET REQUIRED_STATUS_CHECK = TRUE
|
||||
WHERE EXISTS (SELECT * FROM PROTECTED_BRANCH_REQUIRE_CONTEXT
|
||||
WHERE PROTECTED_BRANCH.USER_NAME = PROTECTED_BRANCH_REQUIRE_CONTEXT.USER_NAME
|
||||
AND PROTECTED_BRANCH.REPOSITORY_NAME = PROTECTED_BRANCH_REQUIRE_CONTEXT.REPOSITORY_NAME
|
||||
AND PROTECTED_BRANCH.BRANCH = PROTECTED_BRANCH_REQUIRE_CONTEXT.BRANCH)
|
||||
</sql>
|
||||
|
||||
<!--================================================================================================-->
|
||||
<!-- PROTECTED_BRANCH_RESTRICTIONS_USER -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="PROTECTED_BRANCH_RESTRICTION">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||
<column name="ALLOWED_USER" type="varchar(255)" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_RESTRICTION_PK" tableName="PROTECTED_BRANCH_RESTRICTION" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH, ALLOWED_USER"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_RESTRICTION_FK0" baseTableName="PROTECTED_BRANCH_RESTRICTION" baseColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" referencedTableName="PROTECTED_BRANCH" referencedColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_RESTRICTION_FK1" baseTableName="PROTECTED_BRANCH_RESTRICTION" baseColumnNames="ALLOWED_USER" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||
</changeSet>
|
||||
@@ -120,7 +120,8 @@ object GitBucketCoreModule
|
||||
new Version("4.41.0"),
|
||||
new Version("4.42.0", new LiquibaseMigration("update/gitbucket-core_4.42.xml")),
|
||||
new Version("4.42.1"),
|
||||
new Version("4.43.0")
|
||||
new Version("4.43.0"),
|
||||
new Version("4.44.0", new LiquibaseMigration("update/gitbucket-core_4.44.xml"))
|
||||
) {
|
||||
java.util.logging.Logger.getLogger("liquibase").setLevel(Level.SEVERE)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import gitbucket.core.util.RepositoryName
|
||||
* https://developer.github.com/v3/repos/#get-branch
|
||||
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||
*/
|
||||
case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtection)(
|
||||
case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtectionResponse)(
|
||||
repositoryName: RepositoryName
|
||||
) extends FieldSerializable {
|
||||
val _links =
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
case class ApiBranchProtectionRequest(
|
||||
enabled: Boolean,
|
||||
required_status_checks: Option[ApiBranchProtectionRequest.Status],
|
||||
restrictions: Option[ApiBranchProtectionRequest.Restrictions],
|
||||
enforce_admins: Option[Boolean]
|
||||
)
|
||||
|
||||
object ApiBranchProtectionRequest {
|
||||
|
||||
/** form for enabling-and-disabling-branch-protection */
|
||||
case class EnablingAndDisabling(protection: ApiBranchProtectionRequest)
|
||||
|
||||
case class Status(
|
||||
contexts: Seq[String]
|
||||
)
|
||||
|
||||
case class Restrictions(users: Seq[String])
|
||||
}
|
||||
@@ -4,55 +4,68 @@ import gitbucket.core.service.ProtectedBranchService
|
||||
import org.json4s._
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
case class ApiBranchProtection(
|
||||
case class ApiBranchProtectionResponse(
|
||||
url: Option[ApiPath], // for output
|
||||
enabled: Boolean,
|
||||
required_status_checks: Option[ApiBranchProtection.Status]
|
||||
required_status_checks: Option[ApiBranchProtectionResponse.Status],
|
||||
restrictions: Option[ApiBranchProtectionResponse.Restrictions],
|
||||
enforce_admins: Option[ApiBranchProtectionResponse.EnforceAdmins]
|
||||
) {
|
||||
def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
|
||||
def status: ApiBranchProtectionResponse.Status =
|
||||
required_status_checks.getOrElse(ApiBranchProtectionResponse.statusNone)
|
||||
}
|
||||
|
||||
object ApiBranchProtection {
|
||||
object ApiBranchProtectionResponse {
|
||||
|
||||
/** form for enabling-and-disabling-branch-protection */
|
||||
case class EnablingAndDisabling(protection: ApiBranchProtection)
|
||||
case class EnforceAdmins(enabled: Boolean)
|
||||
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection =
|
||||
ApiBranchProtection(
|
||||
// /** form for enabling-and-disabling-branch-protection */
|
||||
// case class EnablingAndDisabling(protection: ApiBranchProtectionResponse)
|
||||
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtectionResponse =
|
||||
ApiBranchProtectionResponse(
|
||||
url = Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection"
|
||||
)
|
||||
),
|
||||
enabled = info.enabled,
|
||||
required_status_checks = Some(
|
||||
required_status_checks = info.contexts.map { contexts =>
|
||||
Status(
|
||||
Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks"
|
||||
)
|
||||
),
|
||||
EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators),
|
||||
info.contexts,
|
||||
EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.enforceAdmins),
|
||||
contexts,
|
||||
Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks/contexts"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
restrictions = info.restrictionsUsers.map { restrictionsUsers =>
|
||||
Restrictions(restrictionsUsers)
|
||||
},
|
||||
enforce_admins = if (info.enabled) Some(EnforceAdmins(info.enforceAdmins)) else None
|
||||
)
|
||||
val statusNone = Status(None, Off, Seq.empty, None)
|
||||
|
||||
val statusNone: Status = Status(None, Off, Seq.empty, None)
|
||||
|
||||
case class Status(
|
||||
url: Option[ApiPath], // for output
|
||||
enforcement_level: EnforcementLevel,
|
||||
contexts: Seq[String],
|
||||
contexts_url: Option[ApiPath] // for output
|
||||
)
|
||||
|
||||
sealed class EnforcementLevel(val name: String)
|
||||
case object Off extends EnforcementLevel("off")
|
||||
case object NonAdmins extends EnforcementLevel("non_admins")
|
||||
case object Everyone extends EnforcementLevel("everyone")
|
||||
|
||||
object EnforcementLevel {
|
||||
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel =
|
||||
if (enabled) {
|
||||
@@ -66,6 +79,8 @@ object ApiBranchProtection {
|
||||
}
|
||||
}
|
||||
|
||||
case class Restrictions(users: Seq[String])
|
||||
|
||||
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] =
|
||||
new CustomSerializer[EnforcementLevel](format =>
|
||||
(
|
||||
@@ -44,7 +44,7 @@ object JsonFormat {
|
||||
FieldSerializer[ApiCommits.File]() +
|
||||
FieldSerializer[ApiRelease]() +
|
||||
FieldSerializer[ApiReleaseAsset]() +
|
||||
ApiBranchProtection.enforcementLevelSerializer
|
||||
ApiBranchProtectionResponse.enforcementLevelSerializer
|
||||
|
||||
def apiPathSerializer(c: Context) =
|
||||
new CustomSerializer[ApiPath](_ =>
|
||||
|
||||
@@ -261,11 +261,22 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* JSON API for checking user or group existence.
|
||||
*
|
||||
* Returns a single string which is any of "group", "user" or "".
|
||||
* Additionally, check whether the user is writable to the repository
|
||||
* if "owner" and "repository" are given,
|
||||
*/
|
||||
post("/_user/existence")(usersOnly {
|
||||
getAccountByUserNameIgnoreCase(params("userName")).map { account =>
|
||||
if (account.isGroupAccount) "group" else "user"
|
||||
if (!account.isGroupAccount && params.get("repository").isDefined && params.get("owner").isDefined) {
|
||||
getRepository(params("owner"), params("repository"))
|
||||
.collect {
|
||||
case repository if isWritable(repository.repository, Some(account)) => "user"
|
||||
}
|
||||
.getOrElse("")
|
||||
} else {
|
||||
if (account.isGroupAccount) "group" else "user"
|
||||
}
|
||||
} getOrElse ""
|
||||
})
|
||||
|
||||
|
||||
@@ -203,7 +203,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
if (!repository.branchList.contains(branch)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||
} else {
|
||||
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val protection = ApiBranchProtectionResponse(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val lastWeeks = getRecentStatusContexts(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
|
||||
@@ -5,7 +5,7 @@ import gitbucket.core.service.{AccountService, ProtectedBranchService, Repositor
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.util.Directory.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.JGitUtil.getBranchesNoMergeInfo
|
||||
import gitbucket.core.util.JGitUtil.{getBranchesNoMergeInfo, processTree}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.NoContent
|
||||
|
||||
@@ -43,7 +43,9 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtectionResponse(protection))(
|
||||
RepositoryName(repository)
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
@@ -58,7 +60,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranchProtection(protection)
|
||||
ApiBranchProtectionResponse(protection)
|
||||
)
|
||||
} else { NotFound() }
|
||||
})
|
||||
@@ -138,7 +140,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranchProtection(protection).required_status_checks
|
||||
ApiBranchProtectionResponse(protection).required_status_checks
|
||||
)
|
||||
} else { NotFound() }
|
||||
})
|
||||
@@ -262,7 +264,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
protection <- extractFromJsonBody[ApiBranchProtectionRequest.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranchesNoMergeInfo(git).find(_.name == branch)
|
||||
} yield {
|
||||
if (protection.enabled) {
|
||||
@@ -270,13 +272,17 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
repository.owner,
|
||||
repository.name,
|
||||
branch,
|
||||
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
||||
protection.status.contexts
|
||||
protection.enforce_admins.getOrElse(false),
|
||||
protection.required_status_checks.isDefined,
|
||||
protection.required_status_checks.map(_.contexts).getOrElse(Nil),
|
||||
protection.restrictions.isDefined,
|
||||
protection.restrictions.map(_.users).getOrElse(Nil)
|
||||
)
|
||||
} else {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
}
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||
val response = ApiBranchProtectionResponse(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), response)(RepositoryName(repository)))
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import self._
|
||||
import profile.api.*
|
||||
|
||||
lazy val ProtectedBranches = TableQuery[ProtectedBranches]
|
||||
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
|
||||
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
|
||||
def * = (userName, repositoryName, branch, statusCheckAdmin).mapTo[ProtectedBranch]
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String) =
|
||||
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN") // enforceAdmins
|
||||
val requiredStatusCheck = column[Boolean]("REQUIRED_STATUS_CHECK")
|
||||
val restrictions = column[Boolean]("RESTRICTIONS")
|
||||
def * =
|
||||
(userName, repositoryName, branch, statusCheckAdmin, requiredStatusCheck, restrictions).mapTo[ProtectedBranch]
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String): Rep[Boolean] =
|
||||
byBranch(userName, repositoryName, branch)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) =
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]): Rep[Boolean] =
|
||||
byBranch(userName, repositoryName, branch)
|
||||
}
|
||||
|
||||
@@ -22,8 +24,27 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
||||
def * =
|
||||
(userName, repositoryName, branch, context).mapTo[ProtectedBranchContext]
|
||||
}
|
||||
|
||||
lazy val ProtectedBranchRestrictions = TableQuery[ProtectedBranchRestrictions]
|
||||
class ProtectedBranchRestrictions(tag: Tag)
|
||||
extends Table[ProtectedBranchRestriction](tag, "PROTECTED_BRANCH_RESTRICTION")
|
||||
with BranchTemplate {
|
||||
val allowedUser = column[String]("ALLOWED_USER")
|
||||
def * = (userName, repositoryName, branch, allowedUser).mapTo[ProtectedBranchRestriction]
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String, allowedUser: String): Rep[Boolean] =
|
||||
this.userName === userName.bind && this.repositoryName === repositoryName.bind && this.branch === branch.bind && this.allowedUser === allowedUser.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class ProtectedBranch(userName: String, repositoryName: String, branch: String, statusCheckAdmin: Boolean)
|
||||
case class ProtectedBranch(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
enforceAdmins: Boolean,
|
||||
requiredStatusCheck: Boolean,
|
||||
restrictions: Boolean
|
||||
)
|
||||
|
||||
case class ProtectedBranchContext(userName: String, repositoryName: String, branch: String, context: String)
|
||||
|
||||
case class ProtectedBranchRestriction(userName: String, repositoryName: String, branch: String, allowedUser: String)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.{Session => _, _}
|
||||
import gitbucket.core.plugin.ReceiveHook
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.{CommitState, ProtectedBranch, ProtectedBranchContext, ProtectedBranchRestriction, Role}
|
||||
import gitbucket.core.util.SyntaxSugars.*
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
|
||||
trait ProtectedBranchService {
|
||||
@@ -13,17 +14,27 @@ trait ProtectedBranchService {
|
||||
): Option[ProtectedBranchInfo] =
|
||||
ProtectedBranches
|
||||
.joinLeft(ProtectedBranchContexts)
|
||||
.on { case (pb, c) => pb.byBranch(c.userName, c.repositoryName, c.branch) }
|
||||
.map { case (pb, c) => pb -> c.map(_.context) }
|
||||
.on { case pb ~ c => pb.byBranch(c.userName, c.repositoryName, c.branch) }
|
||||
.joinLeft(ProtectedBranchRestrictions)
|
||||
.on { case pb ~ c ~ r => pb.byBranch(r.userName, r.repositoryName, r.branch) }
|
||||
.map { case pb ~ c ~ r => pb -> (c.map(_.context), r.map(_.allowedUser)) }
|
||||
.filter(_._1.byPrimaryKey(owner, repository, branch))
|
||||
.list
|
||||
.groupBy(_._1)
|
||||
.headOption
|
||||
.map { p =>
|
||||
p._1 -> p._2.flatMap(_._2)
|
||||
.map { (p: (ProtectedBranch, List[(ProtectedBranch, (Option[String], Option[String]))])) =>
|
||||
p._1 -> (p._2.flatMap(_._2._1), p._2.flatMap(_._2._2))
|
||||
}
|
||||
.map { case (t1, contexts) =>
|
||||
new ProtectedBranchInfo(t1.userName, t1.repositoryName, t1.branch, true, contexts, t1.statusCheckAdmin)
|
||||
.map { case (t1, (contexts, users)) =>
|
||||
new ProtectedBranchInfo(
|
||||
t1.userName,
|
||||
t1.repositoryName,
|
||||
t1.branch,
|
||||
true,
|
||||
if (t1.requiredStatusCheck) Some(contexts) else None,
|
||||
t1.enforceAdmins,
|
||||
if (t1.restrictions) Some(users) else None
|
||||
)
|
||||
}
|
||||
|
||||
def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit
|
||||
@@ -40,19 +51,32 @@ trait ProtectedBranchService {
|
||||
owner: String,
|
||||
repository: String,
|
||||
branch: String,
|
||||
includeAdministrators: Boolean,
|
||||
contexts: Seq[String]
|
||||
enforceAdmins: Boolean,
|
||||
requiredStatusCheck: Boolean,
|
||||
contexts: Seq[String],
|
||||
restrictions: Boolean,
|
||||
restrictionsUsers: Seq[String]
|
||||
)(implicit session: Session): Unit = {
|
||||
disableBranchProtection(owner, repository, branch)
|
||||
ProtectedBranches.insert(new ProtectedBranch(owner, repository, branch, includeAdministrators && contexts.nonEmpty))
|
||||
contexts.map { context =>
|
||||
ProtectedBranchContexts.insert(new ProtectedBranchContext(owner, repository, branch, context))
|
||||
ProtectedBranches.insert(
|
||||
ProtectedBranch(owner, repository, branch, enforceAdmins, requiredStatusCheck, restrictions)
|
||||
)
|
||||
|
||||
if (restrictions) {
|
||||
restrictionsUsers.foreach { user =>
|
||||
ProtectedBranchRestrictions.insert(ProtectedBranchRestriction(owner, repository, branch, user))
|
||||
}
|
||||
}
|
||||
|
||||
if (requiredStatusCheck) {
|
||||
contexts.foreach { context =>
|
||||
ProtectedBranchContexts.insert(ProtectedBranchContext(owner, repository, branch, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def disableBranchProtection(owner: String, repository: String, branch: String)(implicit session: Session): Unit =
|
||||
ProtectedBranches.filter(_.byPrimaryKey(owner, repository, branch)).delete
|
||||
|
||||
}
|
||||
|
||||
object ProtectedBranchService {
|
||||
@@ -101,6 +125,7 @@ object ProtectedBranchService {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
println("-> else")
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -117,12 +142,16 @@ object ProtectedBranchService {
|
||||
* When enabled, commits must first be pushed to another branch,
|
||||
* then merged or pushed directly to test after status checks have passed.
|
||||
*/
|
||||
contexts: Seq[String],
|
||||
contexts: Option[Seq[String]],
|
||||
/**
|
||||
* Include administrators
|
||||
* Enforce required status checks for repository administrators.
|
||||
*/
|
||||
includeAdministrators: Boolean
|
||||
enforceAdmins: Boolean,
|
||||
/**
|
||||
* Users who can push to the branch.
|
||||
*/
|
||||
restrictionsUsers: Option[Seq[String]]
|
||||
) extends AccountService
|
||||
with RepositoryService
|
||||
with CommitStatusService {
|
||||
@@ -148,42 +177,66 @@ object ProtectedBranchService {
|
||||
session: Session
|
||||
): Option[String] = {
|
||||
if (enabled) {
|
||||
command.getType() match {
|
||||
command.getType match {
|
||||
case ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards =>
|
||||
Some("Cannot force-push to a protected branch")
|
||||
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if !isPushAllowed(pusher) =>
|
||||
Some("You do not have permission to push to this branch")
|
||||
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
|
||||
unSuccessedContexts(command.getNewId.name) match {
|
||||
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
||||
case s if s.sizeIs >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
||||
case _ => None
|
||||
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.head}" is expected""")
|
||||
case s if s.sizeIs >= 1 =>
|
||||
Some(s"${s.size} of ${contexts.map(_.size).getOrElse(0)} required status checks are expected")
|
||||
case _ => None
|
||||
}
|
||||
case ReceiveCommand.Type.DELETE =>
|
||||
Some("Cannot delete a protected branch")
|
||||
Some("You do not have permission to push to this branch")
|
||||
case _ => None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] =
|
||||
if (contexts.isEmpty) {
|
||||
Set.empty
|
||||
} else {
|
||||
contexts.toSet -- getCommitStatuses(owner, repository, sha1)
|
||||
.filter(_.state == CommitState.SUCCESS)
|
||||
.map(_.context)
|
||||
.toSet
|
||||
|
||||
def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] = {
|
||||
contexts match {
|
||||
case None => Set.empty
|
||||
case Some(x) if x.isEmpty => Set.empty
|
||||
case Some(x) =>
|
||||
x.toSet -- getCommitStatuses(owner, repository, sha1)
|
||||
.filter(_.state == CommitState.SUCCESS)
|
||||
.map(_.context)
|
||||
.toSet
|
||||
}
|
||||
}
|
||||
|
||||
def needStatusCheck(pusher: String)(implicit session: Session): Boolean = pusher match {
|
||||
case _ if !enabled => false
|
||||
case _ if contexts.isEmpty => false
|
||||
case _ if includeAdministrators => true
|
||||
case p if isAdministrator(p) => false
|
||||
case _ => true
|
||||
case _ if !enabled => false
|
||||
case _ if contexts.isEmpty => false
|
||||
case _ if enforceAdmins => true
|
||||
case p if isAdministrator(p) => false
|
||||
case _ => true
|
||||
}
|
||||
|
||||
def isPushAllowed(pusher: String)(implicit session: Session): Boolean = pusher match {
|
||||
case _ if !enabled || restrictionsUsers.isEmpty => true
|
||||
case _ if restrictionsUsers.get.contains(pusher) => true
|
||||
case p if isAdministrator(p) && enforceAdmins => false
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
object ProtectedBranchInfo {
|
||||
def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo =
|
||||
ProtectedBranchInfo(owner, repository, branch, false, Nil, false)
|
||||
def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo = {
|
||||
ProtectedBranchInfo(
|
||||
owner,
|
||||
repository,
|
||||
branch,
|
||||
enabled = false,
|
||||
contexts = None,
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,18 +653,18 @@ object PullRequestService {
|
||||
commitIdTo: String
|
||||
) {
|
||||
|
||||
val hasConflict = conflictMessage.isDefined
|
||||
val hasConflict: Boolean = conflictMessage.isDefined
|
||||
val statuses: List[CommitStatus] =
|
||||
commitStatuses ++ (branchProtection.contexts.toSet -- commitStatuses.map(_.context).toSet)
|
||||
commitStatuses ++ (branchProtection.contexts.getOrElse(Nil).toSet -- commitStatuses.map(_.context).toSet)
|
||||
.map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
|
||||
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context =>
|
||||
statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS)
|
||||
)
|
||||
val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(
|
||||
val hasRequiredStatusProblem: Boolean = needStatusCheck && branchProtection.contexts
|
||||
.getOrElse(Nil)
|
||||
.exists(context => !statuses.find(_.context == context).map(_.state).contains(CommitState.SUCCESS))
|
||||
val hasProblem: Boolean = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(
|
||||
statuses.map(_.state).toSet
|
||||
) != CommitState.SUCCESS)
|
||||
val canUpdate = branchIsOutOfDate && !hasConflict
|
||||
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
val canUpdate: Boolean = branchIsOutOfDate && !hasConflict
|
||||
val canMerge: Boolean = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
lazy val commitStateSummary: (CommitState, String) = {
|
||||
val stateMap = statuses.groupBy(_.state)
|
||||
val state = CommitState.combine(stateMap.keySet)
|
||||
@@ -672,8 +672,8 @@ object PullRequestService {
|
||||
state -> summary
|
||||
}
|
||||
lazy val statusesAndRequired: List[(CommitStatus, Boolean)] = statuses.map { s =>
|
||||
s -> branchProtection.contexts.contains(s.context)
|
||||
s -> branchProtection.contexts.getOrElse(Nil).contains(s.context)
|
||||
}
|
||||
lazy val isAllSuccess = commitStateSummary._1 == CommitState.SUCCESS
|
||||
lazy val isAllSuccess: Boolean = commitStateSummary._1 == CommitState.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.service
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.model.{Account, WebHook}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.activity.{CloseIssueInfo, PushInfo}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
@@ -11,14 +11,14 @@ import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.lib.*
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositoryCommitFileService {
|
||||
self: AccountService & ActivityService & IssuesService & PullRequestService & WebHookPullRequestService &
|
||||
RepositoryService =>
|
||||
RepositoryService & ProtectedBranchService =>
|
||||
|
||||
/**
|
||||
* Create multiple files by callback function.
|
||||
@@ -92,10 +92,10 @@ trait RepositoryCommitFileService {
|
||||
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, Option[ObjectId])] = {
|
||||
|
||||
val newPath = newFileName.map { newFileName =>
|
||||
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
||||
if (path.isEmpty) newFileName else s"${path}/${newFileName}"
|
||||
}
|
||||
val oldPath = oldFileName.map { oldFileName =>
|
||||
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
||||
if (path.isEmpty) oldFileName else s"${path}/${oldFileName}"
|
||||
}
|
||||
|
||||
_createFiles(repository, branch, message, pusherAccount, committerName, committerMailAddress, settings) {
|
||||
@@ -139,7 +139,6 @@ trait RepositoryCommitFileService {
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
|
||||
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, R)] = {
|
||||
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
@@ -168,7 +167,14 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// call pre-commit hook
|
||||
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, pusherAccount.userName, false)
|
||||
hook.preReceive(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
receivePack,
|
||||
receiveCommand,
|
||||
pusherAccount.userName,
|
||||
mergePullRequest = false
|
||||
)
|
||||
}.headOption
|
||||
|
||||
error match {
|
||||
@@ -194,7 +200,8 @@ trait RepositoryCommitFileService {
|
||||
// record activity
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
val pushInfo = PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
|
||||
val pushInfo =
|
||||
PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
|
||||
recordActivity(pushInfo)
|
||||
|
||||
// create issue comment by commit message
|
||||
@@ -221,7 +228,14 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks.foreach { hook =>
|
||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName, false)
|
||||
hook.postReceive(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
receivePack,
|
||||
receiveCommand,
|
||||
committerName,
|
||||
mergePullRequest = false
|
||||
)
|
||||
}
|
||||
|
||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
|
||||
@@ -654,11 +654,11 @@ trait RepositoryService {
|
||||
|
||||
def hasOwnerRole(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(Role.ADMIN)).contains(a.userName)) => true
|
||||
case _ => false
|
||||
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(Role.ADMIN)).contains(a.userName) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,11 +666,11 @@ trait RepositoryService {
|
||||
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 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(Role.ADMIN, Role.DEVELOPER)).contains(a.userName)) =>
|
||||
if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
@@ -678,12 +678,12 @@ trait RepositoryService {
|
||||
|
||||
def hasGuestRole(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 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(Role.ADMIN, Role.DEVELOPER, Role.GUEST))
|
||||
.contains(a.userName)) =>
|
||||
if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST))
|
||||
.contains(a.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
@@ -694,17 +694,29 @@ trait RepositoryService {
|
||||
true
|
||||
} else {
|
||||
loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => true
|
||||
case Some(x) if (repository.userName == x.userName) => true
|
||||
case Some(x) if (getGroupMembers(repository.userName).exists(_.userName == x.userName)) => true
|
||||
case Some(x)
|
||||
if (getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName)) =>
|
||||
case Some(x) if x.isAdmin => true
|
||||
case Some(x) if repository.userName == x.userName => true
|
||||
case Some(x) if getGroupMembers(repository.userName).exists(_.userName == x.userName) => true
|
||||
case Some(x) if getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def isWritable(repository: Repository, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(x) if x.isAdmin => true
|
||||
case Some(x) if repository.userName == x.userName => true
|
||||
case Some(x) if getGroupMembers(repository.userName).exists(_.userName == x.userName) => true
|
||||
case Some(x)
|
||||
if getCollaboratorUserNames(repository.userName, repository.repositoryName, Seq(Role.ADMIN, Role.DEVELOPER))
|
||||
.contains(x.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
|
||||
Query(Repositories.filter { t =>
|
||||
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
|
||||
|
||||
@@ -9,11 +9,11 @@ import gitbucket.core.api.JsonFormat.Context
|
||||
import gitbucket.core.model.WebHook
|
||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.service.WebHookService.*
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.activity.{
|
||||
BaseActivityInfo,
|
||||
CloseIssueInfo,
|
||||
@@ -33,9 +33,9 @@ import gitbucket.core.servlet.Database
|
||||
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.http.server.GitServlet
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.transport._
|
||||
import org.eclipse.jgit.transport.resolver._
|
||||
import org.eclipse.jgit.lib.*
|
||||
import org.eclipse.jgit.transport.*
|
||||
import org.eclipse.jgit.transport.resolver.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.ServletConfig
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
@@ -43,7 +43,7 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.internal.storage.file.FileRepository
|
||||
import org.json4s.Formats
|
||||
import org.json4s.convertToJsonInput
|
||||
import org.json4s.jackson.Serialization._
|
||||
import org.json4s.jackson.Serialization.*
|
||||
|
||||
/**
|
||||
* Provides Git repository via HTTP.
|
||||
@@ -117,7 +117,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
GitLfs.BatchResponseObject(
|
||||
requestObject.oid,
|
||||
requestObject.size,
|
||||
true,
|
||||
authenticated = true,
|
||||
GitLfs.Actions(
|
||||
upload = Some(
|
||||
GitLfs.Action(
|
||||
@@ -138,7 +138,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
GitLfs.BatchResponseObject(
|
||||
requestObject.oid,
|
||||
requestObject.size,
|
||||
true,
|
||||
authenticated = true,
|
||||
GitLfs.Actions(
|
||||
download = Some(
|
||||
GitLfs.Action(
|
||||
@@ -223,7 +223,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
}
|
||||
}
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
|
||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String])
|
||||
extends PostReceiveHook
|
||||
@@ -242,6 +242,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService
|
||||
with SystemSettingsService
|
||||
with ProtectedBranchService
|
||||
with RequestCache {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
@@ -253,7 +254,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
commands.asScala.foreach { command =>
|
||||
// call pre-commit hook
|
||||
PluginRegistry().getReceiveHooks
|
||||
.flatMap(_.preReceive(owner, repository, receivePack, command, pusher, false))
|
||||
.flatMap(_.preReceive(owner, repository, receivePack, command, pusher, mergePullRequest = false))
|
||||
.headOption
|
||||
.foreach { error =>
|
||||
command.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, error)
|
||||
@@ -428,8 +429,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
repositoryInfo,
|
||||
newCommits,
|
||||
ownerAccount,
|
||||
newId = command.getNewId(),
|
||||
oldId = command.getOldId()
|
||||
newId = command.getNewId,
|
||||
oldId = command.getOldId
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -453,7 +454,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks
|
||||
.foreach(_.postReceive(owner, repository, receivePack, command, pusher, false))
|
||||
.foreach(_.postReceive(owner, repository, receivePack, command, pusher, mergePullRequest = false))
|
||||
}
|
||||
}
|
||||
// update repository last modified time.
|
||||
|
||||
@@ -15,9 +15,9 @@ trait OneselfAuthenticator { self: ControllerBase =>
|
||||
|
||||
private def authenticate(action: => Any) = {
|
||||
context.loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => action
|
||||
case Some(x) if (request.paths(0) == x.userName) => action
|
||||
case _ => Unauthorized()
|
||||
case Some(x) if x.isAdmin => action
|
||||
case Some(x) if request.paths(0) == x.userName => action
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ trait OneselfAuthenticator { self: ControllerBase =>
|
||||
* Allows only the repository owner and administrators.
|
||||
*/
|
||||
trait OwnerAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def ownerOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def ownerOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def ownerOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
@@ -34,14 +34,14 @@ trait OwnerAuthenticator { self: ControllerBase & RepositoryService & AccountSer
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
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 x.isAdmin => action(repository)
|
||||
case Some(x) if repository.owner == x.userName => action(repository)
|
||||
// TODO Repository management is allowed for only group managers?
|
||||
case Some(x) if (getGroupMembers(repository.owner).exists { m =>
|
||||
case Some(x) if getGroupMembers(repository.owner).exists { m =>
|
||||
m.userName == x.userName && m.isManager
|
||||
}) =>
|
||||
} =>
|
||||
action(repository)
|
||||
case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) =>
|
||||
case Some(x) if getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName) =>
|
||||
action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
@@ -83,10 +83,10 @@ trait AdminAuthenticator { self: ControllerBase =>
|
||||
* Allows only guests and signed in users who can access the repository.
|
||||
*/
|
||||
trait ReferrerAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def referrersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def referrersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def referrersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
private def authenticate(action: RepositoryInfo => Any) = {
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
@@ -103,12 +103,12 @@ trait ReferrerAuthenticator { self: ControllerBase & RepositoryService & Account
|
||||
* Allows only signed in users who have read permission for the repository.
|
||||
*/
|
||||
trait ReadableUsersAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def readableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def readableUsersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => {
|
||||
authenticate(action(form, _))
|
||||
}
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
private def authenticate(action: RepositoryInfo => Any) = {
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
@@ -125,24 +125,19 @@ trait ReadableUsersAuthenticator { self: ControllerBase & RepositoryService & Ac
|
||||
* Allows only signed in users who have write permission for the repository.
|
||||
*/
|
||||
trait WritableUsersAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def writableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
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) = {
|
||||
private def authenticate(action: RepositoryInfo => Any) = {
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => action(repository)
|
||||
case Some(x) if (userName == x.userName) => action(repository)
|
||||
case Some(x) if (getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
|
||||
case Some(x)
|
||||
if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN, Role.DEVELOPER))
|
||||
.contains(x.userName)) =>
|
||||
action(repository)
|
||||
case _ => Unauthorized()
|
||||
if (isWritable(repository.repository, context.loginAccount)) {
|
||||
action(repository)
|
||||
} else {
|
||||
Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
@@ -159,9 +154,9 @@ trait GroupManagerAuthenticator { self: ControllerBase & AccountService =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if x.isAdmin => action
|
||||
case Some(x) if x.userName == request.paths(0) => action
|
||||
case Some(x) if (getGroupMembers(request.paths(0)).exists { member =>
|
||||
case Some(x) if getGroupMembers(request.paths(0)).exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}) =>
|
||||
} =>
|
||||
action
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
protection: gitbucket.core.api.ApiBranchProtection,
|
||||
protection: gitbucket.core.api.ApiBranchProtectionResponse,
|
||||
knownContexts: Seq[String],
|
||||
info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@@ -22,9 +22,44 @@
|
||||
</label>
|
||||
<p class="help-block">Disables force-pushes to this branch and prevents it from being deleted.</p>
|
||||
</div>
|
||||
|
||||
<!--====================================================================-->
|
||||
<!-- Enforce administrators -->
|
||||
<!--====================================================================-->
|
||||
<div class="checkbox js-enabled" style="display:none">
|
||||
<label>
|
||||
<input type="checkbox" name="has_required_statuses" onclick="update()" @check(protection.status.enforcement_level.name!="off") @if(knownContexts.isEmpty){disabled }>
|
||||
<input type="checkbox" name="enforce_for_admins" onclick="update()" @check(protection.enforce_admins.exists(_.enabled))>
|
||||
<span class="strong">Include administrators</span>
|
||||
</label>
|
||||
<p class="help-block">Enforce restrictions even for repository administrators.</p>
|
||||
</div>
|
||||
|
||||
<!--====================================================================-->
|
||||
<!-- Push restrictions -->
|
||||
<!--====================================================================-->
|
||||
<div class="checkbox js-enabled" style="display:none">
|
||||
<label>
|
||||
<input type="checkbox" name="restrictions" onclick="update()" @check(protection.restrictions.isDefined)>
|
||||
<span class="strong">Restrict users for push</span>
|
||||
</label>
|
||||
<p class="help-block">Restrict users who can push to this branch</p>
|
||||
<div class="js-restrictions_enabled" style="display: none;">
|
||||
<ul id="restrictions-user-list">
|
||||
</ul>
|
||||
@gitbucket.core.helper.html.account("userName-restrictions-user", 200, true, false)
|
||||
<input type="button" class="btn btn-default add-restrictions-user" value="Add"/>
|
||||
<div>
|
||||
<span class="error" id="error-restrictions-user"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--====================================================================-->
|
||||
<!-- Status check -->
|
||||
<!--====================================================================-->
|
||||
<div class="checkbox js-enabled" style="display:none">
|
||||
<label>
|
||||
<input type="checkbox" name="has_required_statuses" onclick="update()" @check(protection.required_status_checks.isDefined) @if(knownContexts.isEmpty){disabled }>
|
||||
<span class="strong">Require status checks to pass before merging</span>
|
||||
</label>
|
||||
<p class="help-block">When enabled, commits must first be pushed to another branch, then merged or pushed directly to <b>@branch</b> after status checks have passed.</p>
|
||||
@@ -48,14 +83,6 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="enforce_for_admins" onclick="update()" @check(protection.status.enforcement_level.name=="everyone")>
|
||||
<span class="strong">Include administrators</span>
|
||||
</label>
|
||||
<p class="help-block">Enforce required status checks for repository administrators.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -68,59 +95,114 @@
|
||||
}
|
||||
<script>
|
||||
function getValue(){
|
||||
var v = {}, contexts=[];
|
||||
const v = {}, contexts = [];
|
||||
let restrictions = undefined;
|
||||
$("input[type=checkbox]:checked").each(function(){
|
||||
if(this.name === 'contexts'){
|
||||
if(this.name === 'contexts') {
|
||||
contexts.push(this.value);
|
||||
} else if (this.name === 'restrictions') {
|
||||
restrictions = $('#restrictions-user-list li').map(function(i, e){
|
||||
return $(e).data('name');
|
||||
}).get();
|
||||
} else {
|
||||
v[this.name] = true;
|
||||
}
|
||||
});
|
||||
|
||||
if(v.enabled){
|
||||
return {
|
||||
enabled: true,
|
||||
required_status_checks: {
|
||||
enforcement_level: v.has_required_statuses ? ((v.enforce_for_admins ? 'everyone' : 'non_admins')) : 'off',
|
||||
contexts: v.has_required_statuses ? contexts : []
|
||||
}
|
||||
};
|
||||
enforce_admins: v.enforce_for_admins,
|
||||
required_status_checks: v.has_required_statuses ? { contexts: contexts } : undefined,
|
||||
restrictions: restrictions ? { users: restrictions } : undefined
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
enabled: false,
|
||||
required_status_checks: {
|
||||
enforcement_level: "off",
|
||||
contexts: []
|
||||
}
|
||||
enabled: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function updateView(protection){
|
||||
$('.js-enabled').toggle(protection.enabled);
|
||||
$('.js-has_required_statuses').toggle(protection.required_status_checks.enforcement_level != 'off');
|
||||
$('.js-submit-btn').attr('disabled',protection.required_status_checks.enforcement_level != 'off' && protection.required_status_checks.contexts.length == 0);
|
||||
$('.js-restrictions_enabled').toggle(protection.restrictions !== undefined);
|
||||
$('.js-has_required_statuses').toggle(protection.required_status_checks !== undefined);
|
||||
}
|
||||
|
||||
function update(){
|
||||
var protection = getValue();
|
||||
const protection = getValue();
|
||||
updateView(protection);
|
||||
}
|
||||
$(update);
|
||||
|
||||
function submitForm(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var protection = getValue();
|
||||
const protection = getValue();
|
||||
$.ajax({
|
||||
method:'PATCH',
|
||||
url:'@context.path/api/v3/repos/@repository.owner/@repository.name/branches/@helpers.urlEncode(branch)',
|
||||
method: 'PATCH',
|
||||
url: '@context.path/api/v3/repos/@repository.owner/@repository.name/branches/@helpers.urlEncode(branch)',
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data:JSON.stringify({protection:protection}),
|
||||
success:function(r){
|
||||
data: JSON.stringify({protection: protection}),
|
||||
success: function(r){
|
||||
$('#saved-info').show();
|
||||
},
|
||||
error:function(err){
|
||||
error: function(err){
|
||||
console.log(err);
|
||||
alert('update error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addUserToListHTML(userName, id){
|
||||
$(id).append($('<li>').data('name', userName)
|
||||
.append(' ')
|
||||
.append(userName)
|
||||
.append($('<a href="#" onclick="$(this).parent().remove();" class="remove">(remove)</a>')));
|
||||
}
|
||||
|
||||
$(function() {
|
||||
// Initialize
|
||||
update();
|
||||
|
||||
@protection.restrictions.map(_.users).map { users =>
|
||||
@users.map { user =>
|
||||
addUserToListHTML('@user', '#restrictions-user-list');
|
||||
}
|
||||
}
|
||||
|
||||
$('.add-restrictions-user').click(function(){
|
||||
$('#error-restrictions-user').text('');
|
||||
const userName = $('#userName-restrictions-user').val();
|
||||
|
||||
// check empty
|
||||
if($.trim(userName) === ''){
|
||||
return false;
|
||||
}
|
||||
|
||||
// check duplication
|
||||
const exists = $('#restrictions-user-list li').filter(function(){
|
||||
return $(this).data('name') === userName;
|
||||
}).length > 0;
|
||||
if(exists){
|
||||
$('#error-restrictions-user').text('User has been already added.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// check existence
|
||||
$.post('@context.path/_user/existence', {
|
||||
'userName': userName,
|
||||
'owner': '@repository.owner',
|
||||
'repository': '@repository.name'
|
||||
},
|
||||
function(data, status){
|
||||
if(data !== ''){
|
||||
addUserToListHTML(userName, '#restrictions-user-list');
|
||||
$('#userName-restrictions-user').val('');
|
||||
} else {
|
||||
$('#error-restrictions-user').text("User does not exist or isn't writable to this repository.");
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import java.util.{Calendar, Date, TimeZone}
|
||||
import gitbucket.core.api.ApiBranchProtectionResponse.Restrictions
|
||||
|
||||
import gitbucket.core.model._
|
||||
import java.util.{Calendar, Date, TimeZone}
|
||||
import gitbucket.core.model.*
|
||||
import gitbucket.core.plugin.PluginInfo
|
||||
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchInfo
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
@@ -15,7 +16,7 @@ object ApiSpecModels {
|
||||
|
||||
implicit val context: JsonFormat.Context = JsonFormat.Context("http://gitbucket.exmple.com", None)
|
||||
|
||||
val date1 = {
|
||||
val date1: Date = {
|
||||
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
d.set(2011, 3, 14, 16, 0, 49)
|
||||
d.getTime
|
||||
@@ -29,7 +30,7 @@ object ApiSpecModels {
|
||||
|
||||
// Models
|
||||
|
||||
val account = Account(
|
||||
val account: Account = Account(
|
||||
userName = "octocat",
|
||||
fullName = "octocat",
|
||||
mailAddress = "octocat@example.com",
|
||||
@@ -45,10 +46,10 @@ object ApiSpecModels {
|
||||
description = None
|
||||
)
|
||||
|
||||
val sha1 = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
val repo1Name = RepositoryName("octocat/Hello-World")
|
||||
val sha1: String = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
val repo1Name: RepositoryName = RepositoryName("octocat/Hello-World")
|
||||
|
||||
val repository = Repository(
|
||||
val repository: Repository = Repository(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
isPrivate = false,
|
||||
@@ -73,7 +74,7 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val repositoryInfo = RepositoryInfo(
|
||||
val repositoryInfo: RepositoryInfo = RepositoryInfo(
|
||||
owner = repo1Name.owner,
|
||||
name = repo1Name.name,
|
||||
repository = repository,
|
||||
@@ -101,7 +102,7 @@ object ApiSpecModels {
|
||||
managers = Seq("myboss")
|
||||
)
|
||||
|
||||
val label = Label(
|
||||
val label: Label = Label(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
labelId = 10,
|
||||
@@ -109,7 +110,7 @@ object ApiSpecModels {
|
||||
color = "f29513"
|
||||
)
|
||||
|
||||
val issue = Issue(
|
||||
val issue: Issue = Issue(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = 1347,
|
||||
@@ -124,14 +125,14 @@ object ApiSpecModels {
|
||||
isPullRequest = false
|
||||
)
|
||||
|
||||
val issuePR = issue.copy(
|
||||
val issuePR: Issue = issue.copy(
|
||||
title = "new-feature",
|
||||
content = Some("Please pull these awesome changes"),
|
||||
closed = true,
|
||||
isPullRequest = true
|
||||
)
|
||||
|
||||
val issueComment = IssueComment(
|
||||
val issueComment: IssueComment = IssueComment(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = issue.issueId,
|
||||
@@ -143,7 +144,7 @@ object ApiSpecModels {
|
||||
updatedDate = date1
|
||||
)
|
||||
|
||||
val pullRequest = PullRequest(
|
||||
val pullRequest: PullRequest = PullRequest(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = issuePR.issueId,
|
||||
@@ -156,7 +157,7 @@ object ApiSpecModels {
|
||||
isDraft = true
|
||||
)
|
||||
|
||||
val commitComment = CommitComment(
|
||||
val commitComment: CommitComment = CommitComment(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
commitId = sha1,
|
||||
@@ -174,7 +175,7 @@ object ApiSpecModels {
|
||||
originalNewLine = None
|
||||
)
|
||||
|
||||
val commitStatus = CommitStatus(
|
||||
val commitStatus: CommitStatus = CommitStatus(
|
||||
commitStatusId = 1,
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
@@ -188,7 +189,7 @@ object ApiSpecModels {
|
||||
updatedDate = date1
|
||||
)
|
||||
|
||||
val milestone = Milestone(
|
||||
val milestone: Milestone = Milestone(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
milestoneId = 1,
|
||||
@@ -200,28 +201,28 @@ object ApiSpecModels {
|
||||
|
||||
// APIs
|
||||
|
||||
val apiUser = ApiUser(account)
|
||||
val apiUser: ApiUser = ApiUser(account)
|
||||
|
||||
val apiRepository = ApiRepository(
|
||||
val apiRepository: ApiRepository = ApiRepository(
|
||||
repository = repository,
|
||||
owner = apiUser,
|
||||
forkedCount = repositoryInfo.forkedCount,
|
||||
watchers = 0
|
||||
)
|
||||
|
||||
val apiLabel = ApiLabel(
|
||||
val apiLabel: ApiLabel = ApiLabel(
|
||||
label = label,
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiMilestone = ApiMilestone(
|
||||
val apiMilestone: ApiMilestone = ApiMilestone(
|
||||
repository = repository,
|
||||
milestone = milestone,
|
||||
open_issue_count = 1,
|
||||
closed_issue_count = 1
|
||||
)
|
||||
|
||||
val apiIssue = ApiIssue(
|
||||
val apiIssue: ApiIssue = ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
@@ -230,7 +231,7 @@ object ApiSpecModels {
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiNotAssignedIssue = ApiIssue(
|
||||
val apiNotAssignedIssue: ApiIssue = ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
@@ -239,7 +240,7 @@ object ApiSpecModels {
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiIssuePR = ApiIssue(
|
||||
val apiIssuePR: ApiIssue = ApiIssue(
|
||||
issue = issuePR,
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
@@ -248,7 +249,7 @@ object ApiSpecModels {
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiComment = ApiComment(
|
||||
val apiComment: ApiComment = ApiComment(
|
||||
comment = issueComment,
|
||||
repositoryName = repo1Name,
|
||||
issueId = issueComment.issueId,
|
||||
@@ -256,7 +257,7 @@ object ApiSpecModels {
|
||||
isPullRequest = false
|
||||
)
|
||||
|
||||
val apiCommentPR = ApiComment(
|
||||
val apiCommentPR: ApiComment = ApiComment(
|
||||
comment = issueComment,
|
||||
repositoryName = repo1Name,
|
||||
issueId = issueComment.issueId,
|
||||
@@ -264,7 +265,7 @@ object ApiSpecModels {
|
||||
isPullRequest = true
|
||||
)
|
||||
|
||||
val apiPullRequest = ApiPullRequest(
|
||||
val apiPullRequest: ApiPullRequest = ApiPullRequest(
|
||||
issue = issuePR,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = apiRepository,
|
||||
@@ -275,15 +276,14 @@ object ApiSpecModels {
|
||||
mergedComment = Some((issueComment, account))
|
||||
)
|
||||
|
||||
// https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
|
||||
val apiPullRequestReviewComment = ApiPullRequestReviewComment(
|
||||
val apiPullRequestReviewComment: ApiPullRequestReviewComment = ApiPullRequestReviewComment(
|
||||
comment = commitComment,
|
||||
commentedUser = apiUser,
|
||||
repositoryName = repo1Name,
|
||||
issueId = commitComment.issueId.get
|
||||
)
|
||||
|
||||
val commitInfo = (id: String) =>
|
||||
val commitInfo: String => CommitInfo = (id: String) =>
|
||||
CommitInfo(
|
||||
id = id,
|
||||
shortMessage = "short message",
|
||||
@@ -299,12 +299,12 @@ object ApiSpecModels {
|
||||
None
|
||||
)
|
||||
|
||||
val apiCommitListItem = ApiCommitListItem(
|
||||
val apiCommitListItem: ApiCommitListItem = ApiCommitListItem(
|
||||
commit = commitInfo(sha1),
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiCommit = {
|
||||
val apiCommit: ApiCommit = {
|
||||
val commit = commitInfo(sha1)
|
||||
ApiCommit(
|
||||
id = commit.id,
|
||||
@@ -318,7 +318,7 @@ object ApiSpecModels {
|
||||
)(repo1Name)
|
||||
}
|
||||
|
||||
val apiCommits = ApiCommits(
|
||||
val apiCommits: ApiCommits = ApiCommits(
|
||||
repositoryName = repo1Name,
|
||||
commitInfo = commitInfo(sha1),
|
||||
diffs = Seq(
|
||||
@@ -348,42 +348,45 @@ object ApiSpecModels {
|
||||
commentCount = 2
|
||||
)
|
||||
|
||||
val apiCommitStatus = ApiCommitStatus(
|
||||
val apiCommitStatus: ApiCommitStatus = ApiCommitStatus(
|
||||
status = commitStatus,
|
||||
creator = apiUser
|
||||
)
|
||||
|
||||
val apiCombinedCommitStatus = ApiCombinedCommitStatus(
|
||||
val apiCombinedCommitStatus: ApiCombinedCommitStatus = ApiCombinedCommitStatus(
|
||||
sha = sha1,
|
||||
statuses = Iterable((commitStatus, account)),
|
||||
repository = apiRepository
|
||||
)
|
||||
|
||||
val apiBranchProtectionOutput = ApiBranchProtection(
|
||||
val apiBranchProtectionOutput: ApiBranchProtectionResponse = ApiBranchProtectionResponse(
|
||||
info = ProtectedBranchInfo(
|
||||
owner = repo1Name.owner,
|
||||
repository = repo1Name.name,
|
||||
branch = "main",
|
||||
enabled = true,
|
||||
contexts = Seq("continuous-integration/travis-ci"),
|
||||
includeAdministrators = true
|
||||
contexts = Some(Seq("continuous-integration/travis-ci")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = Some(Seq("admin"))
|
||||
)
|
||||
)
|
||||
|
||||
val apiBranchProtectionInput = new ApiBranchProtection(
|
||||
val apiBranchProtectionInput: ApiBranchProtectionResponse = new ApiBranchProtectionResponse(
|
||||
url = None,
|
||||
enabled = true,
|
||||
required_status_checks = Some(
|
||||
ApiBranchProtection.Status(
|
||||
ApiBranchProtectionResponse.Status(
|
||||
url = None,
|
||||
enforcement_level = ApiBranchProtection.Everyone,
|
||||
enforcement_level = ApiBranchProtectionResponse.Everyone,
|
||||
contexts = Seq("continuous-integration/travis-ci"),
|
||||
contexts_url = None
|
||||
)
|
||||
)
|
||||
),
|
||||
restrictions = Some(Restrictions(users = Seq("admin"))),
|
||||
enforce_admins = None
|
||||
)
|
||||
|
||||
val apiBranch = ApiBranch(
|
||||
val apiBranch: ApiBranch = ApiBranch(
|
||||
name = "main",
|
||||
commit = ApiBranchCommit(sha1),
|
||||
protection = apiBranchProtectionOutput
|
||||
@@ -391,12 +394,12 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiBranchForList = ApiBranchForList(
|
||||
val apiBranchForList: ApiBranchForList = ApiBranchForList(
|
||||
name = "main",
|
||||
commit = ApiBranchCommit(sha1)
|
||||
)
|
||||
|
||||
val apiContents = ApiContents(
|
||||
val apiContents: ApiContents = ApiContents(
|
||||
fileInfo = FileInfo(
|
||||
id = ObjectId.fromString(sha1),
|
||||
isDirectory = false,
|
||||
@@ -413,14 +416,14 @@ object ApiSpecModels {
|
||||
content = Some("README".getBytes("UTF-8"))
|
||||
)
|
||||
|
||||
val apiEndPoint = ApiEndPoint()
|
||||
val apiEndPoint: ApiEndPoint = ApiEndPoint()
|
||||
|
||||
val apiError = ApiError(
|
||||
val apiError: ApiError = ApiError(
|
||||
message = "A repository with this name already exists on this account",
|
||||
documentation_url = Some("https://developer.github.com/v3/repos/#create")
|
||||
)
|
||||
|
||||
val apiGroup = ApiGroup(
|
||||
val apiGroup: ApiGroup = ApiGroup(
|
||||
account.copy(
|
||||
isAdmin = true,
|
||||
isGroupAccount = true,
|
||||
@@ -428,7 +431,7 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val apiPlugin = ApiPlugin(
|
||||
val apiPlugin: ApiPlugin = ApiPlugin(
|
||||
plugin = PluginInfo(
|
||||
pluginId = "gist",
|
||||
pluginName = "Gist Plugin",
|
||||
@@ -441,12 +444,12 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val apiPusher = ApiPusher(account)
|
||||
val apiPusher: ApiPusher = ApiPusher(account)
|
||||
|
||||
// have both urls as https, as the expected samples are using https
|
||||
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
|
||||
val gitHubContext: JsonFormat.Context = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
|
||||
|
||||
val apiRefHeadsMaster = ApiRef(
|
||||
val apiRefHeadsMaster: ApiRef = ApiRef(
|
||||
ref = "refs/heads/main",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/main"),
|
||||
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
||||
@@ -457,7 +460,7 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val apiRefTag = ApiRef(
|
||||
val apiRefTag: ApiRef = ApiRef(
|
||||
ref = "refs/tags/1.0",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/tags/1.0"),
|
||||
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
|
||||
@@ -468,9 +471,9 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val assetFileName = "010203040a0b0c0d"
|
||||
val assetFileName: String = "010203040a0b0c0d"
|
||||
|
||||
val apiReleaseAsset = ApiReleaseAsset(
|
||||
val apiReleaseAsset: ApiReleaseAsset = ApiReleaseAsset(
|
||||
name = "release.zip",
|
||||
size = 100
|
||||
)(
|
||||
@@ -479,7 +482,7 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiRelease = ApiRelease(
|
||||
val apiRelease: ApiRelease = ApiRelease(
|
||||
name = "release1",
|
||||
tag_name = "tag1",
|
||||
body = Some("content"),
|
||||
@@ -489,7 +492,7 @@ object ApiSpecModels {
|
||||
|
||||
// JSON String for APIs
|
||||
|
||||
val jsonUser = """{
|
||||
val jsonUser: String = """{
|
||||
|"login":"octocat",
|
||||
|"email":"octocat@example.com",
|
||||
|"type":"User",
|
||||
@@ -501,7 +504,7 @@ object ApiSpecModels {
|
||||
|"avatar_url":"http://gitbucket.exmple.com/octocat/_avatar"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonRepository = s"""{
|
||||
val jsonRepository: String = s"""{
|
||||
|"name":"Hello-World",
|
||||
|"full_name":"octocat/Hello-World",
|
||||
|"description":"This your first repo!",
|
||||
@@ -519,10 +522,10 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonLabel =
|
||||
val jsonLabel: String =
|
||||
"""{"name":"bug","color":"f29513","url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/labels/bug"}"""
|
||||
|
||||
val jsonMilestone = """{
|
||||
val jsonMilestone: String = """{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/milestones/1",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/milestone/1",
|
||||
|"id":1,
|
||||
@@ -535,7 +538,7 @@ object ApiSpecModels {
|
||||
|"due_on":"2011-04-14T16:00:49Z"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonIssue = s"""{
|
||||
val jsonIssue: String = s"""{
|
||||
|"number":1347,
|
||||
|"title":"Found a bug",
|
||||
|"user":$jsonUser,
|
||||
@@ -552,7 +555,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonNotAssignedIssue = s"""{
|
||||
val jsonNotAssignedIssue: String = s"""{
|
||||
|"number":1347,
|
||||
|"title":"Found a bug",
|
||||
|"user":$jsonUser,
|
||||
@@ -568,7 +571,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonIssuePR = s"""{
|
||||
val jsonIssuePR: String = s"""{
|
||||
|"number":1347,
|
||||
|"title":"new-feature",
|
||||
|"user":$jsonUser,
|
||||
@@ -588,7 +591,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347"}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPullRequest = s"""{
|
||||
val jsonPullRequest: String = s"""{
|
||||
|"number":1347,
|
||||
|"state":"closed",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
@@ -615,7 +618,7 @@ object ApiSpecModels {
|
||||
|"statuses_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPullRequestReviewComment = s"""{
|
||||
val jsonPullRequestReviewComment: String = s"""{
|
||||
|"id":29724692,
|
||||
|"path":"README.md",
|
||||
|"commit_id":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
@@ -632,7 +635,7 @@ object ApiSpecModels {
|
||||
|"pull_request":{"href":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347"}}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonComment = s"""{
|
||||
val jsonComment: String = s"""{
|
||||
|"id":1,
|
||||
|"user":$jsonUser,
|
||||
|"body":"Me too",
|
||||
@@ -641,7 +644,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347#comment-1"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommentPR = s"""{
|
||||
val jsonCommentPR: String = s"""{
|
||||
|"id":1,
|
||||
|"user":$jsonUser,
|
||||
|"body":"Me too",
|
||||
@@ -650,7 +653,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347#comment-1"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommitListItem = s"""{
|
||||
val jsonCommitListItem: String = s"""{
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"commit":{
|
||||
|"message":"full message",
|
||||
@@ -664,7 +667,7 @@ object ApiSpecModels {
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommit = (id: String) => s"""{
|
||||
val jsonCommit: String => String = (id: String) => s"""{
|
||||
|"id":"$id",
|
||||
|"message":"full message",
|
||||
|"timestamp":"2011-04-14T16:00:49Z",
|
||||
@@ -677,7 +680,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/commit/$id"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommits = s"""{
|
||||
val jsonCommits: String = s"""{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
@@ -707,7 +710,7 @@ object ApiSpecModels {
|
||||
|"patch":"@@ -1 +1,2 @@\\n-body1\\n\\\\ No newline at end of file\\n+body1\\n+body2\\n\\\\ No newline at end of file"}]
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommitStatus = s"""{
|
||||
val jsonCommitStatus: String = s"""{
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"state":"success",
|
||||
@@ -719,7 +722,7 @@ object ApiSpecModels {
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/statuses"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCombinedCommitStatus = s"""{
|
||||
val jsonCombinedCommitStatus: String = s"""{
|
||||
|"state":"success",
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"total_count":1,
|
||||
@@ -728,7 +731,7 @@ object ApiSpecModels {
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/status"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchProtectionOutput =
|
||||
val jsonBranchProtectionOutput: String =
|
||||
"""{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection",
|
||||
|"enabled":true,
|
||||
@@ -736,15 +739,25 @@ object ApiSpecModels {
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks",
|
||||
|"enforcement_level":"everyone",
|
||||
|"contexts":["continuous-integration/travis-ci"],
|
||||
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks/contexts"}
|
||||
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks/contexts"
|
||||
|},
|
||||
|"restrictions":{
|
||||
|"users":["admin"]
|
||||
|},
|
||||
|"enforce_admins":{
|
||||
|"enabled":true
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchProtectionInput =
|
||||
val jsonBranchProtectionInput: String =
|
||||
"""{
|
||||
|"enabled":true,
|
||||
|"required_status_checks":{
|
||||
|"enforcement_level":"everyone",
|
||||
|"contexts":["continuous-integration/travis-ci"]
|
||||
|},
|
||||
|"restrictions":{
|
||||
|"users":["admin"]
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
@@ -757,9 +770,9 @@ object ApiSpecModels {
|
||||
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/main"}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchForList = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
val jsonBranchForList: String = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
|
||||
val jsonContents =
|
||||
val jsonContents: String =
|
||||
"""{
|
||||
|"type":"file",
|
||||
|"name":"README.md",
|
||||
@@ -770,14 +783,14 @@ object ApiSpecModels {
|
||||
|"download_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/doc/README.md"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonEndPoint = """{"rate_limit_url":"http://gitbucket.exmple.com/api/v3/rate_limit"}"""
|
||||
val jsonEndPoint: String = """{"rate_limit_url":"http://gitbucket.exmple.com/api/v3/rate_limit"}"""
|
||||
|
||||
val jsonError = """{
|
||||
val jsonError: String = """{
|
||||
|"message":"A repository with this name already exists on this account",
|
||||
|"documentation_url":"https://developer.github.com/v3/repos/#create"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonGroup = """{
|
||||
val jsonGroup: String = """{
|
||||
|"login":"octocat",
|
||||
|"description":"Admin group",
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
@@ -787,7 +800,7 @@ object ApiSpecModels {
|
||||
|"avatar_url":"http://gitbucket.exmple.com/octocat/_avatar"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPlugin = """{
|
||||
val jsonPlugin: String = """{
|
||||
|"id":"gist",
|
||||
|"name":"Gist Plugin",
|
||||
|"version":"4.16.0",
|
||||
@@ -795,12 +808,12 @@ object ApiSpecModels {
|
||||
|"jarFileName":"gitbucket-gist-plugin-gitbucket_4.30.0-SNAPSHOT-4.17.0.jar"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPusher = """{"name":"octocat","email":"octocat@example.com"}"""
|
||||
val jsonPusher: String = """{"name":"octocat","email":"octocat@example.com"}"""
|
||||
|
||||
// I checked all refs in gitbucket repo, and there appears to be only type "commit" and type "tag"
|
||||
val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
val jsonRef: String = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
|
||||
val jsonRefHeadsMain =
|
||||
val jsonRefHeadsMain: String =
|
||||
"""{
|
||||
|"ref": "refs/heads/main",
|
||||
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
||||
@@ -812,7 +825,7 @@ object ApiSpecModels {
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonRefTag =
|
||||
val jsonRefTag: String =
|
||||
"""{
|
||||
|"ref": "refs/tags/1.0",
|
||||
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
|
||||
@@ -824,7 +837,7 @@ object ApiSpecModels {
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonReleaseAsset =
|
||||
val jsonReleaseAsset: String =
|
||||
s"""{
|
||||
|"name":"release.zip",
|
||||
|"size":100,
|
||||
@@ -833,7 +846,7 @@ object ApiSpecModels {
|
||||
|"browser_download_url":"http://gitbucket.exmple.com/octocat/Hello-World/releases/tag1/assets/${assetFileName}"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonRelease =
|
||||
val jsonRelease: String =
|
||||
s"""{
|
||||
|"name":"release1",
|
||||
|"tag_name":"tag1",
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import org.json4s.Formats
|
||||
import org.json4s.{Formats, JValue, jvalue2extractable}
|
||||
import org.json4s.jackson.JsonMethods
|
||||
import org.json4s.jvalue2extractable
|
||||
import org.scalatest.Assertion
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
class JsonFormatSpec extends AnyFunSuite {
|
||||
import ApiSpecModels._
|
||||
import ApiSpecModels.*
|
||||
implicit val format: Formats = JsonFormat.jsonFormats
|
||||
|
||||
private def expected(json: String) = json.replaceAll("\n", "")
|
||||
def normalizeJson(json: String) = {
|
||||
def normalizeJson(json: String): JValue = {
|
||||
org.json4s.jackson.parseJson(json)
|
||||
}
|
||||
def assertEqualJson(actual: String, expected: String) = {
|
||||
def assertEqualJson(actual: String, expected: String): Assertion = {
|
||||
assert(normalizeJson(actual) == normalizeJson(expected))
|
||||
}
|
||||
|
||||
@@ -57,7 +57,9 @@ class JsonFormatSpec extends AnyFunSuite {
|
||||
assert(JsonFormat(apiBranchProtectionOutput) == expected(jsonBranchProtectionOutput))
|
||||
}
|
||||
test("deserialize apiBranchProtection") {
|
||||
assert(JsonMethods.parse(jsonBranchProtectionInput).extract[ApiBranchProtection] == apiBranchProtectionInput)
|
||||
assert(
|
||||
JsonMethods.parse(jsonBranchProtectionInput).extract[ApiBranchProtectionResponse] == apiBranchProtectionInput
|
||||
)
|
||||
}
|
||||
test("apiBranch") {
|
||||
assert(JsonFormat(apiBranch) == expected(jsonBranch))
|
||||
|
||||
@@ -29,26 +29,28 @@ class ProtectedBranchServiceSpec
|
||||
it("should enable and update and disable") {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
assert(
|
||||
getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
true,
|
||||
Nil,
|
||||
false
|
||||
None,
|
||||
false,
|
||||
None
|
||||
)
|
||||
)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Seq("hoge", "huge"))
|
||||
enableBranchProtection("user1", "repo1", "branch", true, true, Seq("hoge", "huge"), false, Nil)
|
||||
assert(
|
||||
getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
true,
|
||||
Seq("hoge", "huge"),
|
||||
true
|
||||
enabled = true,
|
||||
contexts = Some(Seq("hoge", "huge")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
)
|
||||
disableBranchProtection("user1", "repo1", "branch")
|
||||
@@ -57,21 +59,21 @@ class ProtectedBranchServiceSpec
|
||||
)
|
||||
}
|
||||
}
|
||||
it("should empty contexts is no-include-administrators") {
|
||||
it("should empty contexts is include-administrators") {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
assert(getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators == false)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Nil)
|
||||
assert(getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators == false)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
assert(!getProtectedBranchInfo("user1", "repo1", "branch").enforceAdmins)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, false, Nil, false, Nil)
|
||||
assert(getProtectedBranchInfo("user1", "repo1", "branch").enforceAdmins)
|
||||
}
|
||||
}
|
||||
it("getProtectedBranchList") {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
enableBranchProtection("user1", "repo1", "branch2", false, Seq("fuga"))
|
||||
enableBranchProtection("user1", "repo1", "branch3", true, Seq("hoge"))
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
enableBranchProtection("user1", "repo1", "branch2", false, false, Seq("fuga"), false, Nil)
|
||||
enableBranchProtection("user1", "repo1", "branch3", true, false, Seq("hoge"), false, Nil)
|
||||
assert(getProtectedBranchList("user1", "repo1").toSet == Set("branch", "branch2", "branch3"))
|
||||
}
|
||||
}
|
||||
@@ -87,12 +89,12 @@ class ProtectedBranchServiceSpec
|
||||
ReceiveCommand.Type.UPDATE_NONFASTFORWARD
|
||||
)
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"Cannot force-push to a protected branch"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("Cannot force-push to a protected branch")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -109,12 +111,12 @@ class ProtectedBranchServiceSpec
|
||||
ReceiveCommand.Type.UPDATE_NONFASTFORWARD
|
||||
)
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"Cannot force-push to a protected branch"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("Cannot force-push to a protected branch")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -131,33 +133,33 @@ class ProtectedBranchServiceSpec
|
||||
ReceiveCommand.Type.UPDATE
|
||||
)
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must"), false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"Required status check \"must\" is expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("Required status check \"must\" is expected")
|
||||
)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
|
||||
enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must", "must2"), false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"2 of 2 required status checks are expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("2 of 2 required status checks are expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"2 of 2 required status checks are expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("2 of 2 required status checks are expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"Required status check \"must2\" is expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("Required status check \"must2\" is expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == None)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,37 +175,60 @@ class ProtectedBranchServiceSpec
|
||||
ReceiveCommand.Type.UPDATE
|
||||
)
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Seq("must"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must"), false, Nil)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, true, Seq("must"), false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"Required status check \"must\" is expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("Required status check \"must\" is expected")
|
||||
)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Seq("must", "must2"))
|
||||
enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must", "must2"), false, Nil)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, true, Seq("must", "must2"), false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"2 of 2 required status checks are expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("2 of 2 required status checks are expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"2 of 2 required status checks are expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("2 of 2 required status checks are expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"Required status check \"must2\" is expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("Required status check \"must2\" is expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
it("should restrict push to allowed users only") {
|
||||
withTestDB { implicit session =>
|
||||
withTestRepository { git =>
|
||||
val rp = new ReceivePack(git.getRepository)
|
||||
rp.setAllowNonFastForwards(true)
|
||||
val rc = new ReceiveCommand(
|
||||
ObjectId.fromString(sha),
|
||||
ObjectId.fromString(sha2),
|
||||
"refs/heads/branch",
|
||||
ReceiveCommand.Type.UPDATE
|
||||
)
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
generateNewAccount("user2")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, true, Seq("user2"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
|
||||
assert(
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user3", false)
|
||||
.contains("You do not have permission to push to this branch")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,29 +237,53 @@ class ProtectedBranchServiceSpec
|
||||
it("administrator is owner") {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
val x = ProtectedBranchInfo("user1", "repo1", "branch", true, Nil, false)
|
||||
assert(x.isAdministrator("user1") == true)
|
||||
assert(x.isAdministrator("user2") == false)
|
||||
val x = ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Nil),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
assert(x.isAdministrator("user1"))
|
||||
assert(!x.isAdministrator("user2"))
|
||||
}
|
||||
}
|
||||
it("administrator is manager") {
|
||||
withTestDB { implicit session =>
|
||||
val x = ProtectedBranchInfo("grp1", "repo1", "branch", true, Nil, false)
|
||||
val x = ProtectedBranchInfo(
|
||||
"grp1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Nil),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
x.createGroup("grp1", None, None)
|
||||
generateNewAccount("user1")
|
||||
generateNewAccount("user2")
|
||||
generateNewAccount("user3")
|
||||
|
||||
x.updateGroupMembers("grp1", List("user1" -> true, "user2" -> false))
|
||||
assert(x.isAdministrator("user1") == true)
|
||||
assert(x.isAdministrator("user2") == false)
|
||||
assert(x.isAdministrator("user3") == false)
|
||||
assert(x.isAdministrator("user1"))
|
||||
assert(!x.isAdministrator("user2"))
|
||||
assert(!x.isAdministrator("user3"))
|
||||
}
|
||||
}
|
||||
it("unSuccessedContexts") {
|
||||
withTestDB { implicit session =>
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
val x = ProtectedBranchInfo("user1", "repo1", "branch", true, List("must"), false)
|
||||
val x = ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(List("must")),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
assert(x.unSuccessedContexts(sha) == Set("must"))
|
||||
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(x.unSuccessedContexts(sha) == Set("must"))
|
||||
@@ -251,7 +300,15 @@ class ProtectedBranchServiceSpec
|
||||
it("unSuccessedContexts when empty") {
|
||||
withTestDB { implicit session =>
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
val x = ProtectedBranchInfo("user1", "repo1", "branch", true, Nil, false)
|
||||
val x = ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Nil),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
val sha = "0c77148632618b59b6f70004e3084002be2b8804"
|
||||
assert(x.unSuccessedContexts(sha) == Set())
|
||||
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
@@ -261,23 +318,63 @@ class ProtectedBranchServiceSpec
|
||||
it("if disabled, needStatusCheck is false") {
|
||||
withTestDB { implicit session =>
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", false, Seq("must"), true).needStatusCheck("user1") == false
|
||||
!ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = false,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user1")
|
||||
)
|
||||
}
|
||||
}
|
||||
it("needStatusCheck includeAdministrators") {
|
||||
withTestDB { implicit session =>
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), false).needStatusCheck("user2") == true
|
||||
ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user2")
|
||||
)
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), false).needStatusCheck("user1") == false
|
||||
!ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user1")
|
||||
)
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), true).needStatusCheck("user2") == true
|
||||
ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user2")
|
||||
)
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), true).needStatusCheck("user1") == true
|
||||
ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user1")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class RepositoryServiceSpec extends AnyFunSuite with ServiceSpecBase with Reposi
|
||||
now = new java.util.Date
|
||||
)
|
||||
|
||||
service.enableBranchProtection("root", "repo", "branch", true, Seq("must1", "must2"))
|
||||
service.enableBranchProtection("root", "repo", "branch", true, true, Seq("must1", "must2"), false, Nil)
|
||||
|
||||
val orgPbi = service.getProtectedBranchInfo("root", "repo", "branch")
|
||||
val org = service.getCommitStatus("root", "repo", id).get
|
||||
|
||||
Reference in New Issue
Block a user