Skip to content

Commit e273817

Browse files
authored
[API] Fix inconsistent label color format (#10129)
* update and use labelColorPattern * add TestCases * fix lint * # optional for templates * fix typo * some more * fix lint of **master**
1 parent 74a4a1e commit e273817

File tree

9 files changed

+158
-42
lines changed

9 files changed

+158
-42
lines changed

integrations/api_issue_label_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package integrations
77
import (
88
"fmt"
99
"net/http"
10+
"strings"
1011
"testing"
1112

1213
"code.gitea.io/gitea/models"
@@ -15,6 +16,76 @@ import (
1516
"github.com/stretchr/testify/assert"
1617
)
1718

19+
func TestAPIModifyLabels(t *testing.T) {
20+
assert.NoError(t, models.LoadFixtures())
21+
22+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 2}).(*models.Repository)
23+
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
24+
session := loginUser(t, owner.Name)
25+
token := getTokenForLoggedInUser(t, session)
26+
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels?token=%s", owner.Name, repo.Name, token)
27+
28+
// CreateLabel
29+
req := NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{
30+
Name: "TestL 1",
31+
Color: "abcdef",
32+
Description: "test label",
33+
})
34+
resp := session.MakeRequest(t, req, http.StatusCreated)
35+
apiLabel := new(api.Label)
36+
DecodeJSON(t, resp, &apiLabel)
37+
dbLabel := models.AssertExistsAndLoadBean(t, &models.Label{ID: apiLabel.ID, RepoID: repo.ID}).(*models.Label)
38+
assert.EqualValues(t, dbLabel.Name, apiLabel.Name)
39+
assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color)
40+
41+
req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{
42+
Name: "TestL 2",
43+
Color: "#123456",
44+
Description: "jet another test label",
45+
})
46+
session.MakeRequest(t, req, http.StatusCreated)
47+
req = NewRequestWithJSON(t, "POST", urlStr, &api.CreateLabelOption{
48+
Name: "WrongTestL",
49+
Color: "#12345g",
50+
})
51+
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
52+
53+
//ListLabels
54+
req = NewRequest(t, "GET", urlStr)
55+
resp = session.MakeRequest(t, req, http.StatusOK)
56+
var apiLabels []*api.Label
57+
DecodeJSON(t, resp, &apiLabels)
58+
assert.Len(t, apiLabels, 2)
59+
60+
//GetLabel
61+
singleURLStr := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d?token=%s", owner.Name, repo.Name, dbLabel.ID, token)
62+
req = NewRequest(t, "GET", singleURLStr)
63+
resp = session.MakeRequest(t, req, http.StatusOK)
64+
DecodeJSON(t, resp, &apiLabel)
65+
assert.EqualValues(t, strings.TrimLeft(dbLabel.Color, "#"), apiLabel.Color)
66+
67+
//EditLabel
68+
newName := "LabelNewName"
69+
newColor := "09876a"
70+
newColorWrong := "09g76a"
71+
req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{
72+
Name: &newName,
73+
Color: &newColor,
74+
})
75+
resp = session.MakeRequest(t, req, http.StatusOK)
76+
DecodeJSON(t, resp, &apiLabel)
77+
assert.EqualValues(t, newColor, apiLabel.Color)
78+
req = NewRequestWithJSON(t, "PATCH", singleURLStr, &api.EditLabelOption{
79+
Color: &newColorWrong,
80+
})
81+
session.MakeRequest(t, req, http.StatusUnprocessableEntity)
82+
83+
//DeleteLabel
84+
req = NewRequest(t, "DELETE", singleURLStr)
85+
resp = session.MakeRequest(t, req, http.StatusNoContent)
86+
87+
}
88+
1889
func TestAPIAddIssueLabels(t *testing.T) {
1990
assert.NoError(t, models.LoadFixtures())
2091

models/issue_label.go

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,34 @@ import (
1818
"xorm.io/xorm"
1919
)
2020

21-
var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})")
21+
// LabelColorPattern is a regexp witch can validate LabelColor
22+
var LabelColorPattern = regexp.MustCompile("^#[0-9a-fA-F]{6}$")
23+
24+
// Label represents a label of repository for issues.
25+
type Label struct {
26+
ID int64 `xorm:"pk autoincr"`
27+
RepoID int64 `xorm:"INDEX"`
28+
Name string
29+
Description string
30+
Color string `xorm:"VARCHAR(7)"`
31+
NumIssues int
32+
NumClosedIssues int
33+
NumOpenIssues int `xorm:"-"`
34+
IsChecked bool `xorm:"-"`
35+
QueryString string `xorm:"-"`
36+
IsSelected bool `xorm:"-"`
37+
IsExcluded bool `xorm:"-"`
38+
}
39+
40+
// APIFormat converts a Label to the api.Label format
41+
func (label *Label) APIFormat() *api.Label {
42+
return &api.Label{
43+
ID: label.ID,
44+
Name: label.Name,
45+
Color: strings.TrimLeft(label.Color, "#"),
46+
Description: label.Description,
47+
}
48+
}
2249

2350
// GetLabelTemplateFile loads the label template file by given name,
2451
// then parses and returns a list of name-color pairs and optionally description.
@@ -43,7 +70,11 @@ func GetLabelTemplateFile(name string) ([][3]string, error) {
4370
return nil, fmt.Errorf("line is malformed: %s", line)
4471
}
4572

46-
if !labelColorPattern.MatchString(fields[0]) {
73+
color := strings.Trim(fields[0], " ")
74+
if len(color) == 6 {
75+
color = "#" + color
76+
}
77+
if !LabelColorPattern.MatchString(color) {
4778
return nil, fmt.Errorf("bad HTML color code in line: %s", line)
4879
}
4980

@@ -54,38 +85,12 @@ func GetLabelTemplateFile(name string) ([][3]string, error) {
5485
}
5586

5687
fields[1] = strings.TrimSpace(fields[1])
57-
list = append(list, [3]string{fields[1], fields[0], description})
88+
list = append(list, [3]string{fields[1], color, description})
5889
}
5990

6091
return list, nil
6192
}
6293

63-
// Label represents a label of repository for issues.
64-
type Label struct {
65-
ID int64 `xorm:"pk autoincr"`
66-
RepoID int64 `xorm:"INDEX"`
67-
Name string
68-
Description string
69-
Color string `xorm:"VARCHAR(7)"`
70-
NumIssues int
71-
NumClosedIssues int
72-
NumOpenIssues int `xorm:"-"`
73-
IsChecked bool `xorm:"-"`
74-
QueryString string `xorm:"-"`
75-
IsSelected bool `xorm:"-"`
76-
IsExcluded bool `xorm:"-"`
77-
}
78-
79-
// APIFormat converts a Label to the api.Label format
80-
func (label *Label) APIFormat() *api.Label {
81-
return &api.Label{
82-
ID: label.ID,
83-
Name: label.Name,
84-
Color: strings.TrimLeft(label.Color, "#"),
85-
Description: label.Description,
86-
}
87-
}
88-
8994
// CalOpenIssues calculates the open issues of label.
9095
func (label *Label) CalOpenIssues() {
9196
label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
@@ -152,7 +157,7 @@ func LoadLabelsFormatted(labelTemplate string) (string, error) {
152157
return strings.Join(labels, ", "), err
153158
}
154159

155-
func initalizeLabels(e Engine, repoID int64, labelTemplate string) error {
160+
func initializeLabels(e Engine, repoID int64, labelTemplate string) error {
156161
list, err := GetLabelTemplateFile(labelTemplate)
157162
if err != nil {
158163
return ErrIssueLabelTemplateLoad{labelTemplate, err}
@@ -175,9 +180,9 @@ func initalizeLabels(e Engine, repoID int64, labelTemplate string) error {
175180
return nil
176181
}
177182

178-
// InitalizeLabels adds a label set to a repository using a template
179-
func InitalizeLabels(ctx DBContext, repoID int64, labelTemplate string) error {
180-
return initalizeLabels(ctx.e, repoID, labelTemplate)
183+
// InitializeLabels adds a label set to a repository using a template
184+
func InitializeLabels(ctx DBContext, repoID int64, labelTemplate string) error {
185+
return initializeLabels(ctx.e, repoID, labelTemplate)
181186
}
182187

183188
func newLabel(e Engine, label *Label) error {
@@ -187,6 +192,9 @@ func newLabel(e Engine, label *Label) error {
187192

188193
// NewLabel creates a new label for a repository
189194
func NewLabel(label *Label) error {
195+
if !LabelColorPattern.MatchString(label.Color) {
196+
return fmt.Errorf("bad color code: %s", label.Color)
197+
}
190198
return newLabel(x, label)
191199
}
192200

@@ -198,6 +206,9 @@ func NewLabels(labels ...*Label) error {
198206
return err
199207
}
200208
for _, label := range labels {
209+
if !LabelColorPattern.MatchString(label.Color) {
210+
return fmt.Errorf("bad color code: %s", label.Color)
211+
}
201212
if err := newLabel(sess, label); err != nil {
202213
return err
203214
}
@@ -359,6 +370,9 @@ func updateLabel(e Engine, l *Label) error {
359370

360371
// UpdateLabel updates label information.
361372
func UpdateLabel(l *Label) error {
373+
if !LabelColorPattern.MatchString(l.Color) {
374+
return fmt.Errorf("bad color code: %s", l.Color)
375+
}
362376
return updateLabel(x, l)
363377
}
364378

models/issue_label_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,11 @@ func TestNewLabels(t *testing.T) {
4545
assert.NoError(t, PrepareTestDatabase())
4646
labels := []*Label{
4747
{RepoID: 2, Name: "labelName2", Color: "#123456"},
48-
{RepoID: 3, Name: "labelName3", Color: "#234567"},
48+
{RepoID: 3, Name: "labelName3", Color: "#23456F"},
4949
}
50+
assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: ""}))
51+
assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "123456"}))
52+
assert.Error(t, NewLabel(&Label{RepoID: 3, Name: "invalid Color", Color: "#12345G"}))
5053
for _, label := range labels {
5154
AssertNotExistsBean(t, label)
5255
}

models/user.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1044,7 +1044,7 @@ func ChangeUserName(u *User, newUserName string) (err error) {
10441044
} else if isExist {
10451045
return ErrUserAlreadyExist{newUserName}
10461046
}
1047-
1047+
10481048
sess := x.NewSession()
10491049
defer sess.Close()
10501050
if err = sess.Begin(); err != nil {

modules/repository/create.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m
5858

5959
// Initialize Issue Labels if selected
6060
if len(opts.IssueLabels) > 0 {
61-
if err = models.InitalizeLabels(ctx, repo.ID, opts.IssueLabels); err != nil {
62-
return fmt.Errorf("initalizeLabels: %v", err)
61+
if err = models.InitializeLabels(ctx, repo.ID, opts.IssueLabels); err != nil {
62+
return fmt.Errorf("InitializeLabels: %v", err)
6363
}
6464
}
6565

6666
if stdout, err := git.NewCommand("update-server-info").
6767
SetDescription(fmt.Sprintf("CreateRepository(git update-server-info): %s", repoPath)).
6868
RunInDir(repoPath); err != nil {
69-
log.Error("CreateRepitory(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
69+
log.Error("CreateRepository(git update-server-info) in %v: Stdout: %s\nError: %v", repo, stdout, err)
7070
return fmt.Errorf("CreateRepository(git update-server-info): %v", err)
7171
}
7272
}

modules/structs/issue_label.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type CreateLabelOption struct {
2222
Name string `json:"name" binding:"Required"`
2323
// required:true
2424
// example: #00aabb
25-
Color string `json:"color" binding:"Required;Size(7)"`
25+
Color string `json:"color" binding:"Required"`
2626
Description string `json:"description"`
2727
}
2828

routers/api/v1/repo/label.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
package repo
77

88
import (
9+
"fmt"
910
"net/http"
1011
"strconv"
12+
"strings"
1113

1214
"code.gitea.io/gitea/models"
1315
"code.gitea.io/gitea/modules/context"
@@ -135,6 +137,17 @@ func CreateLabel(ctx *context.APIContext, form api.CreateLabelOption) {
135137
// responses:
136138
// "201":
137139
// "$ref": "#/responses/Label"
140+
// "422":
141+
// "$ref": "#/responses/validationError"
142+
143+
form.Color = strings.Trim(form.Color, " ")
144+
if len(form.Color) == 6 {
145+
form.Color = "#" + form.Color
146+
}
147+
if !models.LabelColorPattern.MatchString(form.Color) {
148+
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color))
149+
return
150+
}
138151

139152
label := &models.Label{
140153
Name: form.Name,
@@ -182,6 +195,8 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) {
182195
// responses:
183196
// "200":
184197
// "$ref": "#/responses/Label"
198+
// "422":
199+
// "$ref": "#/responses/validationError"
185200

186201
label, err := models.GetLabelInRepoByID(ctx.Repo.Repository.ID, ctx.ParamsInt64(":id"))
187202
if err != nil {
@@ -197,7 +212,14 @@ func EditLabel(ctx *context.APIContext, form api.EditLabelOption) {
197212
label.Name = *form.Name
198213
}
199214
if form.Color != nil {
200-
label.Color = *form.Color
215+
label.Color = strings.Trim(*form.Color, " ")
216+
if len(label.Color) == 6 {
217+
label.Color = "#" + label.Color
218+
}
219+
if !models.LabelColorPattern.MatchString(label.Color) {
220+
ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color))
221+
return
222+
}
201223
}
202224
if form.Description != nil {
203225
label.Description = *form.Description

routers/repo/issue_label.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ func InitializeLabels(ctx *context.Context, form auth.InitializeLabelsForm) {
3535
return
3636
}
3737

38-
if err := models.InitalizeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName); err != nil {
38+
if err := models.InitializeLabels(models.DefaultDBContext(), ctx.Repo.Repository.ID, form.TemplateName); err != nil {
3939
if models.IsErrIssueLabelTemplateLoad(err) {
4040
originalErr := err.(models.ErrIssueLabelTemplateLoad).OriginalError
4141
ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr))
4242
ctx.Redirect(ctx.Repo.RepoLink + "/labels")
4343
return
4444
}
45-
ctx.ServerError("InitalizeLabels", err)
45+
ctx.ServerError("InitializeLabels", err)
4646
return
4747
}
4848
ctx.Redirect(ctx.Repo.RepoLink + "/labels")

templates/swagger/v1_json.tmpl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5317,6 +5317,9 @@
53175317
"responses": {
53185318
"201": {
53195319
"$ref": "#/responses/Label"
5320+
},
5321+
"422": {
5322+
"$ref": "#/responses/validationError"
53205323
}
53215324
}
53225325
}
@@ -5443,6 +5446,9 @@
54435446
"responses": {
54445447
"200": {
54455448
"$ref": "#/responses/Label"
5449+
},
5450+
"422": {
5451+
"$ref": "#/responses/validationError"
54465452
}
54475453
}
54485454
}

0 commit comments

Comments
 (0)