code.gitea.io/gitea@v1.22.3/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 var teamU []*TeamUnit 266 var unitEnabled bool 267 err = sess.Where("team_id = ?", team.ID).Find(&teamU) 268 269 for _, tu := range teamU { 270 if tu.Type == u.Type { 271 unitEnabled = true 272 break 273 } 274 } 275 276 if unitEnabled { 277 m := perm.UnitsMode[u.Type] 278 if m < team.Authorize { 279 perm.UnitsMode[u.Type] = team.Authorize 280 } 281 found = true 282 } 283 } 284 285 // for a public repo on an organization, a non-restricted user has read permission on non-team defined units. 286 if !found && !repo.IsPrivate && !user.IsRestricted { 287 if _, ok := perm.UnitsMode[u.Type]; !ok { 288 perm.UnitsMode[u.Type] = AccessModeRead 289 } 290 } 291 } 292 293 // remove no permission units 294 perm.Units = make([]*RepoUnit, 0, len(units)) 295 for t := range perm.UnitsMode { 296 for _, u := range units { 297 if u.Type == t { 298 perm.Units = append(perm.Units, u) 299 } 300 } 301 } 302 303 return perm, err 304 } 305 306 // isOfficialReviewer static function based on 5d78792385 307 isOfficialReviewer := func(sess *xorm.Session, issueID int64, reviewer *User) (bool, error) { 308 pr := new(PullRequest) 309 has, err := sess.ID(issueID).Get(pr) 310 if err != nil { 311 return false, err 312 } else if !has { 313 return false, fmt.Errorf("PullRequest for issueID %d not exist", issueID) 314 } 315 316 baseRepo := new(Repository) 317 has, err = sess.ID(pr.BaseRepoID).Get(baseRepo) 318 if err != nil { 319 return false, err 320 } else if !has { 321 return false, fmt.Errorf("baseRepo with id %d not exist", pr.BaseRepoID) 322 } 323 protectedBranch := new(ProtectedBranch) 324 has, err = sess.Where("repo_id=? AND branch_name=?", baseRepo.ID, pr.BaseBranch).Get(protectedBranch) 325 if err != nil { 326 return false, err 327 } 328 if !has { 329 return false, nil 330 } 331 332 if !protectedBranch.EnableApprovalsWhitelist { 333 perm, err := getUserRepoPermission(sess, baseRepo, reviewer) 334 if err != nil { 335 return false, err 336 } 337 if len(perm.UnitsMode) == 0 { 338 for _, u := range perm.Units { 339 if u.Type == UnitTypeCode { 340 return AccessModeWrite <= perm.AccessMode, nil 341 } 342 } 343 return false, nil 344 } 345 return AccessModeWrite <= perm.UnitsMode[UnitTypeCode], nil 346 } 347 for _, id := range protectedBranch.ApprovalsWhitelistUserIDs { 348 if id == reviewer.ID { 349 return true, nil 350 } 351 } 352 353 // isUserInTeams 354 return sess.Where("uid=?", reviewer.ID).In("team_id", protectedBranch.ApprovalsWhitelistTeamIDs).Exist(new(TeamUser)) 355 } 356 357 if _, err := x.Exec("UPDATE `protected_branch` SET `enable_whitelist` = ? WHERE enable_whitelist IS NULL", false); err != nil { 358 return err 359 } 360 if _, err := x.Exec("UPDATE `protected_branch` SET `can_push` = `enable_whitelist`"); err != nil { 361 return err 362 } 363 if _, err := x.Exec("UPDATE `protected_branch` SET `enable_approvals_whitelist` = ? WHERE `required_approvals` > ?", true, 0); err != nil { 364 return err 365 } 366 367 var pageSize int64 = 20 368 qresult, err := x.QueryInterface("SELECT max(id) as max_id FROM issue") 369 if err != nil { 370 return err 371 } 372 var totalIssues int64 373 totalIssues, ok := qresult[0]["max_id"].(int64) 374 if !ok { 375 // If there are no issues at all we ignore it 376 return nil 377 } 378 totalPages := totalIssues / pageSize 379 380 executeBody := func(page, pageSize int64) error { 381 // Find latest review of each user in each pull request, and set official field if appropriate 382 reviews := []*Review{} 383 384 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)", 385 page*pageSize, (page+1)*pageSize, ReviewTypeApprove, ReviewTypeReject). 386 Find(&reviews); err != nil { 387 return err 388 } 389 390 if len(reviews) == 0 { 391 return nil 392 } 393 394 sess := x.NewSession() 395 defer sess.Close() 396 397 if err := sess.Begin(); err != nil { 398 return err 399 } 400 401 var updated int 402 for _, review := range reviews { 403 reviewer := new(User) 404 has, err := sess.ID(review.ReviewerID).Get(reviewer) 405 if err != nil || !has { 406 // Error might occur if user doesn't exist, ignore it. 407 continue 408 } 409 410 official, err := isOfficialReviewer(sess, review.IssueID, reviewer) 411 if err != nil { 412 // Branch might not be proteced or other error, ignore it. 413 continue 414 } 415 review.Official = official 416 updated++ 417 if _, err := sess.ID(review.ID).Cols("official").Update(review); err != nil { 418 return err 419 } 420 } 421 422 if updated > 0 { 423 return sess.Commit() 424 } 425 return nil 426 } 427 428 var page int64 429 for page = 0; page <= totalPages; page++ { 430 if err := executeBody(page, pageSize); err != nil { 431 return err 432 } 433 } 434 435 return nil 436 }