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