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  }