mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-18 22:57:38 +01:00
Merge remote-tracking branch 'upstream/master' into lperkins/docs-restructure
This commit is contained in:
commit
c2d9315bd4
@ -62,13 +62,13 @@ func (cth contentTrustHandler) ServeHTTP(rw http.ResponseWriter, req *http.Reque
|
||||
util.CopyResp(rec, rw)
|
||||
}
|
||||
|
||||
func validate(req *http.Request) (bool, util.ImageInfo) {
|
||||
var img util.ImageInfo
|
||||
imgRaw := req.Context().Value(util.ImageInfoCtxKey)
|
||||
func validate(req *http.Request) (bool, util.ArtifactInfo) {
|
||||
var img util.ArtifactInfo
|
||||
imgRaw := req.Context().Value(util.ArtifactInfoCtxKey)
|
||||
if imgRaw == nil || !config.WithNotary() {
|
||||
return false, img
|
||||
}
|
||||
img, _ = req.Context().Value(util.ImageInfoCtxKey).(util.ImageInfo)
|
||||
img, _ = req.Context().Value(util.ArtifactInfoCtxKey).(util.ArtifactInfo)
|
||||
if img.Digest == "" {
|
||||
return false, img
|
||||
}
|
||||
@ -81,7 +81,7 @@ func validate(req *http.Request) (bool, util.ImageInfo) {
|
||||
return true, img
|
||||
}
|
||||
|
||||
func matchNotaryDigest(img util.ImageInfo) (bool, error) {
|
||||
func matchNotaryDigest(img util.ArtifactInfo) (bool, error) {
|
||||
if NotaryEndpoint == "" {
|
||||
NotaryEndpoint = config.InternalNotaryEndpoint()
|
||||
}
|
||||
|
@ -49,8 +49,8 @@ func TestMain(m *testing.M) {
|
||||
func TestMatchNotaryDigest(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
// The data from common/utils/notary/helper_test.go
|
||||
img1 := util.ImageInfo{Repository: "notary-demo/busybox", Reference: "1.0", ProjectName: "notary-demo", Digest: "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7"}
|
||||
img2 := util.ImageInfo{Repository: "notary-demo/busybox", Reference: "2.0", ProjectName: "notary-demo", Digest: "sha256:12345678"}
|
||||
img1 := util.ArtifactInfo{Repository: "notary-demo/busybox", Reference: "1.0", ProjectName: "notary-demo", Digest: "sha256:1359608115b94599e5641638bac5aef1ddfaa79bb96057ebf41ebc8d33acf8a7"}
|
||||
img2 := util.ArtifactInfo{Repository: "notary-demo/busybox", Reference: "2.0", ProjectName: "notary-demo", Digest: "sha256:12345678"}
|
||||
|
||||
res1, err := matchNotaryDigest(img1)
|
||||
assert.Nil(err, "Unexpected error: %v, image: %#v", err, img1)
|
||||
|
@ -27,12 +27,12 @@ func New(next http.Handler) http.Handler {
|
||||
|
||||
// ServeHTTP ...
|
||||
func (r *regTokenHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
imgRaw := req.Context().Value(util.ImageInfoCtxKey)
|
||||
imgRaw := req.Context().Value(util.ArtifactInfoCtxKey)
|
||||
if imgRaw == nil {
|
||||
r.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
img, _ := req.Context().Value(util.ImageInfoCtxKey).(util.ImageInfo)
|
||||
img, _ := req.Context().Value(util.ArtifactInfoCtxKey).(util.ArtifactInfo)
|
||||
if img.Digest == "" {
|
||||
r.next.ServeHTTP(rw, req)
|
||||
return
|
||||
|
@ -20,10 +20,19 @@ import (
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
coreutils "github.com/goharbor/harbor/src/core/utils"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
urlPatterns = []*regexp.Regexp{
|
||||
util.ManifestURLRe, util.TagListURLRe, util.BlobURLRe, util.BlobUploadURLRe,
|
||||
}
|
||||
)
|
||||
|
||||
// urlHandler extracts the artifact info from the url of request to V2 handler and propagates it to context
|
||||
type urlHandler struct {
|
||||
next http.Handler
|
||||
}
|
||||
@ -37,38 +46,65 @@ func New(next http.Handler) http.Handler {
|
||||
|
||||
// ServeHTTP ...
|
||||
func (uh urlHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
log.Debugf("in url handler, path: %s", req.URL.Path)
|
||||
flag, repository, reference := util.MatchPullManifest(req)
|
||||
if flag {
|
||||
components := strings.SplitN(repository, "/", 2)
|
||||
path := req.URL.Path
|
||||
log.Debugf("in url handler, path: %s", path)
|
||||
m, ok := parse(path)
|
||||
if !ok {
|
||||
uh.next.ServeHTTP(rw, req)
|
||||
}
|
||||
repo := m[util.RepositorySubexp]
|
||||
components := strings.SplitN(repo, "/", 2)
|
||||
if len(components) < 2 {
|
||||
http.Error(rw, util.MarshalError("PROJECT_POLICY_VIOLATION", fmt.Sprintf("Bad repository name: %s", repository)), http.StatusBadRequest)
|
||||
http.Error(rw, util.MarshalError("PROJECT_POLICY_VIOLATION", fmt.Sprintf("Bad repository name: %s", repo)), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
art := util.ArtifactInfo{
|
||||
Repository: repo,
|
||||
ProjectName: components[0],
|
||||
}
|
||||
if digest, ok := m[util.DigestSubexp]; ok {
|
||||
art.Digest = digest
|
||||
}
|
||||
if ref, ok := m[util.ReferenceSubexp]; ok {
|
||||
art.Reference = ref
|
||||
}
|
||||
|
||||
client, err := coreutils.NewRepositoryClientForUI(util.TokenUsername, repository)
|
||||
if util.ManifestURLRe.MatchString(path) && req.Method == http.MethodGet { // Request for pulling manifest
|
||||
client, err := coreutils.NewRepositoryClientForUI(util.TokenUsername, art.Repository)
|
||||
if err != nil {
|
||||
log.Errorf("Error creating repository Client: %v", err)
|
||||
http.Error(rw, util.MarshalError("PROJECT_POLICY_VIOLATION", fmt.Sprintf("Failed due to internal Error: %v", err)), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
digest, _, err := client.ManifestExist(reference)
|
||||
digest, _, err := client.ManifestExist(art.Reference)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get digest for reference: %s, error: %v", reference, err)
|
||||
log.Errorf("Failed to get digest for reference: %s, error: %v", art.Reference, err)
|
||||
http.Error(rw, util.MarshalError("PROJECT_POLICY_VIOLATION", fmt.Sprintf("Failed due to internal Error: %v", err)), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
img := util.ImageInfo{
|
||||
Repository: repository,
|
||||
Reference: reference,
|
||||
ProjectName: components[0],
|
||||
Digest: digest,
|
||||
}
|
||||
|
||||
log.Debugf("image info of the request: %#v", img)
|
||||
ctx := context.WithValue(req.Context(), util.ImageInfoCtxKey, img)
|
||||
art.Digest = digest
|
||||
log.Debugf("artifact info of the request: %#v", art)
|
||||
ctx := context.WithValue(req.Context(), util.ArtifactInfoCtxKey, art)
|
||||
req = req.WithContext(ctx)
|
||||
}
|
||||
uh.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func parse(urlPath string) (map[string]string, bool) {
|
||||
m := make(map[string]string)
|
||||
match := false
|
||||
for _, re := range urlPatterns {
|
||||
l := re.FindStringSubmatch(urlPath)
|
||||
if len(l) > 0 {
|
||||
match = true
|
||||
for i := 1; i < len(l); i++ {
|
||||
m[re.SubexpNames()[i]] = l[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
if digest.DigestRegexp.MatchString(m[util.ReferenceSubexp]) {
|
||||
m[util.DigestSubexp] = m[util.ReferenceSubexp]
|
||||
}
|
||||
return m, match
|
||||
}
|
||||
|
101
src/core/middlewares/url/handler_test.go
Normal file
101
src/core/middlewares/url/handler_test.go
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License
|
||||
|
||||
package url
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/core/middlewares/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
if result := m.Run(); result != 0 {
|
||||
os.Exit(result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseURL(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
expect map[string]string
|
||||
match bool
|
||||
}{
|
||||
{
|
||||
input: "/api/projects",
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "/v2/_catalog",
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "/v2/no-project-repo/tags/list",
|
||||
expect: map[string]string{
|
||||
util.RepositorySubexp: "no-project-repo",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/development/golang/manifests/sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
expect: map[string]string{
|
||||
util.RepositorySubexp: "development/golang",
|
||||
util.ReferenceSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
util.DigestSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/development/golang/manifests/shaxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "/v2/multi/sector/repository/blobs/sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
expect: map[string]string{
|
||||
util.RepositorySubexp: "multi/sector/repository",
|
||||
util.DigestSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/blobs/uploads",
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "/v2/library/ubuntu/blobs/uploads",
|
||||
expect: map[string]string{
|
||||
util.RepositorySubexp: "library/ubuntu",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/library/centos/blobs/uploads/u-12345",
|
||||
expect: map[string]string{
|
||||
util.RepositorySubexp: "library/centos",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
e, m := parse(c.input)
|
||||
assert.Equal(t, c.match, m)
|
||||
assert.Equal(t, c.expect, e)
|
||||
}
|
||||
}
|
@ -31,6 +31,8 @@ import (
|
||||
"github.com/docker/distribution"
|
||||
"github.com/docker/distribution/manifest/schema1"
|
||||
"github.com/docker/distribution/manifest/schema2"
|
||||
"github.com/docker/distribution/reference"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
@ -49,8 +51,15 @@ import (
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
// ImageInfoCtxKey the context key for image information
|
||||
ImageInfoCtxKey = contextKey("ImageInfo")
|
||||
// RepositorySubexp is the name for sub regex that maps to repository name in the url
|
||||
RepositorySubexp = "repository"
|
||||
// ReferenceSubexp is the name for sub regex that maps to reference (tag or digest) url
|
||||
ReferenceSubexp = "reference"
|
||||
// DigestSubexp is the name for sub regex that maps to digest in the url
|
||||
DigestSubexp = "digest"
|
||||
|
||||
// ArtifactInfoCtxKey the context key for artifact information
|
||||
ArtifactInfoCtxKey = contextKey("ArtifactInfo")
|
||||
// ScannerPullCtxKey the context key for robot account to bypass the pull policy check.
|
||||
ScannerPullCtxKey = contextKey("ScannerPullCheck")
|
||||
// TokenUsername ...
|
||||
@ -73,7 +82,16 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
manifestURLRe = regexp.MustCompile(`^/v2/((?:[a-z0-9]+(?:[._-][a-z0-9]+)*/)+)manifests/([\w][\w.:-]{0,127})`)
|
||||
// ManifestURLRe is the regular expression for matching request v2 handler to view/delete manifest
|
||||
ManifestURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/manifests/(?P<%s>%s|%s)$`, RepositorySubexp, reference.NameRegexp.String(), ReferenceSubexp, reference.TagRegexp.String(), digest.DigestRegexp.String()))
|
||||
// TagListURLRe is the regular expression for matching request to v2 handler to list tags
|
||||
TagListURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/tags/list`, RepositorySubexp, reference.NameRegexp.String()))
|
||||
// BlobURLRe is the regular expression for matching request to v2 handler to retrieve delete a blob
|
||||
BlobURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/blobs/(?P<%s>%s)$`, RepositorySubexp, reference.NameRegexp.String(), DigestSubexp, digest.DigestRegexp.String()))
|
||||
// BlobUploadURLRe is the regular expression for matching the request to v2 handler to upload a blob, the upload uuid currently is not put into a group
|
||||
BlobUploadURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/blobs/uploads[/a-zA-Z0-9\-_\.=]*$`, RepositorySubexp, reference.NameRegexp.String()))
|
||||
// CatalogURLRe is the regular expression for mathing the request to v2 handler to list catalog
|
||||
CatalogURLRe = regexp.MustCompile(`^/v2/_catalog$`)
|
||||
)
|
||||
|
||||
// ChartVersionInfo ...
|
||||
@ -91,8 +109,8 @@ func (info *ChartVersionInfo) MutexKey(suffix ...string) string {
|
||||
return strings.Join(append(a, suffix...), ":")
|
||||
}
|
||||
|
||||
// ImageInfo ...
|
||||
type ImageInfo struct {
|
||||
// ArtifactInfo ...
|
||||
type ArtifactInfo struct {
|
||||
Repository string
|
||||
Reference string
|
||||
ProjectName string
|
||||
@ -281,7 +299,7 @@ func MarshalError(code, msg string) string {
|
||||
|
||||
// MatchManifestURL ...
|
||||
func MatchManifestURL(req *http.Request) (bool, string, string) {
|
||||
s := manifestURLRe.FindStringSubmatch(req.URL.Path)
|
||||
s := ManifestURLRe.FindStringSubmatch(req.URL.Path)
|
||||
if len(s) == 3 {
|
||||
s[1] = strings.TrimSuffix(s[1], "/")
|
||||
return true, s[1], s[2]
|
||||
@ -437,8 +455,8 @@ func ChartVersionInfoFromContext(ctx context.Context) (*ChartVersionInfo, bool)
|
||||
}
|
||||
|
||||
// ImageInfoFromContext returns image info from context
|
||||
func ImageInfoFromContext(ctx context.Context) (*ImageInfo, bool) {
|
||||
info, ok := ctx.Value(ImageInfoCtxKey).(*ImageInfo)
|
||||
func ImageInfoFromContext(ctx context.Context) (*ArtifactInfo, bool) {
|
||||
info, ok := ctx.Value(ArtifactInfoCtxKey).(*ArtifactInfo)
|
||||
return info, ok
|
||||
}
|
||||
|
||||
@ -470,8 +488,8 @@ func NewChartVersionInfoContext(ctx context.Context, info *ChartVersionInfo) con
|
||||
}
|
||||
|
||||
// NewImageInfoContext returns context with image info
|
||||
func NewImageInfoContext(ctx context.Context, info *ImageInfo) context.Context {
|
||||
return context.WithValue(ctx, ImageInfoCtxKey, info)
|
||||
func NewImageInfoContext(ctx context.Context, info *ArtifactInfo) context.Context {
|
||||
return context.WithValue(ctx, ArtifactInfoCtxKey, info)
|
||||
}
|
||||
|
||||
// NewManifestInfoContext returns context with manifest info
|
||||
|
@ -108,17 +108,17 @@ func (vh vulnerableHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||
util.CopyResp(rec, rw)
|
||||
}
|
||||
|
||||
func validate(req *http.Request) (bool, util.ImageInfo, vuln.Severity, models.CVEWhitelist) {
|
||||
func validate(req *http.Request) (bool, util.ArtifactInfo, vuln.Severity, models.CVEWhitelist) {
|
||||
var vs vuln.Severity
|
||||
var wl models.CVEWhitelist
|
||||
var img util.ImageInfo
|
||||
imgRaw := req.Context().Value(util.ImageInfoCtxKey)
|
||||
var img util.ArtifactInfo
|
||||
imgRaw := req.Context().Value(util.ArtifactInfoCtxKey)
|
||||
if imgRaw == nil {
|
||||
return false, img, vs, wl
|
||||
}
|
||||
|
||||
// Expected artifact specified?
|
||||
img, ok := imgRaw.(util.ImageInfo)
|
||||
img, ok := imgRaw.(util.ArtifactInfo)
|
||||
if !ok || len(img.Digest) == 0 {
|
||||
return false, img, vs, wl
|
||||
}
|
||||
|
124
src/server/middleware/artifactinfo/artifact_info.go
Normal file
124
src/server/middleware/artifactinfo/artifact_info.go
Normal file
@ -0,0 +1,124 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package artifactinfo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
reg_err "github.com/goharbor/harbor/src/server/registry/error"
|
||||
"github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
const (
|
||||
blobMountQuery = "mount"
|
||||
blobFromQuery = "from"
|
||||
blobMountDigest = "blob_mount_digest"
|
||||
blobMountRepo = "blob_mount_repo"
|
||||
)
|
||||
|
||||
var (
|
||||
urlPatterns = map[string]*regexp.Regexp{
|
||||
"manifest": middleware.V2ManifestURLRe,
|
||||
"tag_list": middleware.V2TagListURLRe,
|
||||
"blob_upload": middleware.V2BlobUploadURLRe,
|
||||
"blob": middleware.V2BlobURLRe,
|
||||
}
|
||||
)
|
||||
|
||||
// Middleware gets the information of artifact via url of the request and inject it into the context
|
||||
func Middleware() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
log.Debugf("In artifact info middleware, url: %s", req.URL.String())
|
||||
m, ok := parse(req.URL)
|
||||
if !ok {
|
||||
next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
repo := m[middleware.RepositorySubexp]
|
||||
pn, err := projectNameFromRepo(repo)
|
||||
if err != nil {
|
||||
reg_err.Handle(rw, req, ierror.BadRequestError(err))
|
||||
return
|
||||
}
|
||||
art := &middleware.ArtifactInfo{
|
||||
Repository: repo,
|
||||
ProjectName: pn,
|
||||
}
|
||||
if d, ok := m[middleware.DigestSubexp]; ok {
|
||||
art.Digest = d
|
||||
}
|
||||
if ref, ok := m[middleware.ReferenceSubexp]; ok {
|
||||
art.Reference = ref
|
||||
}
|
||||
|
||||
if bmr, ok := m[blobMountRepo]; ok {
|
||||
// Fail early for now, though in docker registry an invalid may return 202
|
||||
// it's not clear in OCI spec how to handle invalid from parm
|
||||
bmp, err := projectNameFromRepo(bmr)
|
||||
if err != nil {
|
||||
reg_err.Handle(rw, req, ierror.BadRequestError(err))
|
||||
return
|
||||
}
|
||||
art.BlobMountDigest = m[blobMountDigest]
|
||||
art.BlobMountProjectName = bmp
|
||||
art.BlobMountRepository = bmr
|
||||
}
|
||||
ctx := context.WithValue(req.Context(), middleware.ArtifactInfoKey, art)
|
||||
next.ServeHTTP(rw, req.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func projectNameFromRepo(repo string) (string, error) {
|
||||
components := strings.SplitN(repo, "/", 2)
|
||||
if len(components) < 2 {
|
||||
return "", fmt.Errorf("invalid repository name: %s", repo)
|
||||
}
|
||||
return components[0], nil
|
||||
}
|
||||
|
||||
func parse(url *url.URL) (map[string]string, bool) {
|
||||
path := url.Path
|
||||
query := url.Query()
|
||||
m := make(map[string]string)
|
||||
match := false
|
||||
for key, re := range urlPatterns {
|
||||
l := re.FindStringSubmatch(path)
|
||||
if len(l) > 0 {
|
||||
match = true
|
||||
for i := 1; i < len(l); i++ {
|
||||
m[re.SubexpNames()[i]] = l[i]
|
||||
}
|
||||
if key == "blob_upload" && len(query.Get(blobFromQuery)) > 0 {
|
||||
m[blobMountDigest] = query.Get(blobMountQuery)
|
||||
m[blobMountRepo] = query.Get(blobFromQuery)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if digest.DigestRegexp.MatchString(m[middleware.ReferenceSubexp]) {
|
||||
m[middleware.DigestSubexp] = m[middleware.ReferenceSubexp]
|
||||
}
|
||||
return m, match
|
||||
}
|
182
src/server/middleware/artifactinfo/artifact_info_test.go
Normal file
182
src/server/middleware/artifactinfo/artifact_info_test.go
Normal file
@ -0,0 +1,182 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License
|
||||
|
||||
package artifactinfo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseURL(t *testing.T) {
|
||||
cases := []struct {
|
||||
input string
|
||||
expect map[string]string
|
||||
match bool
|
||||
}{
|
||||
{
|
||||
input: "/api/projects",
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "/v2/_catalog",
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "/v2/no-project-repo/tags/list",
|
||||
expect: map[string]string{
|
||||
middleware.RepositorySubexp: "no-project-repo",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/development/golang/manifests/sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
expect: map[string]string{
|
||||
middleware.RepositorySubexp: "development/golang",
|
||||
middleware.ReferenceSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
middleware.DigestSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/development/golang/manifests/shaxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "/v2/multi/sector/repository/blobs/sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
expect: map[string]string{
|
||||
middleware.RepositorySubexp: "multi/sector/repository",
|
||||
middleware.DigestSubexp: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/blobs/uploads",
|
||||
expect: map[string]string{},
|
||||
match: false,
|
||||
},
|
||||
{
|
||||
input: "/v2/library/ubuntu/blobs/uploads",
|
||||
expect: map[string]string{
|
||||
middleware.RepositorySubexp: "library/ubuntu",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/library/ubuntu/blobs/uploads/?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=old/ubuntu",
|
||||
expect: map[string]string{
|
||||
middleware.RepositorySubexp: "library/ubuntu",
|
||||
blobMountDigest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
blobMountRepo: "old/ubuntu",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
{
|
||||
input: "/v2/library/centos/blobs/uploads/u-12345",
|
||||
expect: map[string]string{
|
||||
middleware.RepositorySubexp: "library/centos",
|
||||
},
|
||||
match: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
url, err := url.Parse(c.input)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e, m := parse(url)
|
||||
assert.Equal(t, c.match, m)
|
||||
assert.Equal(t, c.expect, e)
|
||||
}
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
h.ctx = req.Context()
|
||||
}
|
||||
func TestPopulateArtifactInfo(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
req *http.Request
|
||||
sc int
|
||||
art *middleware.ArtifactInfo
|
||||
}{
|
||||
{
|
||||
req: httptest.NewRequest(http.MethodDelete, "/v2/hello-world/manifests/latest", nil),
|
||||
sc: http.StatusBadRequest,
|
||||
art: nil,
|
||||
},
|
||||
{
|
||||
req: httptest.NewRequest(http.MethodDelete, "/v2/library/hello-world/manifests/latest", nil),
|
||||
sc: http.StatusOK,
|
||||
art: &middleware.ArtifactInfo{
|
||||
Repository: "library/hello-world",
|
||||
Reference: "latest",
|
||||
ProjectName: "library",
|
||||
},
|
||||
},
|
||||
{
|
||||
req: httptest.NewRequest(http.MethodPost, "/v2/library/ubuntu/blobs/uploads/?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=no-project", nil),
|
||||
sc: http.StatusBadRequest,
|
||||
art: nil,
|
||||
},
|
||||
{
|
||||
req: httptest.NewRequest(http.MethodPost, "/v2/library/ubuntu/blobs/uploads/?from=old/ubuntu&mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f", nil),
|
||||
sc: http.StatusOK,
|
||||
art: &middleware.ArtifactInfo{
|
||||
Repository: "library/ubuntu",
|
||||
ProjectName: "library",
|
||||
BlobMountRepository: "old/ubuntu",
|
||||
BlobMountDigest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
BlobMountProjectName: "old",
|
||||
},
|
||||
},
|
||||
{
|
||||
req: httptest.NewRequest(http.MethodDelete, "/v2/library/hello-world/manifests/sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f", nil),
|
||||
sc: http.StatusOK,
|
||||
art: &middleware.ArtifactInfo{
|
||||
Repository: "library/hello-world",
|
||||
Reference: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
Digest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
ProjectName: "library",
|
||||
},
|
||||
},
|
||||
}
|
||||
next := &handler{}
|
||||
|
||||
for _, tt := range cases {
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
Middleware()(next).ServeHTTP(rec, tt.req)
|
||||
assert.Equal(t, tt.sc, rec.Code)
|
||||
if tt.art != nil {
|
||||
a, ok := middleware.ArtifactInfoFromContext(next.ctx)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, *tt.art, *a)
|
||||
}
|
||||
}
|
||||
}
|
@ -2,17 +2,42 @@ package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
// RepositorySubexp is the name for sub regex that maps to repository name in the url
|
||||
RepositorySubexp = "repository"
|
||||
// ReferenceSubexp is the name for sub regex that maps to reference (tag or digest) url
|
||||
ReferenceSubexp = "reference"
|
||||
// DigestSubexp is the name for sub regex that maps to digest in the url
|
||||
DigestSubexp = "digest"
|
||||
// ArtifactInfoKey the context key for artifact info
|
||||
ArtifactInfoKey = contextKey("artifactInfo")
|
||||
// manifestInfoKey the context key for manifest info
|
||||
manifestInfoKey = contextKey("ManifestInfo")
|
||||
// ScannerPullCtxKey the context key for robot account to bypass the pull policy check.
|
||||
ScannerPullCtxKey = contextKey("ScannerPullCheck")
|
||||
)
|
||||
|
||||
var (
|
||||
// V2ManifestURLRe is the regular expression for matching request v2 handler to view/delete manifest
|
||||
V2ManifestURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/manifests/(?P<%s>%s|%s)$`, RepositorySubexp, reference.NameRegexp.String(), ReferenceSubexp, reference.TagRegexp.String(), digest.DigestRegexp.String()))
|
||||
// V2TagListURLRe is the regular expression for matching request to v2 handler to list tags
|
||||
V2TagListURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/tags/list`, RepositorySubexp, reference.NameRegexp.String()))
|
||||
// V2BlobURLRe is the regular expression for matching request to v2 handler to retrieve delete a blob
|
||||
V2BlobURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/blobs/(?P<%s>%s)$`, RepositorySubexp, reference.NameRegexp.String(), DigestSubexp, digest.DigestRegexp.String()))
|
||||
// V2BlobUploadURLRe is the regular expression for matching the request to v2 handler to upload a blob, the upload uuid currently is not put into a group
|
||||
V2BlobUploadURLRe = regexp.MustCompile(fmt.Sprintf(`^/v2/(?P<%s>%s)/blobs/uploads[/a-zA-Z0-9\-_\.=]*$`, RepositorySubexp, reference.NameRegexp.String()))
|
||||
// V2CatalogURLRe is the regular expression for mathing the request to v2 handler to list catalog
|
||||
V2CatalogURLRe = regexp.MustCompile(`^/v2/_catalog$`)
|
||||
)
|
||||
|
||||
// ManifestInfo ...
|
||||
type ManifestInfo struct {
|
||||
ProjectID int64
|
||||
@ -21,6 +46,23 @@ type ManifestInfo struct {
|
||||
Digest string
|
||||
}
|
||||
|
||||
// ArtifactInfo ...
|
||||
type ArtifactInfo struct {
|
||||
Repository string
|
||||
Reference string
|
||||
ProjectName string
|
||||
Digest string
|
||||
BlobMountRepository string
|
||||
BlobMountProjectName string
|
||||
BlobMountDigest string
|
||||
}
|
||||
|
||||
// ArtifactInfoFromContext returns the artifact info from context
|
||||
func ArtifactInfoFromContext(ctx context.Context) (*ArtifactInfo, bool) {
|
||||
info, ok := ctx.Value(ArtifactInfoKey).(*ArtifactInfo)
|
||||
return info, ok
|
||||
}
|
||||
|
||||
// NewManifestInfoContext returns context with manifest info
|
||||
func NewManifestInfoContext(ctx context.Context, info *ManifestInfo) context.Context {
|
||||
return context.WithValue(ctx, manifestInfoKey, info)
|
||||
|
116
src/server/middleware/v2authz/authz.go
Normal file
116
src/server/middleware/v2authz/authz.go
Normal file
@ -0,0 +1,116 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package authz
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils/log"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/core/filter"
|
||||
"github.com/goharbor/harbor/src/core/promgr"
|
||||
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
reg_err "github.com/goharbor/harbor/src/server/registry/error"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type reqChecker struct {
|
||||
pm promgr.ProjectManager
|
||||
}
|
||||
|
||||
func (rc *reqChecker) check(req *http.Request) error {
|
||||
securityCtx, err := filter.GetSecurityContext(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a, ok := middleware.ArtifactInfoFromContext(req.Context()); ok {
|
||||
action := getAction(req)
|
||||
if action == "" {
|
||||
return nil
|
||||
}
|
||||
log.Debugf("action: %s, repository: %s", action, a.Repository)
|
||||
pid, err := rc.projectID(a.ProjectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resource := rbac.NewProjectNamespace(pid).Resource(rbac.ResourceRepository)
|
||||
if !securityCtx.Can(action, resource) {
|
||||
return fmt.Errorf("unauthorized to access repository: %s, action: %s", a.Repository, action)
|
||||
}
|
||||
if req.Method == http.MethodPost && a.BlobMountProjectName != "" { // check permission for the source of blob mount
|
||||
p, err := rc.pm.Get(a.BlobMountProjectName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resource := rbac.NewProjectNamespace(p.ProjectID).Resource(rbac.ResourceRepository)
|
||||
if !securityCtx.Can(rbac.ActionPull, resource) {
|
||||
return fmt.Errorf("unauthorized to access repository from which to mount blob: %s, action: %s", a.BlobMountRepository, rbac.ActionPull)
|
||||
}
|
||||
}
|
||||
} else if len(middleware.V2CatalogURLRe.FindStringSubmatch(req.URL.Path)) == 1 && !securityCtx.IsSysAdmin() {
|
||||
return fmt.Errorf("unauthorized to list catalog")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *reqChecker) projectID(name string) (int64, error) {
|
||||
p, err := rc.pm.Get(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if p == nil {
|
||||
return 0, fmt.Errorf("project not found, name: %s", name)
|
||||
}
|
||||
return p.ProjectID, nil
|
||||
}
|
||||
|
||||
func getAction(req *http.Request) rbac.Action {
|
||||
pushActions := map[string]struct{}{
|
||||
http.MethodPost: {},
|
||||
http.MethodDelete: {},
|
||||
http.MethodPatch: {},
|
||||
http.MethodPut: {},
|
||||
}
|
||||
pullActions := map[string]struct{}{
|
||||
http.MethodGet: {},
|
||||
http.MethodHead: {},
|
||||
}
|
||||
if _, ok := pushActions[req.Method]; ok {
|
||||
return rbac.ActionPush
|
||||
}
|
||||
if _, ok := pullActions[req.Method]; ok {
|
||||
return rbac.ActionPull
|
||||
}
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
var checker = reqChecker{
|
||||
pm: config.GlobalProjectMgr,
|
||||
}
|
||||
|
||||
// Middleware checks the permission of the request to access the artifact
|
||||
func Middleware() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
if err := checker.check(req); err != nil {
|
||||
reg_err.Handle(rw, req, ierror.UnauthorizedError(err))
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
}
|
212
src/server/middleware/v2authz/authz_test.go
Normal file
212
src/server/middleware/v2authz/authz_test.go
Normal file
@ -0,0 +1,212 @@
|
||||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package authz
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/core/filter"
|
||||
"github.com/goharbor/harbor/src/core/promgr/metamgr"
|
||||
"github.com/goharbor/harbor/src/server/middleware"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockPM struct{}
|
||||
|
||||
func (mockPM) Get(projectIDOrName interface{}) (*models.Project, error) {
|
||||
name := projectIDOrName.(string)
|
||||
id, _ := strconv.Atoi(strings.TrimPrefix(name, "project_"))
|
||||
if id == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return &models.Project{
|
||||
ProjectID: int64(id),
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (mockPM) Create(*models.Project) (int64, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mockPM) Delete(projectIDOrName interface{}) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mockPM) Update(projectIDOrName interface{}, project *models.Project) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mockPM) List(query *models.ProjectQueryParam) (*models.ProjectQueryResult, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mockPM) IsPublic(projectIDOrName interface{}) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (mockPM) Exists(projectIDOrName interface{}) (bool, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mockPM) GetPublic() ([]*models.Project, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mockPM) GetMetadataManager() metamgr.ProjectMetadataManager {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
type mockSC struct{}
|
||||
|
||||
func (mockSC) IsAuthenticated() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (mockSC) GetUsername() string {
|
||||
return "mock"
|
||||
}
|
||||
|
||||
func (mockSC) IsSysAdmin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (mockSC) IsSolutionUser() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (mockSC) GetMyProjects() ([]*models.Project, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mockSC) GetProjectRoles(projectIDOrName interface{}) []int {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (mockSC) Can(action rbac.Action, resource rbac.Resource) bool {
|
||||
ns, _ := resource.GetNamespace()
|
||||
perms := map[int64]map[rbac.Action]struct{}{
|
||||
1: {
|
||||
rbac.ActionPull: {},
|
||||
rbac.ActionPush: {},
|
||||
},
|
||||
2: {
|
||||
rbac.ActionPull: {},
|
||||
},
|
||||
}
|
||||
pid := ns.Identity().(int64)
|
||||
m, ok := perms[pid]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
_, ok = m[action]
|
||||
return ok
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
checker = reqChecker{
|
||||
pm: mockPM{},
|
||||
}
|
||||
if rc := m.Run(); rc != 0 {
|
||||
os.Exit(rc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiddleware(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
})
|
||||
|
||||
baseCtx := context.WithValue(context.Background(), filter.SecurCtxKey, mockSC{})
|
||||
ar1 := &middleware.ArtifactInfo{
|
||||
Repository: "project_1/hello-world",
|
||||
Reference: "v1",
|
||||
ProjectName: "project_1",
|
||||
}
|
||||
ar2 := &middleware.ArtifactInfo{
|
||||
Repository: "library/ubuntu",
|
||||
Reference: "14.04",
|
||||
ProjectName: "library",
|
||||
}
|
||||
ar3 := &middleware.ArtifactInfo{
|
||||
Repository: "project_1/ubuntu",
|
||||
Reference: "14.04",
|
||||
ProjectName: "project_1",
|
||||
BlobMountRepository: "project_2/ubuntu",
|
||||
BlobMountProjectName: "project_2",
|
||||
BlobMountDigest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
}
|
||||
ar4 := &middleware.ArtifactInfo{
|
||||
Repository: "project_1/ubuntu",
|
||||
Reference: "14.04",
|
||||
ProjectName: "project_1",
|
||||
BlobMountRepository: "project_3/ubuntu",
|
||||
BlobMountProjectName: "project_3",
|
||||
BlobMountDigest: "sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f",
|
||||
}
|
||||
ctx1 := context.WithValue(baseCtx, middleware.ArtifactInfoKey, ar1)
|
||||
ctx2 := context.WithValue(baseCtx, middleware.ArtifactInfoKey, ar2)
|
||||
ctx3 := context.WithValue(baseCtx, middleware.ArtifactInfoKey, ar3)
|
||||
ctx4 := context.WithValue(baseCtx, middleware.ArtifactInfoKey, ar4)
|
||||
req1a, _ := http.NewRequest(http.MethodGet, "/v2/project_1/hello-world/manifest/v1", nil)
|
||||
req1b, _ := http.NewRequest(http.MethodDelete, "/v2/project_1/hello-world/manifest/v1", nil)
|
||||
req2, _ := http.NewRequest(http.MethodGet, "/v2/library/ubuntu/manifest/14.04", nil)
|
||||
req3, _ := http.NewRequest(http.MethodGet, "/v2/_catalog", nil)
|
||||
req4, _ := http.NewRequest(http.MethodPost, "/v2/project_1/ubuntu/blobs/uploads/mount=?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=project_2/ubuntu", nil)
|
||||
req5, _ := http.NewRequest(http.MethodPost, "/v2/project_1/ubuntu/blobs/uploads/mount=?mount=sha256:08e4a417ff4e3913d8723a05cc34055db01c2fd165b588e049c5bad16ce6094f&from=project_3/ubuntu", nil)
|
||||
|
||||
cases := []struct {
|
||||
input *http.Request
|
||||
status int
|
||||
}{
|
||||
{
|
||||
input: req1a.WithContext(ctx1),
|
||||
status: http.StatusOK,
|
||||
},
|
||||
{
|
||||
input: req1b.WithContext(ctx1),
|
||||
status: http.StatusOK,
|
||||
},
|
||||
{
|
||||
input: req2.WithContext(ctx2),
|
||||
status: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
input: req3.WithContext(baseCtx),
|
||||
status: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
input: req4.WithContext(ctx3),
|
||||
status: http.StatusOK,
|
||||
},
|
||||
{
|
||||
input: req5.WithContext(ctx4),
|
||||
status: http.StatusUnauthorized,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
rec := httptest.NewRecorder()
|
||||
t.Logf("req : %s, %s", c.input.Method, c.input.URL)
|
||||
Middleware()(next).ServeHTTP(rec, c.input)
|
||||
assert.Equal(t, c.status, rec.Result().StatusCode)
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ import (
|
||||
|
||||
// Handle generates the HTTP status code and error payload and writes them to the response
|
||||
func Handle(w http.ResponseWriter, req *http.Request, err error) {
|
||||
log.Errorf("failed to handle the request %s: %v", req.URL.Path, err)
|
||||
log.Errorf("failed to handle the request %s: %v", req.URL, err)
|
||||
statusCode, payload := serror.APIError(err)
|
||||
w.WriteHeader(statusCode)
|
||||
w.Write([]byte(payload))
|
||||
|
@ -73,7 +73,7 @@ class TestProjects(unittest.TestCase):
|
||||
#5. Get project quota
|
||||
quota = self.system.get_project_quota("project", TestProjects.project_test_quota_id, **ADMIN_CLIENT)
|
||||
self.assertEqual(quota[0].used["count"], 1)
|
||||
self.assertEqual(quota[0].used["storage"], 2789174)
|
||||
self.assertEqual(quota[0].used["storage"], 2789002)
|
||||
|
||||
#6. Delete repository(RA) by user(UA);
|
||||
self.repo.delete_repoitory(TestProjects.repo_name, **ADMIN_CLIENT)
|
||||
|
Loading…
Reference in New Issue
Block a user