code.gitea.io/gitea@v1.22.3/models/unit/unit.go (about)

     1  // Copyright 2017 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package unit
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"strings"
    10  	"sync/atomic"
    11  
    12  	"code.gitea.io/gitea/models/perm"
    13  	"code.gitea.io/gitea/modules/container"
    14  	"code.gitea.io/gitea/modules/log"
    15  	"code.gitea.io/gitea/modules/setting"
    16  )
    17  
    18  // Type is Unit's Type
    19  type Type int
    20  
    21  // Enumerate all the unit types
    22  const (
    23  	TypeInvalid         Type = iota // 0 invalid
    24  	TypeCode                        // 1 code
    25  	TypeIssues                      // 2 issues
    26  	TypePullRequests                // 3 PRs
    27  	TypeReleases                    // 4 Releases
    28  	TypeWiki                        // 5 Wiki
    29  	TypeExternalWiki                // 6 ExternalWiki
    30  	TypeExternalTracker             // 7 ExternalTracker
    31  	TypeProjects                    // 8 Kanban board
    32  	TypePackages                    // 9 Packages
    33  	TypeActions                     // 10 Actions
    34  )
    35  
    36  // Value returns integer value for unit type
    37  func (u Type) Value() int {
    38  	return int(u)
    39  }
    40  
    41  func (u Type) String() string {
    42  	switch u {
    43  	case TypeCode:
    44  		return "TypeCode"
    45  	case TypeIssues:
    46  		return "TypeIssues"
    47  	case TypePullRequests:
    48  		return "TypePullRequests"
    49  	case TypeReleases:
    50  		return "TypeReleases"
    51  	case TypeWiki:
    52  		return "TypeWiki"
    53  	case TypeExternalWiki:
    54  		return "TypeExternalWiki"
    55  	case TypeExternalTracker:
    56  		return "TypeExternalTracker"
    57  	case TypeProjects:
    58  		return "TypeProjects"
    59  	case TypePackages:
    60  		return "TypePackages"
    61  	case TypeActions:
    62  		return "TypeActions"
    63  	}
    64  	return fmt.Sprintf("Unknown Type %d", u)
    65  }
    66  
    67  func (u Type) LogString() string {
    68  	return fmt.Sprintf("<UnitType:%d:%s>", u, u.String())
    69  }
    70  
    71  var (
    72  	// AllRepoUnitTypes contains all the unit types
    73  	AllRepoUnitTypes = []Type{
    74  		TypeCode,
    75  		TypeIssues,
    76  		TypePullRequests,
    77  		TypeReleases,
    78  		TypeWiki,
    79  		TypeExternalWiki,
    80  		TypeExternalTracker,
    81  		TypeProjects,
    82  		TypePackages,
    83  		TypeActions,
    84  	}
    85  
    86  	// DefaultRepoUnits contains the default unit types
    87  	DefaultRepoUnits = []Type{
    88  		TypeCode,
    89  		TypeIssues,
    90  		TypePullRequests,
    91  		TypeReleases,
    92  		TypeWiki,
    93  		TypeProjects,
    94  		TypePackages,
    95  		TypeActions,
    96  	}
    97  
    98  	// ForkRepoUnits contains the default unit types for forks
    99  	DefaultForkRepoUnits = []Type{
   100  		TypeCode,
   101  		TypePullRequests,
   102  	}
   103  
   104  	// NotAllowedDefaultRepoUnits contains units that can't be default
   105  	NotAllowedDefaultRepoUnits = []Type{
   106  		TypeExternalWiki,
   107  		TypeExternalTracker,
   108  	}
   109  
   110  	disabledRepoUnitsAtomic atomic.Pointer[[]Type] // the units that have been globally disabled
   111  )
   112  
   113  // DisabledRepoUnitsGet returns the globally disabled units, it is a quick patch to fix data-race during testing.
   114  // Because the queue worker might read when a test is mocking the value. FIXME: refactor to a clear solution later.
   115  func DisabledRepoUnitsGet() []Type {
   116  	v := disabledRepoUnitsAtomic.Load()
   117  	if v == nil {
   118  		return nil
   119  	}
   120  	return *v
   121  }
   122  
   123  func DisabledRepoUnitsSet(v []Type) {
   124  	disabledRepoUnitsAtomic.Store(&v)
   125  }
   126  
   127  // Get valid set of default repository units from settings
   128  func validateDefaultRepoUnits(defaultUnits, settingDefaultUnits []Type) []Type {
   129  	units := defaultUnits
   130  
   131  	// Use setting if not empty
   132  	if len(settingDefaultUnits) > 0 {
   133  		units = make([]Type, 0, len(settingDefaultUnits))
   134  		for _, settingUnit := range settingDefaultUnits {
   135  			if !settingUnit.CanBeDefault() {
   136  				log.Warn("Not allowed as default unit: %s", settingUnit.String())
   137  				continue
   138  			}
   139  			units = append(units, settingUnit)
   140  		}
   141  	}
   142  
   143  	// Remove disabled units
   144  	for _, disabledUnit := range DisabledRepoUnitsGet() {
   145  		for i, unit := range units {
   146  			if unit == disabledUnit {
   147  				units = append(units[:i], units[i+1:]...)
   148  			}
   149  		}
   150  	}
   151  
   152  	return units
   153  }
   154  
   155  // LoadUnitConfig load units from settings
   156  func LoadUnitConfig() error {
   157  	disabledRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DisabledRepoUnits...)
   158  	if len(invalidKeys) > 0 {
   159  		log.Warn("Invalid keys in disabled repo units: %s", strings.Join(invalidKeys, ", "))
   160  	}
   161  	DisabledRepoUnitsSet(disabledRepoUnits)
   162  
   163  	setDefaultRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultRepoUnits...)
   164  	if len(invalidKeys) > 0 {
   165  		log.Warn("Invalid keys in default repo units: %s", strings.Join(invalidKeys, ", "))
   166  	}
   167  	DefaultRepoUnits = validateDefaultRepoUnits(DefaultRepoUnits, setDefaultRepoUnits)
   168  	if len(DefaultRepoUnits) == 0 {
   169  		return errors.New("no default repository units found")
   170  	}
   171  	setDefaultForkRepoUnits, invalidKeys := FindUnitTypes(setting.Repository.DefaultForkRepoUnits...)
   172  	if len(invalidKeys) > 0 {
   173  		log.Warn("Invalid keys in default fork repo units: %s", strings.Join(invalidKeys, ", "))
   174  	}
   175  	DefaultForkRepoUnits = validateDefaultRepoUnits(DefaultForkRepoUnits, setDefaultForkRepoUnits)
   176  	if len(DefaultForkRepoUnits) == 0 {
   177  		return errors.New("no default fork repository units found")
   178  	}
   179  	return nil
   180  }
   181  
   182  // UnitGlobalDisabled checks if unit type is global disabled
   183  func (u Type) UnitGlobalDisabled() bool {
   184  	for _, ud := range DisabledRepoUnitsGet() {
   185  		if u == ud {
   186  			return true
   187  		}
   188  	}
   189  	return false
   190  }
   191  
   192  // CanBeDefault checks if the unit type can be a default repo unit
   193  func (u *Type) CanBeDefault() bool {
   194  	for _, nadU := range NotAllowedDefaultRepoUnits {
   195  		if *u == nadU {
   196  			return false
   197  		}
   198  	}
   199  	return true
   200  }
   201  
   202  // Unit is a section of one repository
   203  type Unit struct {
   204  	Type          Type
   205  	NameKey       string
   206  	URI           string
   207  	DescKey       string
   208  	Priority      int
   209  	MaxAccessMode perm.AccessMode // The max access mode of the unit. i.e. Read means this unit can only be read.
   210  }
   211  
   212  // IsLessThan compares order of two units
   213  func (u Unit) IsLessThan(unit Unit) bool {
   214  	return u.Priority < unit.Priority
   215  }
   216  
   217  // MaxPerm returns the max perms of this unit
   218  func (u Unit) MaxPerm() perm.AccessMode {
   219  	if u.Type == TypeExternalTracker || u.Type == TypeExternalWiki {
   220  		return perm.AccessModeRead
   221  	}
   222  	return perm.AccessModeAdmin
   223  }
   224  
   225  // Enumerate all the units
   226  var (
   227  	UnitCode = Unit{
   228  		TypeCode,
   229  		"repo.code",
   230  		"/",
   231  		"repo.code.desc",
   232  		0,
   233  		perm.AccessModeOwner,
   234  	}
   235  
   236  	UnitIssues = Unit{
   237  		TypeIssues,
   238  		"repo.issues",
   239  		"/issues",
   240  		"repo.issues.desc",
   241  		1,
   242  		perm.AccessModeOwner,
   243  	}
   244  
   245  	UnitExternalTracker = Unit{
   246  		TypeExternalTracker,
   247  		"repo.ext_issues",
   248  		"/issues",
   249  		"repo.ext_issues.desc",
   250  		101,
   251  		perm.AccessModeRead,
   252  	}
   253  
   254  	UnitPullRequests = Unit{
   255  		TypePullRequests,
   256  		"repo.pulls",
   257  		"/pulls",
   258  		"repo.pulls.desc",
   259  		2,
   260  		perm.AccessModeOwner,
   261  	}
   262  
   263  	UnitReleases = Unit{
   264  		TypeReleases,
   265  		"repo.releases",
   266  		"/releases",
   267  		"repo.releases.desc",
   268  		3,
   269  		perm.AccessModeOwner,
   270  	}
   271  
   272  	UnitWiki = Unit{
   273  		TypeWiki,
   274  		"repo.wiki",
   275  		"/wiki",
   276  		"repo.wiki.desc",
   277  		4,
   278  		perm.AccessModeOwner,
   279  	}
   280  
   281  	UnitExternalWiki = Unit{
   282  		TypeExternalWiki,
   283  		"repo.ext_wiki",
   284  		"/wiki",
   285  		"repo.ext_wiki.desc",
   286  		102,
   287  		perm.AccessModeRead,
   288  	}
   289  
   290  	UnitProjects = Unit{
   291  		TypeProjects,
   292  		"repo.projects",
   293  		"/projects",
   294  		"repo.projects.desc",
   295  		5,
   296  		perm.AccessModeOwner,
   297  	}
   298  
   299  	UnitPackages = Unit{
   300  		TypePackages,
   301  		"repo.packages",
   302  		"/packages",
   303  		"packages.desc",
   304  		6,
   305  		perm.AccessModeRead,
   306  	}
   307  
   308  	UnitActions = Unit{
   309  		TypeActions,
   310  		"repo.actions",
   311  		"/actions",
   312  		"actions.unit.desc",
   313  		7,
   314  		perm.AccessModeOwner,
   315  	}
   316  
   317  	// Units contains all the units
   318  	Units = map[Type]Unit{
   319  		TypeCode:            UnitCode,
   320  		TypeIssues:          UnitIssues,
   321  		TypeExternalTracker: UnitExternalTracker,
   322  		TypePullRequests:    UnitPullRequests,
   323  		TypeReleases:        UnitReleases,
   324  		TypeWiki:            UnitWiki,
   325  		TypeExternalWiki:    UnitExternalWiki,
   326  		TypeProjects:        UnitProjects,
   327  		TypePackages:        UnitPackages,
   328  		TypeActions:         UnitActions,
   329  	}
   330  )
   331  
   332  // FindUnitTypes give the unit key names and return valid unique units and invalid keys
   333  func FindUnitTypes(nameKeys ...string) (res []Type, invalidKeys []string) {
   334  	m := make(container.Set[Type])
   335  	for _, key := range nameKeys {
   336  		t := TypeFromKey(key)
   337  		if t == TypeInvalid {
   338  			invalidKeys = append(invalidKeys, key)
   339  		} else if m.Add(t) {
   340  			res = append(res, t)
   341  		}
   342  	}
   343  	return res, invalidKeys
   344  }
   345  
   346  // TypeFromKey give the unit key name and return unit
   347  func TypeFromKey(nameKey string) Type {
   348  	for t, u := range Units {
   349  		if strings.EqualFold(nameKey, u.NameKey) {
   350  			return t
   351  		}
   352  	}
   353  	return TypeInvalid
   354  }
   355  
   356  // AllUnitKeyNames returns all unit key names
   357  func AllUnitKeyNames() []string {
   358  	res := make([]string, 0, len(Units))
   359  	for _, u := range Units {
   360  		res = append(res, u.NameKey)
   361  	}
   362  	return res
   363  }
   364  
   365  // MinUnitAccessMode returns the minial permission of the permission map
   366  func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode {
   367  	res := perm.AccessModeNone
   368  	for t, mode := range unitsMap {
   369  		// Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
   370  		if t == TypeExternalTracker || t == TypeExternalWiki {
   371  			continue
   372  		}
   373  
   374  		// get the minial permission great than AccessModeNone except all are AccessModeNone
   375  		if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
   376  			res = mode
   377  		}
   378  	}
   379  	return res
   380  }