0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

fix(repoinfo): fix userprefs values for repos returned by expanded repo info (#1413)

- now isBookmarked and isStarred are updated correctly

Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
LaurentiuNiculae 2023-05-04 19:51:21 +03:00 committed by GitHub
parent e299ae199a
commit 449f0d0ac3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 377 additions and 6 deletions

View file

@ -1096,7 +1096,7 @@ func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cv
return &gql_generated.RepoInfo{}, nil //nolint:nilerr // don't give details to a potential attacker return &gql_generated.RepoInfo{}, nil //nolint:nilerr // don't give details to a potential attacker
} }
repoMeta, err := repoDB.GetRepoMeta(repo) repoMeta, err := repoDB.GetUserRepoMeta(ctx, repo)
if err != nil { if err != nil {
log.Error().Err(err).Str("repository", repo).Msg("resolver: can't retrieve repoMeta for repo") log.Error().Err(err).Str("repository", repo).Msg("resolver: can't retrieve repoMeta for repo")

View file

@ -3274,7 +3274,7 @@ func TestExpandedRepoInfo(t *testing.T) {
graphql.DefaultRecover) graphql.DefaultRecover)
repoDB := mocks.RepoDBMock{ repoDB := mocks.RepoDBMock{
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) { GetUserRepoMetaFn: func(ctx context.Context, repo string) (repodb.RepoMetadata, error) {
return repodb.RepoMetadata{ return repodb.RepoMetadata{
Tags: map[string]repodb.Descriptor{ Tags: map[string]repodb.Descriptor{
"tagManifest": { "tagManifest": {

View file

@ -4699,7 +4699,7 @@ func TestRepoDBIndexOperations(t *testing.T) {
func RunRepoDBIndexTests(baseURL, port string) { func RunRepoDBIndexTests(baseURL, port string) {
Convey("Push test index", func() { Convey("Push test index", func() {
repo := "repo" const repo = "repo"
multiarchImage, err := GetRandomMultiarchImage("tag1") multiarchImage, err := GetRandomMultiarchImage("tag1")
So(err, ShouldBeNil) So(err, ShouldBeNil)

View file

@ -806,6 +806,198 @@ func TestGlobalSearchWithUserPrefFiltering(t *testing.T) {
}) })
} }
func TestExpandedRepoInfoWithUserPrefs(t *testing.T) {
Convey("ExpandedRepoInfo with User Prefs", t, func() {
dir := t.TempDir()
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = dir
simpleUser := "simpleUser"
simpleUserPassword := "simpleUserPass"
credTests := fmt.Sprintf("%s\n\n", getCredString(simpleUser, simpleUserPassword))
htpasswdPath := MakeHtpasswdFileFromString(credTests)
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
conf.HTTP.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
"**": config.PolicyGroup{
Policies: []config.Policy{
{
Users: []string{simpleUser},
Actions: []string{"read", "create"},
},
},
},
},
}
defaultVal := true
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
}
ctlr := api.NewController(conf)
ctlrManager := NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
preferencesBaseURL := baseURL + constants.FullUserPreferencesPrefix
simpleUserClient := resty.R().SetBasicAuth(simpleUser, simpleUserPassword)
// ------ Add sbrepo and star/bookmark it
sbrepo := "sbrepo"
img, err := GetRandomImage("tag")
So(err, ShouldBeNil)
err = UploadImageWithBasicAuth(img, baseURL, sbrepo, simpleUser, simpleUserPassword)
So(err, ShouldBeNil)
resp, err := simpleUserClient.Put(preferencesBaseURL + PutRepoStarURL(sbrepo))
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
So(err, ShouldBeNil)
resp, err = simpleUserClient.Put(preferencesBaseURL + PutRepoBookmarkURL(sbrepo))
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
So(err, ShouldBeNil)
// ExpandedRepoinfo
query := `
{
ExpandedRepoInfo(repo:"sbrepo"){
Summary {
Name IsStarred IsBookmarked
}
}
}`
resp, err = simpleUserClient.Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
responseStruct := ExpandedRepoInfoResp{}
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
repoInfo := responseStruct.ExpandedRepoInfo.RepoInfo
So(repoInfo.Summary.IsBookmarked, ShouldBeTrue)
So(repoInfo.Summary.IsStarred, ShouldBeTrue)
// ------ Add srepo and star it
srepo := "srepo"
img, err = GetRandomImage("tag")
So(err, ShouldBeNil)
err = UploadImageWithBasicAuth(img, baseURL, srepo, simpleUser, simpleUserPassword)
So(err, ShouldBeNil)
resp, err = simpleUserClient.Put(preferencesBaseURL + PutRepoStarURL(srepo))
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
So(err, ShouldBeNil)
// ExpandedRepoinfo
query = `
{
ExpandedRepoInfo(repo:"srepo"){
Summary {
Name IsStarred IsBookmarked
}
}
}`
resp, err = simpleUserClient.Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
responseStruct = ExpandedRepoInfoResp{}
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
repoInfo = responseStruct.ExpandedRepoInfo.RepoInfo
So(repoInfo.Summary.IsBookmarked, ShouldBeFalse)
So(repoInfo.Summary.IsStarred, ShouldBeTrue)
// ------ Add brepo and bookmark it
brepo := "brepo"
img, err = GetRandomImage("tag")
So(err, ShouldBeNil)
err = UploadImageWithBasicAuth(img, baseURL, brepo, simpleUser, simpleUserPassword)
So(err, ShouldBeNil)
resp, err = simpleUserClient.Put(preferencesBaseURL + PutRepoBookmarkURL(brepo))
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
So(err, ShouldBeNil)
// ExpandedRepoinfo
query = `
{
ExpandedRepoInfo(repo:"brepo"){
Summary {
Name IsStarred IsBookmarked
}
}
}`
resp, err = simpleUserClient.Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
responseStruct = ExpandedRepoInfoResp{}
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
repoInfo = responseStruct.ExpandedRepoInfo.RepoInfo
So(repoInfo.Summary.IsBookmarked, ShouldBeTrue)
So(repoInfo.Summary.IsStarred, ShouldBeFalse)
// ------ Add repo without star/bookmark
repo := "repo"
img, err = GetRandomImage("tag")
So(err, ShouldBeNil)
err = UploadImageWithBasicAuth(img, baseURL, repo, simpleUser, simpleUserPassword)
So(err, ShouldBeNil)
// ExpandedRepoinfo
query = `
{
ExpandedRepoInfo(repo:"repo"){
Summary {
Name IsStarred IsBookmarked
}
}
}`
resp, err = simpleUserClient.Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query))
So(resp, ShouldNotBeNil)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 200)
responseStruct = ExpandedRepoInfoResp{}
err = json.Unmarshal(resp.Body(), &responseStruct)
So(err, ShouldBeNil)
repoInfo = responseStruct.ExpandedRepoInfo.RepoInfo
So(repoInfo.Summary.IsBookmarked, ShouldBeFalse)
So(repoInfo.Summary.IsStarred, ShouldBeFalse)
})
}
func PutRepoStarURL(repo string) string { func PutRepoStarURL(repo string) string {
return fmt.Sprintf("?repo=%s&action=toggleStar", repo) return fmt.Sprintf("?repo=%s&action=toggleStar", repo)
} }

View file

@ -533,6 +533,36 @@ func (bdw *DBWrapper) GetRepoMeta(repo string) (repodb.RepoMetadata, error) {
return repoMeta, err return repoMeta, err
} }
func (bdw *DBWrapper) GetUserRepoMeta(ctx context.Context, repo string) (repodb.RepoMetadata, error) {
var repoMeta repodb.RepoMetadata
err := bdw.DB.Update(func(tx *bbolt.Tx) error {
buck := tx.Bucket([]byte(bolt.RepoMetadataBucket))
userBookmarks := getUserBookmarks(ctx, tx)
userStars := getUserStars(ctx, tx)
repoMetaBlob := buck.Get([]byte(repo))
// object not found
if repoMetaBlob == nil {
return zerr.ErrRepoMetaNotFound
}
// object found
err := json.Unmarshal(repoMetaBlob, &repoMeta)
if err != nil {
return err
}
repoMeta.IsBookmarked = zcommon.Contains(userBookmarks, repo)
repoMeta.IsStarred = zcommon.Contains(userStars, repo)
return nil
})
return repoMeta, err
}
func (bdw *DBWrapper) SetRepoMeta(repo string, repoMeta repodb.RepoMetadata) error { func (bdw *DBWrapper) SetRepoMeta(repo string, repoMeta repodb.RepoMetadata) error {
err := bdw.DB.Update(func(tx *bbolt.Tx) error { err := bdw.DB.Update(func(tx *bbolt.Tx) error {
buck := tx.Bucket([]byte(bolt.RepoMetadataBucket)) buck := tx.Bucket([]byte(bolt.RepoMetadataBucket))

View file

@ -922,6 +922,30 @@ func TestWrapperErrors(t *testing.T) {
) )
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
Convey("GetUserRepoMeta unmarshal error", func() {
acCtx := localCtx.AccessControlContext{
ReadGlobPatterns: map[string]bool{
"repo": true,
},
Username: "username",
}
authzCtxKey := localCtx.GetContextKey()
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
repoBuck := tx.Bucket([]byte(bolt.RepoMetadataBucket))
err := repoBuck.Put([]byte("repo"), []byte("bad repo"))
So(err, ShouldBeNil)
return nil
})
So(err, ShouldBeNil)
_, err := boltdbWrapper.GetUserRepoMeta(ctx, "repo")
So(err, ShouldNotBeNil)
})
}) })
} }

View file

@ -1037,6 +1037,55 @@ func TestWrapperErrors(t *testing.T) {
err := dynamoWrapper.PatchDB() err := dynamoWrapper.PatchDB()
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("GetUserRepoMeta client.GetItem error", func() {
dynamoWrapper.RepoMetaTablename = badTablename
_, err = dynamoWrapper.GetUserRepoMeta(ctx, "repo")
So(err, ShouldNotBeNil)
})
Convey("GetUserRepoMeta repoMeta not found", func() {
_, err = dynamoWrapper.GetUserRepoMeta(ctx, "unknown-repo-meta")
So(err, ShouldNotBeNil)
})
Convey("GetUserRepoMeta userMeta not found", func() {
err := dynamoWrapper.SetRepoReference("repo", "tag", digest.FromString("1"), ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
dynamoWrapper.UserDataTablename = badTablename
acCtx := localCtx.AccessControlContext{
ReadGlobPatterns: map[string]bool{
"repo": true,
},
Username: "username",
}
authzCtxKey := localCtx.GetContextKey()
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
_, err = dynamoWrapper.GetUserRepoMeta(ctx, "repo")
So(err, ShouldNotBeNil)
})
Convey("GetUserRepoMeta unmarshal error", func() {
err := setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo")
So(err, ShouldBeNil)
acCtx := localCtx.AccessControlContext{
ReadGlobPatterns: map[string]bool{
"repo": true,
},
Username: "username",
}
authzCtxKey := localCtx.GetContextKey()
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
_, err = dynamoWrapper.GetUserRepoMeta(ctx, "repo")
So(err, ShouldNotBeNil)
})
}) })
Convey("NewDynamoDBWrapper errors", t, func() { Convey("NewDynamoDBWrapper errors", t, func() {

View file

@ -616,6 +616,39 @@ func (dwr *DBWrapper) GetRepoMeta(repo string) (repodb.RepoMetadata, error) {
return repoMeta, nil return repoMeta, nil
} }
func (dwr *DBWrapper) GetUserRepoMeta(ctx context.Context, repo string) (repodb.RepoMetadata, error) {
resp, err := dwr.Client.GetItem(ctx, &dynamodb.GetItemInput{
TableName: aws.String(dwr.RepoMetaTablename),
Key: map[string]types.AttributeValue{
"RepoName": &types.AttributeValueMemberS{Value: repo},
},
})
if err != nil {
return repodb.RepoMetadata{}, err
}
if resp.Item == nil {
return repodb.RepoMetadata{}, zerr.ErrRepoMetaNotFound
}
var repoMeta repodb.RepoMetadata
err = attributevalue.Unmarshal(resp.Item["RepoMetadata"], &repoMeta)
if err != nil {
return repodb.RepoMetadata{}, err
}
userMeta, err := dwr.GetUserMeta(ctx)
if err != nil {
return repodb.RepoMetadata{}, err
}
repoMeta.IsBookmarked = zcommon.Contains(userMeta.BookmarkedRepos, repo)
repoMeta.IsStarred = zcommon.Contains(userMeta.StarredRepos, repo)
return repoMeta, nil
}
func (dwr *DBWrapper) IncrementImageDownloads(repo string, reference string) error { func (dwr *DBWrapper) IncrementImageDownloads(repo string, reference string) error {
repoMeta, err := dwr.GetRepoMeta(repo) repoMeta, err := dwr.GetRepoMeta(repo)
if err != nil { if err != nil {
@ -1926,7 +1959,7 @@ func (dwr *DBWrapper) GetStarredRepos(ctx context.Context) ([]string, error) {
return userMeta.StarredRepos, err return userMeta.StarredRepos, err
} }
func (dwr DBWrapper) GetUserMeta(ctx context.Context) (repodb.UserData, error) { func (dwr *DBWrapper) GetUserMeta(ctx context.Context) (repodb.UserData, error) {
acCtx, err := localCtx.GetAccessControlContext(ctx) acCtx, err := localCtx.GetAccessControlContext(ctx)
if err != nil { if err != nil {
return repodb.UserData{}, err return repodb.UserData{}, err
@ -1963,7 +1996,7 @@ func (dwr DBWrapper) GetUserMeta(ctx context.Context) (repodb.UserData, error) {
return userMeta, nil return userMeta, nil
} }
func (dwr DBWrapper) createUserDataTable() error { func (dwr *DBWrapper) createUserDataTable() error {
_, err := dwr.Client.CreateTable(context.Background(), &dynamodb.CreateTableInput{ _, err := dwr.Client.CreateTable(context.Background(), &dynamodb.CreateTableInput{
TableName: aws.String(dwr.UserDataTablename), TableName: aws.String(dwr.UserDataTablename),
AttributeDefinitions: []types.AttributeDefinition{ AttributeDefinitions: []types.AttributeDefinition{
@ -1988,7 +2021,7 @@ func (dwr DBWrapper) createUserDataTable() error {
return dwr.waitTableToBeCreated(dwr.UserDataTablename) return dwr.waitTableToBeCreated(dwr.UserDataTablename)
} }
func (dwr DBWrapper) SetUserMeta(ctx context.Context, userMeta repodb.UserData) error { func (dwr *DBWrapper) SetUserMeta(ctx context.Context, userMeta repodb.UserData) error {
acCtx, err := localCtx.GetAccessControlContext(ctx) acCtx, err := localCtx.GetAccessControlContext(ctx)
if err != nil { if err != nil {
return err return err

View file

@ -47,6 +47,10 @@ type RepoDB interface { //nolint:interfacebloat
// GetRepoMeta returns RepoMetadata of a repo from the database // GetRepoMeta returns RepoMetadata of a repo from the database
GetRepoMeta(repo string) (RepoMetadata, error) GetRepoMeta(repo string) (RepoMetadata, error)
// GetUserRepometa return RepoMetadata of a repo from the database along side specific information about the
// user
GetUserRepoMeta(ctx context.Context, repo string) (RepoMetadata, error)
// GetRepoMeta returns RepoMetadata of a repo from the database // GetRepoMeta returns RepoMetadata of a repo from the database
SetRepoMeta(repo string, repoMeta RepoMetadata) error SetRepoMeta(repo string, repoMeta RepoMetadata) error

View file

@ -2511,6 +2511,35 @@ func RunRepoDBTests(repoDB repodb.RepoDB, preparationFuncs ...func() error) {
So(repoMetas[0].IsBookmarked, ShouldBeTrue) So(repoMetas[0].IsBookmarked, ShouldBeTrue)
So(repoMetas[0].IsStarred, ShouldBeTrue) So(repoMetas[0].IsStarred, ShouldBeTrue)
}) })
Convey("Test GetUserRepoMeta", func() {
authzCtxKey := localCtx.GetContextKey()
acCtx := localCtx.AccessControlContext{
ReadGlobPatterns: map[string]bool{
"repo": true,
},
Username: "user1",
}
ctx := context.WithValue(context.Background(), authzCtxKey, acCtx)
digest := godigest.FromString("1")
err := repoDB.SetRepoReference("repo", "tag", digest, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil)
_, err = repoDB.ToggleBookmarkRepo(ctx, "repo")
So(err, ShouldBeNil)
_, err = repoDB.ToggleStarRepo(ctx, "repo")
So(err, ShouldBeNil)
repoMeta, err := repoDB.GetUserRepoMeta(ctx, "repo")
So(err, ShouldBeNil)
So(repoMeta.IsBookmarked, ShouldBeTrue)
So(repoMeta.IsStarred, ShouldBeTrue)
So(repoMeta.Tags, ShouldContainKey, "tag")
})
}) })
} }

View file

@ -25,6 +25,8 @@ type RepoDBMock struct {
GetRepoMetaFn func(repo string) (repodb.RepoMetadata, error) GetRepoMetaFn func(repo string) (repodb.RepoMetadata, error)
GetUserRepoMetaFn func(ctx context.Context, repo string) (repodb.RepoMetadata, error)
SetRepoMetaFn func(repo string, repoMeta repodb.RepoMetadata) error SetRepoMetaFn func(repo string, repoMeta repodb.RepoMetadata) error
GetMultipleRepoMetaFn func(ctx context.Context, filter func(repoMeta repodb.RepoMetadata) bool, GetMultipleRepoMetaFn func(ctx context.Context, filter func(repoMeta repodb.RepoMetadata) bool,
@ -155,6 +157,14 @@ func (sdm RepoDBMock) GetRepoMeta(repo string) (repodb.RepoMetadata, error) {
return repodb.RepoMetadata{}, nil return repodb.RepoMetadata{}, nil
} }
func (sdm RepoDBMock) GetUserRepoMeta(ctx context.Context, repo string) (repodb.RepoMetadata, error) {
if sdm.GetUserRepoMetaFn != nil {
return sdm.GetUserRepoMetaFn(ctx, repo)
}
return repodb.RepoMetadata{}, nil
}
func (sdm RepoDBMock) SetRepoMeta(repo string, repoMeta repodb.RepoMetadata) error { func (sdm RepoDBMock) SetRepoMeta(repo string, repoMeta repodb.RepoMetadata) error {
if sdm.SetRepoMetaFn != nil { if sdm.SetRepoMetaFn != nil {
return sdm.SetRepoMetaFn(repo, repoMeta) return sdm.SetRepoMetaFn(repo, repoMeta)