Validate repo and tag names in retag

Signed-off-by: cd1989 <chende@caicloud.io>
This commit is contained in:
cd1989 2018-12-18 14:29:44 +08:00
parent 70016cc0eb
commit c117a23133
5 changed files with 248 additions and 3 deletions

View File

@ -38,7 +38,7 @@ type Image struct {
func ParseImage(image string) (*Image, error) { func ParseImage(image string) (*Image, error) {
repo := strings.SplitN(image, "/", 2) repo := strings.SplitN(image, "/", 2)
if len(repo) < 2 { if len(repo) < 2 {
return nil, fmt.Errorf("Unable to parse image from string: %s", image) return nil, fmt.Errorf("unable to parse image from string: %s", image)
} }
i := strings.SplitN(repo[1], ":", 2) i := strings.SplitN(repo[1], ":", 2)
res := &Image{ res := &Image{

View File

@ -0,0 +1,24 @@
package utils
import (
"fmt"
"regexp"
)
const nameComponent = `[a-z0-9]+((?:[._]|__|[-]*)[a-z0-9]+)*`
// TagRegexp is regular expression to match image tags, for example, 'v1.0'
var TagRegexp = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
// RepoRegexp is regular expression to match repo name, for example, 'busybox', 'stage/busybox'
var RepoRegexp = regexp.MustCompile(fmt.Sprintf("^%s(/%s)*$", nameComponent, nameComponent))
// ValidateTag validates whether a tag is valid.
func ValidateTag(tag string) bool {
return TagRegexp.MatchString(tag)
}
// ValidateRepo validates whether a repo name is valid.
func ValidateRepo(repo string) bool {
return RepoRegexp.MatchString(repo)
}

View File

@ -0,0 +1,183 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestValidateTag(t *testing.T) {
cases := []struct {
tag string
valid bool
}{
{
"v1.0",
true,
},
{
"1.0.0",
true,
},
{
"v1.0-alpha.0",
true,
},
{
"1__",
true,
},
{
"__v1.0",
true,
},
{
"_...",
true,
},
{
"_-_",
true,
},
{
"--v1.0",
false,
},
{
".0.1",
false,
},
{
"-0.1",
false,
},
{
"0.1.*",
false,
},
{
"0.1.?",
false,
},
}
for _, c := range cases {
if c.valid {
assert.True(t, ValidateTag(c.tag))
} else {
assert.False(t, ValidateTag(c.tag))
}
}
}
func TestValidateRepo(t *testing.T) {
cases := []struct {
repo string
valid bool
}{
{
"a",
true,
},
{
"a_a",
true,
},
{
"a__a",
true,
},
{
"a-a",
true,
},
{
"a--a",
true,
},
{
"a---a",
true,
},
{
"a.a",
true,
},
{
"a/b.b",
true,
},
{
"a_a/b-b",
true,
},
{
".a",
false,
},
{
"_a",
false,
},
{
"-a",
false,
},
{
"a.",
false,
},
{
"a_",
false,
},
{
"a-",
false,
},
{
"a..a",
false,
},
{
"a___a",
false,
},
{
"a.-a",
false,
},
{
"a_-a",
false,
},
{
"a*",
false,
},
{
"A/_a",
false,
},
{
"A/.a",
false,
},
{
"Aaaa",
false,
},
{
"aaaA",
false,
},
}
for _, c := range cases {
if c.valid {
assert.True(t, ValidateRepo(c.repo))
} else {
assert.False(t, ValidateRepo(c.repo))
}
}
}

View File

@ -432,6 +432,12 @@ func (ra *RepositoryAPI) Retag() {
} }
repoName := ra.GetString(":splat") repoName := ra.GetString(":splat")
project, repo := utils.ParseRepository(repoName)
if !utils.ValidateRepo(repo) {
ra.HandleBadRequest(fmt.Sprintf("invalid repo '%s'", repo))
return
}
request := models.RetagRequest{} request := models.RetagRequest{}
ra.DecodeJSONReq(&request) ra.DecodeJSONReq(&request)
srcImage, err := models.ParseImage(request.SrcImage) srcImage, err := models.ParseImage(request.SrcImage)
@ -440,6 +446,11 @@ func (ra *RepositoryAPI) Retag() {
return return
} }
if !utils.ValidateTag(request.Tag) {
ra.HandleBadRequest(fmt.Sprintf("invalid tag '%s'", request.Tag))
return
}
// Check whether source image exists // Check whether source image exists
exist, _, err := ra.checkExistence(fmt.Sprintf("%s/%s", srcImage.Project, srcImage.Repo), srcImage.Tag) exist, _, err := ra.checkExistence(fmt.Sprintf("%s/%s", srcImage.Project, srcImage.Repo), srcImage.Tag)
if err != nil { if err != nil {
@ -452,7 +463,6 @@ func (ra *RepositoryAPI) Retag() {
} }
// Check whether target project exists // Check whether target project exists
project, repo := utils.ParseRepository(repoName)
exist, err = ra.ProjectMgr.Exists(project) exist, err = ra.ProjectMgr.Exists(project)
if err != nil { if err != nil {
ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", project), err) ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", project), err)
@ -476,7 +486,7 @@ func (ra *RepositoryAPI) Retag() {
} }
} }
// Check whether use has read permission to source project // Check whether user has read permission to source project
if !ra.SecurityCtx.HasReadPerm(srcImage.Project) { if !ra.SecurityCtx.HasReadPerm(srcImage.Project) {
log.Errorf("user has no read permission to project '%s'", srcImage.Project) log.Errorf("user has no read permission to project '%s'", srcImage.Project)
ra.HandleForbidden(fmt.Sprintf("%s has no read permission to project %s", ra.SecurityCtx.GetUsername(), srcImage.Project)) ra.HandleForbidden(fmt.Sprintf("%s has no read permission to project %s", ra.SecurityCtx.GetUsername(), srcImage.Project))

View File

@ -440,5 +440,33 @@ func TestRetag(t *testing.T) {
assert.Equal(int(409), httpStatusCode, "httpStatusCode should be 409") assert.Equal(int(409), httpStatusCode, "httpStatusCode should be 409")
} }
// -------------------case 7 : response code = 400------------------------//
fmt.Println("case 7 : response code = 400")
retagReq = &apilib.Retag{
Tag: ".0.1",
SrcImage: "library/hello-world:latest",
Override: true,
}
code, err = apiTest.RetagImage(*admin, repo, retagReq)
if err != nil {
t.Errorf("failed to retag: %v", err)
} else {
assert.Equal(int(400), code, "response code should be 400")
}
// -------------------case 8 : response code = 400------------------------//
fmt.Println("case 8 : response code = 400")
retagReq = &apilib.Retag{
Tag: "v0.1",
SrcImage: "library/hello-world:latest",
Override: true,
}
code, err = apiTest.RetagImage(*admin, "library/Aaaa", retagReq)
if err != nil {
t.Errorf("failed to retag: %v", err)
} else {
assert.Equal(int(400), code, "response code should be 400")
}
fmt.Printf("\n") fmt.Printf("\n")
} }