code.gitea.io/gitea@v1.21.7/models/migrations/v1_11/v111.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package v1_11 //nolint 5 6 import ( 7 "fmt" 8 9 "xorm.io/xorm" 10 ) 11 12 func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error { 13 type ProtectedBranch struct { 14 CanPush bool `xorm:"NOT NULL DEFAULT false"` 15 EnableApprovalsWhitelist bool `xorm:"NOT NULL DEFAULT false"` 16 ApprovalsWhitelistUserIDs []int64 `xorm:"JSON TEXT"` 17 ApprovalsWhitelistTeamIDs []int64 `xorm:"JSON TEXT"` 18 RequiredApprovals int64 `xorm:"NOT NULL DEFAULT 0"` 19 } 20 21 type User struct { 22 ID int64 `xorm:"pk autoincr"` 23 Type int 24 25 // Permissions 26 IsAdmin bool 27 IsRestricted bool `xorm:"NOT NULL DEFAULT false"` 28 Visibility int `xorm:"NOT NULL DEFAULT 0"` 29 } 30 31 type Review struct { 32 ID int64 `xorm:"pk autoincr"` 33 Official bool `xorm:"NOT NULL DEFAULT false"` 34 35 ReviewerID int64 `xorm:"index"` 36 IssueID int64 `xorm:"index"` 37 } 38 39 if err := x.Sync(new(ProtectedBranch)); err != nil { 40 return err 41 } 42 43 if err := x.Sync(new(Review)); err != nil { 44 return err 45 } 46 47 const ( 48 // ReviewTypeApprove approves changes 49 ReviewTypeApprove int = 1 50 // ReviewTypeReject gives feedback blocking merge 51 ReviewTypeReject int = 3 52 53 // VisibleTypePublic Visible for everyone 54 VisibleTypePublic int = 0 55 // VisibleTypePrivate Visible only for organization's members 56 VisibleTypePrivate int = 2 57 58 // unit.UnitTypeCode is unit type code 59 UnitTypeCode int = 1 60 61 // AccessModeNone no access 62 AccessModeNone int = 0 63 // AccessModeRead read access 64 AccessModeRead int = 1 65 // AccessModeWrite write access 66 AccessModeWrite int = 2 67 // AccessModeOwner owner access 68 AccessModeOwner int = 4 69 ) 70 71 // Repository represents a git repository. 72 type Repository struct { 73 ID int64 `xorm:"pk autoincr"` 74 OwnerID int64 `xorm:"UNIQUE(s) index"` 75 76 IsPrivate bool `xorm:"INDEX"` 77 } 78 79 type PullRequest struct { 80 ID int64 `xorm:"pk autoincr"` 81 82 BaseRepoID int64 `xorm:"INDEX"` 83 BaseBranch string 84 } 85 86 // RepoUnit describes all units of a repository 87 type RepoUnit struct { 88 ID int64 89 RepoID int64 `xorm:"INDEX(s)"` 90 Type int `xorm:"INDEX(s)"` 91 } 92 93 type Permission struct { 94 AccessMode int 95 Units []*RepoUnit 96 UnitsMode map[int]int 97 } 98 99 type TeamUser struct { 100 ID int64 `xorm:"pk autoincr"` 101 TeamID int64 `xorm:"UNIQUE(s)"` 102 UID int64 `xorm:"UNIQUE(s)"` 103 } 104 105 type Collaboration struct { 106 ID int64 `xorm:"pk autoincr"` 107 RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` 108 UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` 109 Mode int `xorm:"DEFAULT 2 NOT NULL"` 110 } 111 112 type Access struct { 113 ID int64 `xorm:"pk autoincr"` 114 UserID int64 `xorm:"UNIQUE(s)"` 115 RepoID int64 `xorm:"UNIQUE(s)"` 116 Mode int 117 } 118 119 type TeamUnit struct { 120 ID int64 `xorm:"pk autoincr"` 121 OrgID int64 `xorm:"INDEX"` 122 TeamID int64 `xorm:"UNIQUE(s)"` 123 Type int `xorm:"UNIQUE(s)"` 124 } 125 126 // Team represents a organization team. 127 type Team struct { 128 ID int64 `xorm:"pk autoincr"` 129 OrgID int64 `xorm:"INDEX"` 130 Authorize int 131 } 132 133 // getUserRepoPermission static function based on issues_model.IsOfficialReviewer at 5d78792385 134 getUserRepoPermission := func(sess *xorm.Session, repo *Repository, user *User) (Permission, error) { 135 var perm Permission 136 137 repoOwner := new(User) 138 has, err := sess.ID(repo.OwnerID).Get(repoOwner) 139 if err != nil || !has { 140 return perm, err 141 } 142 143 // Prevent strangers from checking out public repo of private organization 144 // Allow user if they are collaborator of a repo within a private organization but not a member of the organization itself 145 hasOrgVisible := true 146 // Not SignedUser 147 if user == nil { 148 hasOrgVisible = repoOwner.Visibility == VisibleTypePublic 149 } else if !user.IsAdmin { 150 hasMemberWithUserID, err := sess. 151 Where("uid=?", user.ID). 152 And("org_id=?", repoOwner.ID). 153 Table("org_user"). 154 Exist() 155 if err != nil { 156 hasOrgVisible = false 157 } 158 if (repoOwner.Visibility == VisibleTypePrivate || user.IsRestricted) && !hasMemberWithUserID { 159 hasOrgVisible = false 160 } 161 } 162 163 isCollaborator, err := sess.Get(&Collaboration{RepoID: repo.ID, UserID: user.ID}) 164 if err != nil { 165 return perm, err 166 } 167 168 if repoOwner.Type == 1 && !hasOrgVisible && !isCollaborator { 169 perm.AccessMode = AccessModeNone 170 return perm, err 171 } 172 173 var units []*RepoUnit 174 if err := sess.Where("repo_id = ?", repo.ID).Find(&units); err != nil { 175 return perm, err 176 } 177 perm.Units = units 178 179 // anonymous visit public repo 180 if user == nil { 181 perm.AccessMode = AccessModeRead 182 return perm, err 183 } 184 185 // Admin or the owner has super access to the repository 186 if user.IsAdmin || user.ID == repo.OwnerID { 187 perm.AccessMode = AccessModeOwner 188 return perm, err 189 } 190 191 accessLevel := func(user *User, repo *Repository) (int, error) { 192 mode := AccessModeNone 193 var userID int64 194 restricted := false 195 196 if user != nil { 197 userID = user.ID 198 restricted = user.IsRestricted 199 } 200 201 if !restricted && !repo.IsPrivate { 202 mode = AccessModeRead 203 } 204 205 if userID == 0 { 206 return mode, nil 207 } 208 209 if userID == repo.OwnerID { 210 return AccessModeOwner, nil 211 } 212 213 a := &Access{UserID: userID, RepoID: repo.ID} 214 if has, err := sess.Get(a); !has || err != nil { 215 return mode, err 216 } 217 return a.Mode, nil 218 } 219 220 // plain user 221 perm.AccessMode, err = accessLevel(user, repo) 222 if err != nil { 223 return perm, err 224 } 225 226 // If Owner is no Org 227 if repoOwner.Type != 1 { 228 return perm, err 229 } 230 231 perm.UnitsMode = make(map[int]int) 232 233 // Collaborators on organization 234 if isCollaborator { 235 for _, u := range units { 236 perm.UnitsMode[u.Type] = perm.AccessMode 237 } 238 } 239 240 // get units mode from teams 241 var teams []*Team 242 err = sess. 243 Join("INNER", "team_user", "team_user.team_id = team.id"). 244 Join("INNER", "team_repo", "team_repo.team_id = team.id"). 245 Where("team.org_id = ?", repo.OwnerID). 246 And("team_user.uid=?", user.ID). 247 And("team_repo.repo_id=?", repo.ID). 248 Find(&teams) 249 if err != nil { 250 return perm, err 251 } 252 253 // if user in an owner team 254 for _, team := range teams { 255 if team.Authorize >= AccessModeOwner { 256 perm.AccessMode = AccessModeOwner 257 perm.UnitsMode = nil 258 return perm, err 259 } 260 } 261 262 for _, u := range units { 263 var found bool 264 for _, team := range teams { 265 266 var teamU []*TeamUnit 267 var unitEnabled bool 268 err = sess.Where("team_id = ?", team.ID).Find(&teamU) 269 270 for _, tu := range teamU { 271 if tu.Type == u.Type { 272 unitEnabled = true 273 break 274 } 275 } 276 277 if unitEnabled { 278 m := perm.UnitsMode[u.Type] 279 if m < team.Authorize { 280 perm.UnitsMode[u.Type] = team.Authorize 281 } 282 found = true 283 } 284 } 285 286 // for a public repo on an organization, a non-restricted user has read permission on non-team defined units. 287 if !found && !repo.IsPrivate && !user.IsRestricted { 288 if _, ok := perm.UnitsMode[u.Type]; !ok { 289 perm.UnitsMode[u.Type] = AccessModeRead 290 } 291 } 292 } 293 294 // remove no permission units 295 perm.Units = make([]*RepoUnit, 0, len(units)) 296 for t := range perm.UnitsMode { 297 for _, u := range units { 298 if u.Type == t { 299 perm.Units = append(perm.Units, u) 300 } 301 } 302 } 303 304 return perm, err 305 } 306 307 // isOfficialReviewer static function based on 5d78792385 308 isOfficialReviewer := func(sess *xorm.Session, issueID int64, reviewer *User) (bool, error) { 309 pr := new(PullRequest) 310 has, err := sess.ID(issueID).Get(pr) 311 if err != nil { 312 return false, err 313 } else if !has { 314 return false, fmt.Errorf("PullRequest for issueID %d not exist", issueID) 315 } 316 317 baseRepo := new(Repository) 318 has, err = sess.ID(pr.BaseRepoID).Get(baseRepo) 319 if err != nil { 320 return false, err 321 } else if !has { 322 return false, fmt.Errorf("baseRepo with id %d not exist", pr.BaseRepoID) 323 } 324 protectedBranch := new(ProtectedBranch) 325 has, err = sess.Where("repo_id=? AND branch_name=?", baseRepo.ID, pr.BaseBranch).Get(protectedBranch) 326 if err != nil { 327 return false, err 328 } 329 if !has { 330 return false, nil 331 } 332 333 if !protectedBranch.EnableApprovalsWhitelist { 334 335 perm, err := getUserRepoPermission(sess, baseRepo, reviewer) 336 if err != nil { 337 return false, err 338 } 339 if perm.UnitsMode == nil { 340 for _, u := range perm.Units { 341 if u.Type == UnitTypeCode { 342 return AccessModeWrite <= perm.AccessMode, nil 343 } 344 } 345 return false, nil 346 } 347 return AccessModeWrite <= perm.UnitsMode[UnitTypeCode], nil 348 } 349 for _, id := range protectedBranch.ApprovalsWhitelistUserIDs { 350 if id == reviewer.ID { 351 return true, nil 352 } 353 } 354 355 // isUserInTeams 356 return sess.Where("uid=?", reviewer.ID).In("team_id", protectedBranch.ApprovalsWhitelistTeamIDs).Exist(new(TeamUser)) 357 } 358 359 if _, err := x.Exec("UPDATE `protected_branch` SET `enable_whitelist` = ? WHERE enable_whitelist IS NULL", false); err != nil { 360 return err 361 } 362 if _, err := x.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil { 363 return err 364 } 365 if _, err := x.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0); err != nil { 366 return err 367 } 368 369 var pageSize int64 = 20 370 qresult, err := x.QueryInterface("SELECT max(id) as max_id FROM issue") 371 if err != nil { 372 return err 373 } 374 var totalIssues int64 375 totalIssues, ok := qresult[0]["max_id"].(int64) 376 if !ok { 377 // If there are no issues at all we ignore it 378 return nil 379 } 380 totalPages := totalIssues / pageSize 381 382 executeBody := func(page, pageSize int64) error { 383 // Find latest review of each user in each pull request, and set official field if appropriate 384 reviews := []*Review{} 385 386 if err := x.SQL("SELECT * FROM review WHERE id IN (SELECT max(id) as id FROM review WHERE issue_id > ? AND issue_id <= ? AND type in (?, ?) GROUP BY issue_id, reviewer_id)", 387 page*pageSize, (page+1)*pageSize, ReviewTypeApprove, ReviewTypeReject). 388 Find(&reviews); err != nil { 389 return err 390 } 391 392 if len(reviews) == 0 { 393 return nil 394 } 395 396 sess := x.NewSession() 397 defer sess.Close() 398 399 if err := sess.Begin(); err != nil { 400 return err 401 } 402 403 var updated int 404 for _, review := range reviews { 405 reviewer := new(User) 406 has, err := sess.ID(review.ReviewerID).Get(reviewer) 407 if err != nil || !has { 408 // Error might occur if user doesn't exist, ignore it. 409 continue 410 } 411 412 official, err := isOfficialReviewer(sess, review.IssueID, reviewer) 413 if err != nil { 414 // Branch might not be proteced or other error, ignore it. 415 continue 416 } 417 review.Official = official 418 updated++ 419 if _, err := sess.ID(review.ID).Cols("official").Update(review); err != nil { 420 return err 421 } 422 } 423 424 if updated > 0 { 425 return sess.Commit() 426 } 427 return nil 428 } 429 430 var page int64 431 for page = 0; page <= totalPages; page++ { 432 if err := executeBody(page, pageSize); err != nil { 433 return err 434 } 435 } 436 437 return nil 438 }