code.gitea.io/gitea@v1.21.7/models/perm/access/repo_permission.go (about) 1 // Copyright 2018 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package access 5 6 import ( 7 "context" 8 "fmt" 9 10 "code.gitea.io/gitea/models/db" 11 "code.gitea.io/gitea/models/organization" 12 perm_model "code.gitea.io/gitea/models/perm" 13 repo_model "code.gitea.io/gitea/models/repo" 14 "code.gitea.io/gitea/models/unit" 15 user_model "code.gitea.io/gitea/models/user" 16 "code.gitea.io/gitea/modules/log" 17 ) 18 19 // Permission contains all the permissions related variables to a repository for a user 20 type Permission struct { 21 AccessMode perm_model.AccessMode 22 Units []*repo_model.RepoUnit 23 UnitsMode map[unit.Type]perm_model.AccessMode 24 } 25 26 // IsOwner returns true if current user is the owner of repository. 27 func (p *Permission) IsOwner() bool { 28 return p.AccessMode >= perm_model.AccessModeOwner 29 } 30 31 // IsAdmin returns true if current user has admin or higher access of repository. 32 func (p *Permission) IsAdmin() bool { 33 return p.AccessMode >= perm_model.AccessModeAdmin 34 } 35 36 // HasAccess returns true if the current user has at least read access to any unit of this repository 37 func (p *Permission) HasAccess() bool { 38 if p.UnitsMode == nil { 39 return p.AccessMode >= perm_model.AccessModeRead 40 } 41 return len(p.UnitsMode) > 0 42 } 43 44 // UnitAccessMode returns current user accessmode to the specify unit of the repository 45 func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode { 46 if p.UnitsMode == nil { 47 for _, u := range p.Units { 48 if u.Type == unitType { 49 return p.AccessMode 50 } 51 } 52 return perm_model.AccessModeNone 53 } 54 return p.UnitsMode[unitType] 55 } 56 57 // CanAccess returns true if user has mode access to the unit of the repository 58 func (p *Permission) CanAccess(mode perm_model.AccessMode, unitType unit.Type) bool { 59 return p.UnitAccessMode(unitType) >= mode 60 } 61 62 // CanAccessAny returns true if user has mode access to any of the units of the repository 63 func (p *Permission) CanAccessAny(mode perm_model.AccessMode, unitTypes ...unit.Type) bool { 64 for _, u := range unitTypes { 65 if p.CanAccess(mode, u) { 66 return true 67 } 68 } 69 return false 70 } 71 72 // CanRead returns true if user could read to this unit 73 func (p *Permission) CanRead(unitType unit.Type) bool { 74 return p.CanAccess(perm_model.AccessModeRead, unitType) 75 } 76 77 // CanReadAny returns true if user has read access to any of the units of the repository 78 func (p *Permission) CanReadAny(unitTypes ...unit.Type) bool { 79 return p.CanAccessAny(perm_model.AccessModeRead, unitTypes...) 80 } 81 82 // CanReadIssuesOrPulls returns true if isPull is true and user could read pull requests and 83 // returns true if isPull is false and user could read to issues 84 func (p *Permission) CanReadIssuesOrPulls(isPull bool) bool { 85 if isPull { 86 return p.CanRead(unit.TypePullRequests) 87 } 88 return p.CanRead(unit.TypeIssues) 89 } 90 91 // CanWrite returns true if user could write to this unit 92 func (p *Permission) CanWrite(unitType unit.Type) bool { 93 return p.CanAccess(perm_model.AccessModeWrite, unitType) 94 } 95 96 // CanWriteIssuesOrPulls returns true if isPull is true and user could write to pull requests and 97 // returns true if isPull is false and user could write to issues 98 func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool { 99 if isPull { 100 return p.CanWrite(unit.TypePullRequests) 101 } 102 return p.CanWrite(unit.TypeIssues) 103 } 104 105 func (p *Permission) LogString() string { 106 format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ " 107 args := []any{p.AccessMode.String(), len(p.Units), len(p.UnitsMode)} 108 109 for i, unit := range p.Units { 110 config := "" 111 if unit.Config != nil { 112 configBytes, err := unit.Config.ToDB() 113 config = string(configBytes) 114 if err != nil { 115 config = err.Error() 116 } 117 } 118 format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s" 119 args = append(args, i, unit.ID, unit.RepoID, unit.Type.LogString(), config) 120 } 121 for key, value := range p.UnitsMode { 122 format += "\nUnitMode[%-v]: %-v" 123 args = append(args, key.LogString(), value.LogString()) 124 } 125 format += " ]>" 126 return fmt.Sprintf(format, args...) 127 } 128 129 // GetUserRepoPermission returns the user permissions to the repository 130 func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (Permission, error) { 131 var perm Permission 132 if log.IsTrace() { 133 defer func() { 134 if user == nil { 135 log.Trace("Permission Loaded for anonymous user in %-v:\nPermissions: %-+v", 136 repo, 137 perm) 138 return 139 } 140 log.Trace("Permission Loaded for %-v in %-v:\nPermissions: %-+v", 141 user, 142 repo, 143 perm) 144 }() 145 } 146 147 // anonymous user visit private repo. 148 // TODO: anonymous user visit public unit of private repo??? 149 if user == nil && repo.IsPrivate { 150 perm.AccessMode = perm_model.AccessModeNone 151 return perm, nil 152 } 153 154 var isCollaborator bool 155 var err error 156 if user != nil { 157 isCollaborator, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID) 158 if err != nil { 159 return perm, err 160 } 161 } 162 163 if err := repo.LoadOwner(ctx); err != nil { 164 return perm, err 165 } 166 167 // Prevent strangers from checking out public repo of private organization/users 168 // Allow user if they are collaborator of a repo within a private user or a private organization but not a member of the organization itself 169 if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !isCollaborator { 170 perm.AccessMode = perm_model.AccessModeNone 171 return perm, nil 172 } 173 174 if err := repo.LoadUnits(ctx); err != nil { 175 return perm, err 176 } 177 178 perm.Units = repo.Units 179 180 // anonymous visit public repo 181 if user == nil { 182 perm.AccessMode = perm_model.AccessModeRead 183 return perm, nil 184 } 185 186 // Admin or the owner has super access to the repository 187 if user.IsAdmin || user.ID == repo.OwnerID { 188 perm.AccessMode = perm_model.AccessModeOwner 189 return perm, nil 190 } 191 192 // plain user 193 perm.AccessMode, err = accessLevel(ctx, user, repo) 194 if err != nil { 195 return perm, err 196 } 197 198 if err := repo.LoadOwner(ctx); err != nil { 199 return perm, err 200 } 201 if !repo.Owner.IsOrganization() { 202 return perm, nil 203 } 204 205 perm.UnitsMode = make(map[unit.Type]perm_model.AccessMode) 206 207 // Collaborators on organization 208 if isCollaborator { 209 for _, u := range repo.Units { 210 perm.UnitsMode[u.Type] = perm.AccessMode 211 } 212 } 213 214 // get units mode from teams 215 teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID) 216 if err != nil { 217 return perm, err 218 } 219 220 // if user in an owner team 221 for _, team := range teams { 222 if team.AccessMode >= perm_model.AccessModeAdmin { 223 perm.AccessMode = perm_model.AccessModeOwner 224 perm.UnitsMode = nil 225 return perm, nil 226 } 227 } 228 229 for _, u := range repo.Units { 230 var found bool 231 for _, team := range teams { 232 teamMode := team.UnitAccessMode(ctx, u.Type) 233 if teamMode > perm_model.AccessModeNone { 234 m := perm.UnitsMode[u.Type] 235 if m < teamMode { 236 perm.UnitsMode[u.Type] = teamMode 237 } 238 found = true 239 } 240 } 241 242 // for a public repo on an organization, a non-restricted user has read permission on non-team defined units. 243 if !found && !repo.IsPrivate && !user.IsRestricted { 244 if _, ok := perm.UnitsMode[u.Type]; !ok { 245 perm.UnitsMode[u.Type] = perm_model.AccessModeRead 246 } 247 } 248 } 249 250 // remove no permission units 251 perm.Units = make([]*repo_model.RepoUnit, 0, len(repo.Units)) 252 for t := range perm.UnitsMode { 253 for _, u := range repo.Units { 254 if u.Type == t { 255 perm.Units = append(perm.Units, u) 256 } 257 } 258 } 259 260 return perm, err 261 } 262 263 // IsUserRealRepoAdmin check if this user is real repo admin 264 func IsUserRealRepoAdmin(repo *repo_model.Repository, user *user_model.User) (bool, error) { 265 if repo.OwnerID == user.ID { 266 return true, nil 267 } 268 269 if err := repo.LoadOwner(db.DefaultContext); err != nil { 270 return false, err 271 } 272 273 accessMode, err := accessLevel(db.DefaultContext, user, repo) 274 if err != nil { 275 return false, err 276 } 277 278 return accessMode >= perm_model.AccessModeAdmin, nil 279 } 280 281 // IsUserRepoAdmin return true if user has admin right of a repo 282 func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) { 283 if user == nil || repo == nil { 284 return false, nil 285 } 286 if user.IsAdmin { 287 return true, nil 288 } 289 290 mode, err := accessLevel(ctx, user, repo) 291 if err != nil { 292 return false, err 293 } 294 if mode >= perm_model.AccessModeAdmin { 295 return true, nil 296 } 297 298 teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID) 299 if err != nil { 300 return false, err 301 } 302 303 for _, team := range teams { 304 if team.AccessMode >= perm_model.AccessModeAdmin { 305 return true, nil 306 } 307 } 308 return false, nil 309 } 310 311 // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the 312 // user does not have access. 313 func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) { //nolint 314 return AccessLevelUnit(ctx, user, repo, unit.TypeCode) 315 } 316 317 // AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the 318 // user does not have access. 319 func AccessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint 320 perm, err := GetUserRepoPermission(ctx, repo, user) 321 if err != nil { 322 return perm_model.AccessModeNone, err 323 } 324 return perm.UnitAccessMode(unitType), nil 325 } 326 327 // HasAccessUnit returns true if user has testMode to the unit of the repository 328 func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type, testMode perm_model.AccessMode) (bool, error) { 329 mode, err := AccessLevelUnit(ctx, user, repo, unitType) 330 return testMode <= mode, err 331 } 332 333 // CanBeAssigned return true if user can be assigned to issue or pull requests in repo 334 // Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface. 335 func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) { 336 if user.IsOrganization() { 337 return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID) 338 } 339 perm, err := GetUserRepoPermission(ctx, repo, user) 340 if err != nil { 341 return false, err 342 } 343 return perm.CanAccessAny(perm_model.AccessModeWrite, unit.AllRepoUnitTypes...) || 344 perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil 345 } 346 347 // HasAccess returns true if user has access to repo 348 func HasAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) { 349 var user *user_model.User 350 var err error 351 if userID > 0 { 352 user, err = user_model.GetUserByID(ctx, userID) 353 if err != nil { 354 return false, err 355 } 356 } 357 perm, err := GetUserRepoPermission(ctx, repo, user) 358 if err != nil { 359 return false, err 360 } 361 return perm.HasAccess(), nil 362 } 363 364 // getUsersWithAccessMode returns users that have at least given access mode to the repository. 365 func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) { 366 if err = repo.LoadOwner(ctx); err != nil { 367 return nil, err 368 } 369 370 e := db.GetEngine(ctx) 371 accesses := make([]*Access, 0, 10) 372 if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil { 373 return nil, err 374 } 375 376 // Leave a seat for owner itself to append later, but if owner is an organization 377 // and just waste 1 unit is cheaper than re-allocate memory once. 378 users := make([]*user_model.User, 0, len(accesses)+1) 379 if len(accesses) > 0 { 380 userIDs := make([]int64, len(accesses)) 381 for i := 0; i < len(accesses); i++ { 382 userIDs[i] = accesses[i].UserID 383 } 384 385 if err = e.In("id", userIDs).Find(&users); err != nil { 386 return nil, err 387 } 388 } 389 if !repo.Owner.IsOrganization() { 390 users = append(users, repo.Owner) 391 } 392 393 return users, nil 394 } 395 396 // GetRepoReaders returns all users that have explicit read access or higher to the repository. 397 func GetRepoReaders(repo *repo_model.Repository) (_ []*user_model.User, err error) { 398 return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeRead) 399 } 400 401 // GetRepoWriters returns all users that have write access to the repository. 402 func GetRepoWriters(repo *repo_model.Repository) (_ []*user_model.User, err error) { 403 return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeWrite) 404 } 405 406 // IsRepoReader returns true if user has explicit read access or higher to the repository. 407 func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) { 408 if repo.OwnerID == userID { 409 return true, nil 410 } 411 return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{}) 412 } 413 414 // CheckRepoUnitUser check whether user could visit the unit of this repository 415 func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool { 416 if user != nil && user.IsAdmin { 417 return true 418 } 419 perm, err := GetUserRepoPermission(ctx, repo, user) 420 if err != nil { 421 log.Error("GetUserRepoPermission: %w", err) 422 return false 423 } 424 425 return perm.CanRead(unitType) 426 }