diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 26c4ba29f..42202f4a5 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -43,21 +43,26 @@ paths: description: Unexpected internal errors. /projects: get: - summary: Return projects created by Harbor + summary: List projects description: | This endpoint returns all projects created by Harbor, and can be filtered by project name. parameters: - - name: project_name + - name: name in: query - description: Project name for filtering results. + description: The name of project. required: false type: string - - name: is_public + - name: public in: query - description: Public sign for filtering projects. + description: The project is public or private. required: false - type: integer + type: boolean format: int32 + - name: owner + in: query + description: The name of project owner. + required: false + type: string - name: page in: query type: integer diff --git a/src/common/dao/project.go b/src/common/dao/project.go index d0cb990e4..aa51cb353 100644 --- a/src/common/dao/project.go +++ b/src/common/dao/project.go @@ -190,7 +190,7 @@ func GetHasReadPermProjects(username string) ([]*models.Project, error) { // GetTotalOfProjects returns the total count of projects // according to the query conditions -func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) { +func GetTotalOfProjects(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) { var ( owner string @@ -210,7 +210,7 @@ func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) { } } - sql, params := projectQueryConditions(owner, name, public, member, role) + sql, params := projectQueryConditions(owner, name, public, member, role, base...) sql = `select count(*) ` + sql @@ -220,7 +220,7 @@ func GetTotalOfProjects(query *models.ProjectQueryParam) (int64, error) { } // GetProjects returns a project list according to the query conditions -func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) { +func GetProjects(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) { var ( owner string @@ -246,7 +246,7 @@ func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) { } } - sql, params := projectQueryConditions(owner, name, public, member, role) + sql, params := projectQueryConditions(owner, name, public, member, role, base...) sql = `select distinct p.project_id, p.name, p.public, p.owner_id, p.creation_time, p.update_time ` + sql @@ -266,10 +266,33 @@ func GetProjects(query *models.ProjectQueryParam) ([]*models.Project, error) { } func projectQueryConditions(owner, name string, public *bool, member string, - role int) (string, []interface{}) { + role int, base ...*models.BaseProjectCollection) (string, []interface{}) { params := []interface{}{} - sql := ` from project p` + // the base project collections: + // 1. all projects + // 2. public projects + // 3. public projects and projects which the user is a member of + collection := `project ` + if len(base) != 0 && base[0] != nil { + if len(base[0].Member) == 0 && base[0].Public { + collection = `(select * from project pr + where pr.public=1) ` + } + if len(base[0].Member) > 0 && base[0].Public { + collection = `(select pr.project_id, pr.owner_id, pr.name, pr. + creation_time, pr.update_time, pr.deleted, pr.public + from project pr + join project_member prm + on pr.project_id = prm.project_id + join user ur + on prm.user_id=ur.user_id + where ur.username=? or pr.public=1 )` + params = append(params, base[0].Member) + } + } + + sql := ` from ` + collection + ` as p` if len(owner) != 0 { sql += ` join user u1 diff --git a/src/common/models/project.go b/src/common/models/project.go index 883f32ce3..c5ab7e1e5 100644 --- a/src/common/models/project.go +++ b/src/common/models/project.go @@ -91,3 +91,11 @@ type Pagination struct { Page int64 Size int64 } + +// BaseProjectCollection contains the query conditions which can be used +// to get a project collection. The collection can be used as the base to +// do other filter +type BaseProjectCollection struct { + Public bool + Member string +} diff --git a/src/common/security/rbac/context_test.go b/src/common/security/rbac/context_test.go index d72d66426..f23958c58 100644 --- a/src/common/security/rbac/context_test.go +++ b/src/common/security/rbac/context_test.go @@ -106,7 +106,7 @@ func (f *fakePM) Update(projectIDOrName interface{}, project *models.Project) er } // nil implement -func (f *fakePM) GetAll(*models.ProjectQueryParam) ([]*models.Project, error) { +func (f *fakePM) GetAll(*models.ProjectQueryParam, ...*models.BaseProjectCollection) ([]*models.Project, error) { return []*models.Project{}, nil } @@ -116,7 +116,7 @@ func (f *fakePM) GetHasReadPerm(username ...string) ([]*models.Project, error) { } // nil implement -func (f *fakePM) GetTotal(*models.ProjectQueryParam) (int64, error) { +func (f *fakePM) GetTotal(*models.ProjectQueryParam, ...*models.BaseProjectCollection) (int64, error) { return 0, nil } diff --git a/src/ui/api/harborapi_test.go b/src/ui/api/harborapi_test.go index 29866c095..441eea29d 100644 --- a/src/ui/api/harborapi_test.go +++ b/src/ui/api/harborapi_test.go @@ -330,17 +330,10 @@ func (a testapi) ProjectsGetByPID(projectID string) (int, apilib.Project, error) } //Search projects by projectName and isPublic -func (a testapi) ProjectsGet(projectName string, isPublic int32, authInfo ...usrInfo) (int, []apilib.Project, error) { - _sling := sling.New().Get(a.basePath) - - //create api path - path := "api/projects" - _sling = _sling.Path(path) - type QueryParams struct { - ProjectName string `url:"project_name,omitempty"` - IsPubilc int32 `url:"is_public,omitempty"` - } - _sling = _sling.QueryStruct(&QueryParams{ProjectName: projectName, IsPubilc: isPublic}) +func (a testapi) ProjectsGet(query *apilib.ProjectQuery, authInfo ...usrInfo) (int, []apilib.Project, error) { + _sling := sling.New().Get(a.basePath). + Path("api/projects"). + QueryStruct(query) var successPayload []apilib.Project @@ -355,6 +348,8 @@ func (a testapi) ProjectsGet(projectName string, isPublic int32, authInfo ...usr if err == nil && httpStatusCode == 200 { err = json.Unmarshal(body, &successPayload) + } else { + log.Println(string(body)) } return httpStatusCode, successPayload, err diff --git a/src/ui/api/log_test.go b/src/ui/api/log_test.go index e86367bd9..cfef52579 100644 --- a/src/ui/api/log_test.go +++ b/src/ui/api/log_test.go @@ -23,28 +23,24 @@ import ( ) func TestLogGet(t *testing.T) { - fmt.Println("Testing Log API") - assert := assert.New(t) apiTest := newHarborAPI() + assert := assert.New(t) - //prepare for test CommonAddUser() - var project apilib.ProjectReq - project.ProjectName = "my_project" - project.Public = 1 - statusCode, result, err := apiTest.LogGet(*testUser) - if err != nil { - t.Error("Error while get log information", err.Error()) - t.Log(err) - } else { - assert.Equal(int(200), statusCode, "Log get should return 200") - } + statusCode, result, err := apiTest.LogGet(*testUser) + assert.Nil(err) + assert.Equal(200, statusCode) + logNum := len(result) - fmt.Println("result", result) - //add the project first. + fmt.Println("add the project first.") + project := apilib.ProjectReq{ + ProjectName: "project_for_test_log", + Public: 1, + } + reply, err := apiTest.ProjectsPost(*testUser, project) if err != nil { t.Error("Error while creat project", err.Error()) @@ -63,7 +59,7 @@ func TestLogGet(t *testing.T) { if num != 1 { assert.Equal(1, num, "add my_project log number should be 1") } else { - assert.Equal("my_project/", result[index].RepoName, "RepoName should be equal") + assert.Equal("project_for_test_log/", result[index].RepoName) assert.Equal("N/A", result[index].RepoTag, "RepoTag should be equal") assert.Equal("create", result[index].Operation, "Operation should be equal") } @@ -73,7 +69,12 @@ func TestLogGet(t *testing.T) { //get the project var projects []apilib.Project var addProjectID int32 - httpStatusCode, projects, err := apiTest.ProjectsGet(project.ProjectName, 1) + httpStatusCode, projects, err := apiTest.ProjectsGet( + &apilib.ProjectQuery{ + Name: project.ProjectName, + Owner: testUser.Name, + Public: true, + }) if err != nil { t.Error("Error while search project by proName and isPublic", err.Error()) t.Log(err) @@ -99,7 +100,7 @@ func TestLogGet(t *testing.T) { func getLog(result []apilib.AccessLog) (int, int) { var num, index int for i := 0; i < len(result); i++ { - if result[i].RepoName == "my_project/" { + if result[i].RepoName == "project_for_test_log/" { num++ index = i } diff --git a/src/ui/api/project.go b/src/ui/api/project.go index c2a48959c..a06be9e69 100644 --- a/src/ui/api/project.go +++ b/src/ui/api/project.go @@ -256,58 +256,56 @@ func projectContainsPolicy(id int64) (bool, error) { } // List ... -// TODO refacter pattern to: -// /api/repositories?owner=xxx&name=xxx&public=true&member=xxx&role=1&page=1&size=3 func (p *ProjectAPI) List() { - query := &models.ProjectQueryParam{} - - query.Name = p.GetString("project_name") - public := p.GetString("is_public") - if len(public) != 0 { - if public != "0" && public != "1" { - p.HandleBadRequest("is_public should be 0 or 1") - return - } - if public == "1" { - t := true - query.Public = &t - } + // query strings + page, size := p.GetPaginationParams() + query := &models.ProjectQueryParam{ + Name: p.GetString("name"), + Owner: p.GetString("owner"), + Pagination: &models.Pagination{ + Page: page, + Size: size, + }, } - if query.Public == nil || *query.Public == false { - //if the request is not for public projects, user must login or provide credential - if !p.SecurityCtx.IsAuthenticated() { - p.HandleUnauthorized() + public := p.GetString("public") + if len(public) > 0 { + pub, err := strconv.ParseBool(public) + if err != nil { + p.HandleBadRequest(fmt.Sprintf("invalid public: %s", public)) return } + query.Public = &pub + } + // base project collection from which filter is done + base := &models.BaseProjectCollection{} + if !p.SecurityCtx.IsAuthenticated() { + // not login, only get public projects + base.Public = true + } else { if !p.SecurityCtx.IsSysAdmin() { - query.Member = &models.Member{ - Name: p.SecurityCtx.GetUsername(), - } + // login, but not system admin, get public projects and + // projects that the user is member of + base.Member = p.SecurityCtx.GetUsername() + base.Public = true } } - total, err := p.ProjectMgr.GetTotal(query) + total, err := p.ProjectMgr.GetTotal(query, base) if err != nil { p.HandleInternalServerError(fmt.Sprintf("failed to get total of projects: %v", err)) return } - page, size := p.GetPaginationParams() - query.Pagination = &models.Pagination{ - Page: page, - Size: size, - } - - projects, err := p.ProjectMgr.GetAll(query) + projects, err := p.ProjectMgr.GetAll(query, base) if err != nil { p.HandleInternalServerError(fmt.Sprintf("failed to get projects: %v", err)) return } for _, project := range projects { - if query.Public == nil || *query.Public == false { + if p.SecurityCtx.IsAuthenticated() { roles, err := p.ProjectMgr.GetRoles(p.SecurityCtx.GetUsername(), project.ProjectID) if err != nil { p.HandleInternalServerError(fmt.Sprintf("failed to get roles of user %s to project %d: %v", diff --git a/src/ui/api/project_test.go b/src/ui/api/project_test.go index f819b53e4..0bc143d80 100644 --- a/src/ui/api/project_test.go +++ b/src/ui/api/project_test.go @@ -90,8 +90,7 @@ func TestAddProject(t *testing.T) { } -//Get project by proName -func TestProGetByName(t *testing.T) { +func TestListProjects(t *testing.T) { fmt.Println("\nTest for Project GET API by project name") assert := assert.New(t) @@ -100,29 +99,27 @@ func TestProGetByName(t *testing.T) { //----------------------------case 1 : Response Code=200----------------------------// fmt.Println("case 1: respose code:200") - httpStatusCode, result, err := apiTest.ProjectsGet(addProject.ProjectName, 1) - if err != nil { - t.Error("Error while search project by proName and isPublic", err.Error()) - t.Log(err) - } else { - assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") - assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong") - assert.Equal(int32(1), result[0].Public, "Public is wrong") - //find add projectID - addPID = int(result[0].ProjectId) - } - //----------------------------case 2 : Response Code=401:is_public=0----------------------------// - fmt.Println("case 2: respose code:401,isPublic = 0") - httpStatusCode, result, err = apiTest.ProjectsGet("library", 0) - if err != nil { - t.Error("Error while search project by proName and isPublic", err.Error()) - t.Log(err) - } else { - assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 200") - } + httpStatusCode, result, err := apiTest.ProjectsGet( + &apilib.ProjectQuery{ + Name: addProject.ProjectName, + Owner: admin.Name, + Public: true, + }) + assert.Nil(err) + assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") + assert.Equal(addProject.ProjectName, result[0].ProjectName, "Project name is wrong") + assert.Equal(int32(1), result[0].Public, "Public is wrong") + + //find add projectID + addPID = int(result[0].ProjectId) //-------------------case 3 : check admin project role------------------------// - httpStatusCode, result, err = apiTest.ProjectsGet(addProject.ProjectName, 0, *admin) + httpStatusCode, result, err = apiTest.ProjectsGet( + &apilib.ProjectQuery{ + Name: addProject.ProjectName, + Owner: admin.Name, + Public: true, + }, *admin) if err != nil { t.Error("Error while search project by proName and isPublic", err.Error()) t.Log(err) @@ -144,7 +141,10 @@ func TestProGetByName(t *testing.T) { } else { assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200") } - httpStatusCode, result, err = apiTest.ProjectsGet(addProject.ProjectName, 0, *testUser) + httpStatusCode, result, err = apiTest.ProjectsGet( + &apilib.ProjectQuery{ + Name: addProject.ProjectName, + }, *testUser) if err != nil { t.Error("Error while search project by proName and isPublic", err.Error()) t.Log(err) diff --git a/src/ui/projectmanager/db/pm.go b/src/ui/projectmanager/db/pm.go index dd8819d3e..a44f6a1ba 100644 --- a/src/ui/projectmanager/db/pm.go +++ b/src/ui/projectmanager/db/pm.go @@ -190,15 +190,15 @@ func (p *ProjectManager) Update(projectIDOrName interface{}, } // GetAll returns a project list according to the query parameters -func (p *ProjectManager) GetAll(query *models.ProjectQueryParam) ( +func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ( []*models.Project, error) { - return dao.GetProjects(query) + return dao.GetProjects(query, base...) } // GetTotal returns the total count according to the query parameters -func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam) ( +func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ( int64, error) { - return dao.GetTotalOfProjects(query) + return dao.GetTotalOfProjects(query, base...) } // GetHasReadPerm returns projects which are public or the user is a member of diff --git a/src/ui/projectmanager/pm.go b/src/ui/projectmanager/pm.go index 3e497a737..676dfe9c3 100644 --- a/src/ui/projectmanager/pm.go +++ b/src/ui/projectmanager/pm.go @@ -33,9 +33,9 @@ type ProjectManager interface { Delete(projectIDOrName interface{}) error Update(projectIDOrName interface{}, project *models.Project) error // GetAll returns a project list according to the query parameters - GetAll(query *models.ProjectQueryParam) ([]*models.Project, error) + GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) // GetTotal returns the total count according to the query parameters - GetTotal(query *models.ProjectQueryParam) (int64, error) + GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) // GetHasReadPerm returns a project list which the user has read // permission of. The list should contains all public projects and // projects which the user is a member of if the username is not nil diff --git a/src/ui/projectmanager/pms/pm.go b/src/ui/projectmanager/pms/pm.go index d5db15496..16b6692fb 100644 --- a/src/ui/projectmanager/pms/pm.go +++ b/src/ui/projectmanager/pms/pm.go @@ -371,12 +371,12 @@ func (p *ProjectManager) Update(projectIDOrName interface{}, project *models.Pro } // GetAll ... -func (p *ProjectManager) GetAll(query *models.ProjectQueryParam) ([]*models.Project, error) { +func (p *ProjectManager) GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error) { return nil, errors.New("get all projects is unsupported") } // GetTotal ... -func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam) (int64, error) { +func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error) { return 0, errors.New("get total of projects is unsupported") } diff --git a/tests/apitests/apilib/access_log_filter.go b/tests/apitests/apilib/access_log_filter.go index 0b6b63d68..9a3a38ad7 100644 --- a/tests/apitests/apilib/access_log_filter.go +++ b/tests/apitests/apilib/access_log_filter.go @@ -23,12 +23,12 @@ package apilib type LogQuery struct { - Username string `json:"username"` - Repository string `json:"repository"` - Tag string `json:"tag"` - Operation []string `json:"operation"` - BeginTimestamp int64 `json:"begin_timestamp"` - EndTimestamp int64 `json:"end_timestamp"` - Page int64 `json:"page"` - PageSize int64 `json:"page_size"` + Username string `url:"username,omitempty"` + Repository string `url:"repository,omitempty"` + Tag string `url:"tag,omitempty"` + Operation []string `url:"operation,omitempty"` + BeginTimestamp int64 `url:"begin_timestamp,omitempty"` + EndTimestamp int64 `url:"end_timestamp,omitempty"` + Page int64 `url:"page,omitempty"` + PageSize int64 `url:"page_size,omitempty"` } diff --git a/tests/apitests/apilib/project.go b/tests/apitests/apilib/project.go index a7bde6f6e..7aae3714d 100644 --- a/tests/apitests/apilib/project.go +++ b/tests/apitests/apilib/project.go @@ -57,3 +57,13 @@ type Project struct { // The number of the repositories under this project. RepoCount int32 `json:"repo_count,omitempty"` } + +type ProjectQuery struct { + Name string `url:"name,omitempty"` + Owner string `url:"owner,omitempty"` + Public bool `url:"public,omitempty"` + Member string `url:"member,omitempty"` + Role int `url:"role,omitempty"` + Page int64 `url:"page,omitempty"` + PageSize int64 `url:"page_size,omitempty"` +}