mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
feat(ui): let UI delete manifests if current user has permissions to do so (#2132)
- added a new field 'IsDeletable' for graphql ImageSummary struct. - apply cors on DeleteManifest route Signed-off-by: Petu Eusebiu <peusebiu@cisco.com>
This commit is contained in:
parent
86b0a226f3
commit
dbb1c3519f
8 changed files with 118 additions and 7 deletions
|
@ -639,7 +639,7 @@ func TestAllowMethodsHeader(t *testing.T) {
|
|||
// /v2/{name}/manifests/{reference}
|
||||
resp, err = simpleUserClient.Options(baseURL + "/v2/reponame/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "HEAD,GET,OPTIONS")
|
||||
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "HEAD,GET,DELETE,OPTIONS")
|
||||
|
||||
// /v2/{name}/referrers/{digest}
|
||||
resp, err = simpleUserClient.Options(baseURL + "/v2/reponame/referrers/" + digest.String())
|
||||
|
|
|
@ -134,14 +134,14 @@ func (rh *RouteHandler) SetupRoutes() {
|
|||
getUIHeadersHandler(rh.c.Config, http.MethodGet, http.MethodOptions)(
|
||||
applyCORSHeaders(rh.ListTags))).Methods(http.MethodGet, http.MethodOptions)
|
||||
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
|
||||
getUIHeadersHandler(rh.c.Config, http.MethodHead, http.MethodGet, http.MethodOptions)(
|
||||
getUIHeadersHandler(rh.c.Config, http.MethodHead, http.MethodGet, http.MethodDelete, http.MethodOptions)(
|
||||
applyCORSHeaders(rh.CheckManifest))).Methods(http.MethodHead, http.MethodOptions)
|
||||
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
|
||||
applyCORSHeaders(rh.GetManifest)).Methods(http.MethodGet)
|
||||
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
|
||||
rh.UpdateManifest).Methods(http.MethodPut)
|
||||
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", zreg.NameRegexp.String()),
|
||||
rh.DeleteManifest).Methods(http.MethodDelete)
|
||||
applyCORSHeaders(rh.DeleteManifest)).Methods(http.MethodDelete)
|
||||
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", zreg.NameRegexp.String()),
|
||||
rh.CheckBlob).Methods(http.MethodHead)
|
||||
prefixedDistSpecRouter.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", zreg.NameRegexp.String()),
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/boltdb"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
reqCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
. "zotregistry.io/zot/pkg/test/image-utils"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
ociutils "zotregistry.io/zot/pkg/test/oci-utils"
|
||||
|
@ -815,5 +816,35 @@ func TestConvertErrors(t *testing.T) {
|
|||
)
|
||||
So(len(imgSums), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("RepoMeta2ExpandedRepoInfo - bad ctx value", func() {
|
||||
uacKey := reqCtx.GetContextKey()
|
||||
ctx := context.WithValue(ctx, uacKey, "bad context")
|
||||
|
||||
_, imgSums := convert.RepoMeta2ExpandedRepoInfo(ctx,
|
||||
mTypes.RepoMeta{},
|
||||
map[string]mTypes.ImageMeta{
|
||||
"digest": {},
|
||||
},
|
||||
convert.SkipQGLField{}, nil,
|
||||
log,
|
||||
)
|
||||
So(len(imgSums), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("RepoMeta2ExpandedRepoInfo - nil ctx value", func() {
|
||||
uacKey := reqCtx.GetContextKey()
|
||||
ctx := context.WithValue(ctx, uacKey, nil)
|
||||
|
||||
_, imgSums := convert.RepoMeta2ExpandedRepoInfo(ctx,
|
||||
mTypes.RepoMeta{},
|
||||
map[string]mTypes.ImageMeta{
|
||||
"digest": {},
|
||||
},
|
||||
convert.SkipQGLField{}, nil,
|
||||
log,
|
||||
)
|
||||
So(len(imgSums), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"zotregistry.io/zot/pkg/extensions/search/pagination"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
reqCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||
)
|
||||
|
||||
type SkipQGLField struct {
|
||||
|
@ -136,6 +137,8 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta mTypes.RepoMeta,
|
|||
repoName := repoMeta.Name
|
||||
imageSummaries := make([]*gql_generated.ImageSummary, 0, len(repoMeta.Tags))
|
||||
|
||||
userCanDeleteTag, _ := reqCtx.CanDelete(ctx, repoName)
|
||||
|
||||
for tag, descriptor := range repoMeta.Tags {
|
||||
imageMeta := imageMetaMap[descriptor.Digest]
|
||||
|
||||
|
@ -147,6 +150,8 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta mTypes.RepoMeta,
|
|||
continue
|
||||
}
|
||||
|
||||
imageSummary.IsDeletable = &userCanDeleteTag
|
||||
|
||||
updateImageSummaryVulnerabilities(ctx, imageSummary, skip, cveInfo)
|
||||
|
||||
imageSummaries = append(imageSummaries, imageSummary)
|
||||
|
|
|
@ -86,6 +86,7 @@ type ComplexityRoot struct {
|
|||
Digest func(childComplexity int) int
|
||||
Documentation func(childComplexity int) int
|
||||
DownloadCount func(childComplexity int) int
|
||||
IsDeletable func(childComplexity int) int
|
||||
IsSigned func(childComplexity int) int
|
||||
Labels func(childComplexity int) int
|
||||
LastUpdated func(childComplexity int) int
|
||||
|
@ -422,6 +423,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||
|
||||
return e.complexity.ImageSummary.DownloadCount(childComplexity), true
|
||||
|
||||
case "ImageSummary.IsDeletable":
|
||||
if e.complexity.ImageSummary.IsDeletable == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.ImageSummary.IsDeletable(childComplexity), true
|
||||
|
||||
case "ImageSummary.IsSigned":
|
||||
if e.complexity.ImageSummary.IsSigned == nil {
|
||||
break
|
||||
|
@ -1346,6 +1354,10 @@ type ImageSummary {
|
|||
Information about objects that reference this image
|
||||
"""
|
||||
Referrers: [Referrer]
|
||||
"""
|
||||
True if current user has delete permission on this tag.
|
||||
"""
|
||||
IsDeletable: Boolean
|
||||
}
|
||||
"""
|
||||
Details about a specific version of an image for a certain operating system and architecture.
|
||||
|
@ -2919,6 +2931,8 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Images(ctx context.C
|
|||
return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field)
|
||||
case "Referrers":
|
||||
return ec.fieldContext_ImageSummary_Referrers(ctx, field)
|
||||
case "IsDeletable":
|
||||
return ec.fieldContext_ImageSummary_IsDeletable(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name)
|
||||
},
|
||||
|
@ -4117,6 +4131,47 @@ func (ec *executionContext) fieldContext_ImageSummary_Referrers(ctx context.Cont
|
|||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _ImageSummary_IsDeletable(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_ImageSummary_IsDeletable(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.IsDeletable, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*bool)
|
||||
fc.Result = res
|
||||
return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_ImageSummary_IsDeletable(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "ImageSummary",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type Boolean does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _ImageVulnerabilitySummary_MaxSeverity(ctx context.Context, field graphql.CollectedField, obj *ImageVulnerabilitySummary) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_ImageVulnerabilitySummary_MaxSeverity(ctx, field)
|
||||
if err != nil {
|
||||
|
@ -5295,6 +5350,8 @@ func (ec *executionContext) fieldContext_PaginatedImagesResult_Results(ctx conte
|
|||
return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field)
|
||||
case "Referrers":
|
||||
return ec.fieldContext_ImageSummary_Referrers(ctx, field)
|
||||
case "IsDeletable":
|
||||
return ec.fieldContext_ImageSummary_IsDeletable(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name)
|
||||
},
|
||||
|
@ -6194,6 +6251,8 @@ func (ec *executionContext) fieldContext_Query_Image(ctx context.Context, field
|
|||
return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field)
|
||||
case "Referrers":
|
||||
return ec.fieldContext_ImageSummary_Referrers(ctx, field)
|
||||
case "IsDeletable":
|
||||
return ec.fieldContext_ImageSummary_IsDeletable(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name)
|
||||
},
|
||||
|
@ -6820,6 +6879,8 @@ func (ec *executionContext) fieldContext_RepoInfo_Images(ctx context.Context, fi
|
|||
return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field)
|
||||
case "Referrers":
|
||||
return ec.fieldContext_ImageSummary_Referrers(ctx, field)
|
||||
case "IsDeletable":
|
||||
return ec.fieldContext_ImageSummary_IsDeletable(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name)
|
||||
},
|
||||
|
@ -7179,6 +7240,8 @@ func (ec *executionContext) fieldContext_RepoSummary_NewestImage(ctx context.Con
|
|||
return ec.fieldContext_ImageSummary_Vulnerabilities(ctx, field)
|
||||
case "Referrers":
|
||||
return ec.fieldContext_ImageSummary_Referrers(ctx, field)
|
||||
case "IsDeletable":
|
||||
return ec.fieldContext_ImageSummary_IsDeletable(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type ImageSummary", field.Name)
|
||||
},
|
||||
|
@ -9652,6 +9715,8 @@ func (ec *executionContext) _ImageSummary(ctx context.Context, sel ast.Selection
|
|||
out.Values[i] = ec._ImageSummary_Vulnerabilities(ctx, field, obj)
|
||||
case "Referrers":
|
||||
out.Values[i] = ec._ImageSummary_Referrers(ctx, field, obj)
|
||||
case "IsDeletable":
|
||||
out.Values[i] = ec._ImageSummary_IsDeletable(ctx, field, obj)
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
|
|
@ -134,6 +134,8 @@ type ImageSummary struct {
|
|||
Vulnerabilities *ImageVulnerabilitySummary `json:"Vulnerabilities,omitempty"`
|
||||
// Information about objects that reference this image
|
||||
Referrers []*Referrer `json:"Referrers,omitempty"`
|
||||
// True if current user has delete permission on this tag.
|
||||
IsDeletable *bool `json:"IsDeletable,omitempty"`
|
||||
}
|
||||
|
||||
// Contains summary of vulnerabilities found in a specific image
|
||||
|
|
|
@ -200,6 +200,10 @@ type ImageSummary {
|
|||
Information about objects that reference this image
|
||||
"""
|
||||
Referrers: [Referrer]
|
||||
"""
|
||||
True if current user has delete permission on this tag.
|
||||
"""
|
||||
IsDeletable: Boolean
|
||||
}
|
||||
"""
|
||||
Details about a specific version of an image for a certain operating system and architecture.
|
||||
|
|
|
@ -246,10 +246,14 @@ func RepoIsUserAvailable(ctx context.Context, repoName string) (bool, error) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
// no authn/authz enabled on server
|
||||
if uac == nil {
|
||||
return true, nil
|
||||
return uac.Can(constants.ReadPermission, repoName), nil
|
||||
}
|
||||
|
||||
func CanDelete(ctx context.Context, repoName string) (bool, error) {
|
||||
uac, err := UserAcFromContext(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return uac.Can("read", repoName), nil
|
||||
return uac.Can(constants.DeletePermission, repoName), nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue