refactor project api

This commit is contained in:
Wenkai Yin 2017-05-16 13:59:40 +08:00
parent e28ad39430
commit e1c1b8ec34
15 changed files with 573 additions and 393 deletions

View File

@ -39,6 +39,17 @@ type BaseAPI struct {
beego.Controller
}
// GetStringFromPath gets the param from path and returns it as string
func (b *BaseAPI) GetStringFromPath(key string) string {
return b.Ctx.Input.Param(key)
}
// GetInt64FromPath gets the param from path and returns it as int64
func (b *BaseAPI) GetInt64FromPath(key string) (int64, error) {
value := b.Ctx.Input.Param(key)
return strconv.ParseInt(value, 10, 64)
}
// HandleNotFound ...
func (b *BaseAPI) HandleNotFound(text string) {
log.Info(text)

View File

@ -24,10 +24,9 @@ const (
LDAPScopeOnelevel = "2"
LDAPScopeSubtree = "3"
RoleSystemAdmin = 1
RoleProjectAdmin = 2
RoleDeveloper = 3
RoleGuest = 4
RoleProjectAdmin = 1
RoleDeveloper = 2
RoleGuest = 3
DeployModeStandAlone = "standalone"
DeployModeIntegration = "integration"

View File

@ -801,32 +801,8 @@ func TestProjectPermission(t *testing.T) {
}
}
func TestGetTotalOfUserRelevantProjects(t *testing.T) {
total, err := GetTotalOfUserRelevantProjects(currentUser.UserID, "")
if err != nil {
t.Fatalf("failed to get total of user relevant projects: %v", err)
}
if total != 1 {
t.Errorf("unexpected total: %d != 1", total)
}
}
func TestGetUserRelevantProjects(t *testing.T) {
projects, err := GetUserRelevantProjects(currentUser.UserID, "")
if err != nil {
t.Errorf("Error occurred in GetUserRelevantProjects: %v", err)
}
if len(projects) != 1 {
t.Errorf("Expected length of relevant projects is 1, but actual: %d, the projects: %+v", len(projects), projects)
}
if projects[0].Name != projectName {
t.Errorf("Expected project name in the list: %s, actual: %s", projectName, projects[1].Name)
}
}
func TestGetTotalOfProjects(t *testing.T) {
total, err := GetTotalOfProjects("")
total, err := GetTotalOfProjects("", "", "", "", 0)
if err != nil {
t.Fatalf("failed to get total of projects: %v", err)
}
@ -837,7 +813,7 @@ func TestGetTotalOfProjects(t *testing.T) {
}
func TestGetProjects(t *testing.T) {
projects, err := GetProjects("")
projects, err := GetProjects("", "", "", "", 0, 0, 0)
if err != nil {
t.Errorf("Error occurred in GetAllProjects: %v", err)
}
@ -850,7 +826,7 @@ func TestGetProjects(t *testing.T) {
}
func TestGetPublicProjects(t *testing.T) {
projects, err := GetProjects("", 1)
projects, err := GetProjects("", "", "true", "", 0, 0, 0)
if err != nil {
t.Errorf("Error occurred in getProjects: %v", err)
}

View File

@ -15,6 +15,7 @@
package dao
import (
"github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/models"
"fmt"
@ -159,7 +160,7 @@ func ToggleProjectPublicity(projectID int64, publicity int) error {
// which satisfies the following conditions:
// 1. the project is not deleted
// 2. the prject is public or the user is a member of the project
func SearchProjects(userID int) ([]models.Project, error) {
func SearchProjects(userID int) ([]*models.Project, error) {
o := GetOrmer()
sql :=
@ -171,7 +172,7 @@ func SearchProjects(userID int) ([]models.Project, error) {
where (pm.user_id = ? or p.public = 1)
and p.deleted = 0 `
var projects []models.Project
var projects []*models.Project
if _, err := o.Raw(sql, userID).QueryRows(&projects); err != nil {
return nil, err
@ -180,101 +181,104 @@ func SearchProjects(userID int) ([]models.Project, error) {
return projects, nil
}
//GetTotalOfUserRelevantProjects returns the total count of
// user relevant projects
func GetTotalOfUserRelevantProjects(userID int, projectName string) (int64, error) {
o := GetOrmer()
sql := `select count(*) from project p
left join project_member pm
on p.project_id = pm.project_id
where p.deleted = 0 and pm.user_id= ?`
// GetTotalOfProjects returns the total count of projects
// according to the query conditions
func GetTotalOfProjects(owner, name, public, member string,
role int) (int64, error) {
sql, params := queryConditions(owner, name, public, member, role)
queryParam := []interface{}{}
queryParam = append(queryParam, userID)
if projectName != "" {
sql += " and p.name like ? "
queryParam = append(queryParam, "%"+escape(projectName)+"%")
}
sql = `select count(*) ` + sql
var total int64
err := o.Raw(sql, queryParam).QueryRow(&total)
err := GetOrmer().Raw(sql, params).QueryRow(&total)
return total, err
}
// GetUserRelevantProjects returns the user relevant projects
// args[0]: public, args[1]: limit, args[2]: offset
func GetUserRelevantProjects(userID int, projectName string, args ...int64) ([]models.Project, error) {
return getProjects(userID, projectName, args...)
// GetProjects returns a project list according to the query conditions
func GetProjects(owner, name, public, member string,
role int, page, size int64) ([]*models.Project, error) {
sql, params := queryConditions(owner, name, public, member, role)
sql = `select distinct p.project_id, p.name, p.public, p.owner_id,
p.creation_time, p.update_time ` + sql
if size > 0 {
sql += ` limit ?`
params = append(params, size)
if page > 0 {
sql += ` offset ?`
params = append(params, (page-1)*size)
}
}
// GetTotalOfProjects returns the total count of projects
func GetTotalOfProjects(name string, public ...int) (int64, error) {
qs := GetOrmer().
QueryTable(new(models.Project)).
Filter("Deleted", 0)
if len(name) > 0 {
qs = qs.Filter("Name__icontains", name)
}
if len(public) > 0 {
qs = qs.Filter("Public", public[0])
}
return qs.Count()
}
// GetProjects returns project list
// args[0]: public, args[1]: limit, args[2]: offset
func GetProjects(name string, args ...int64) ([]models.Project, error) {
return getProjects(0, name, args...)
}
func getProjects(userID int, name string, args ...int64) ([]models.Project, error) {
projects := []models.Project{}
o := GetOrmer()
sql := ""
queryParam := []interface{}{}
if userID != 0 { //get user's projects
sql = `select distinct p.project_id, p.owner_id, p.name,
p.creation_time, p.update_time, p.public, pm.role role
from project p
left join project_member pm
on p.project_id = pm.project_id
where p.deleted = 0 and pm.user_id= ?`
queryParam = append(queryParam, userID)
} else { // get all projects
sql = `select * from project p where p.deleted = 0 `
}
if name != "" {
sql += ` and p.name like ? `
queryParam = append(queryParam, "%"+escape(name)+"%")
}
switch len(args) {
case 1:
sql += ` and p.public = ?`
queryParam = append(queryParam, args[0])
sql += ` order by p.name `
case 2:
sql += ` order by p.name `
sql = paginateForRawSQL(sql, args[0], args[1])
case 3:
sql += ` and p.public = ?`
queryParam = append(queryParam, args[0])
sql += ` order by p.name `
sql = paginateForRawSQL(sql, args[1], args[2])
}
_, err := o.Raw(sql, queryParam).QueryRows(&projects)
var projects []*models.Project
_, err := GetOrmer().Raw(sql, params).QueryRows(&projects)
return projects, err
}
func queryConditions(owner, name, public, member string,
role int) (string, []interface{}) {
params := []interface{}{}
sql := ` from project p`
if len(owner) != 0 {
sql += ` join user u1
on p.owner_id = u1.user_id`
}
if len(member) != 0 {
sql += ` join project_member pm
on p.project_id = pm.project_id
join user u2
on pm.user_id=u2.user_id`
}
sql += ` where p.deleted=0`
if len(owner) != 0 {
sql += ` and u1.username=?`
params = append(params, owner)
}
if len(name) != 0 {
sql += ` and p.name like ?`
params = append(params, "%"+escape(name)+"%")
}
if len(public) != 0 {
sql += ` and p.public = ?`
if public == "true" {
params = append(params, 1)
} else {
params = append(params, 0)
}
}
if len(member) != 0 {
sql += ` and u2.username=?`
params = append(params, member)
if role > 0 {
sql += ` and pm.role = ?`
roleID := 0
switch role {
case common.RoleProjectAdmin:
roleID = 1
case common.RoleDeveloper:
roleID = 2
case common.RoleGuest:
roleID = 3
}
params = append(params, roleID)
}
}
sql += ` order by p.name`
return sql, params
}
// DeleteProject ...
func DeleteProject(id int64) error {
project, err := GetProjectByID(id)

View File

@ -19,6 +19,7 @@ import (
)
// AccessLog holds information about logs which are used to record the actions that user take to the resourses.
// TODO remove useless attrs
type AccessLog struct {
LogID int `orm:"pk;auto;column(log_id)" json:"log_id"`
Username string `orm:"column(username)" json:"username"`

View File

@ -19,6 +19,7 @@ import (
)
// Project holds the details of a project.
// TODO remove useless attrs
type Project struct {
ProjectID int64 `orm:"pk;auto;column(project_id)" json:"project_id"`
OwnerID int `orm:"column(owner_id)" json:"owner_id"`
@ -39,7 +40,7 @@ type Project struct {
// ProjectSorter holds an array of projects
type ProjectSorter struct {
Projects []Project
Projects []*Project
}
// Len returns the length of array in ProjectSorter

View File

@ -15,6 +15,7 @@
package rbac
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
@ -80,13 +81,34 @@ func (f *fakePM) Exist(projectIDOrName interface{}) bool {
}
// nil implement
func (f *fakePM) GetPublic() []models.Project {
return []models.Project{}
func (f *fakePM) GetPublic() []*models.Project {
return []*models.Project{}
}
// nil implement
func (f *fakePM) GetByMember(username string) []models.Project {
return []models.Project{}
func (f *fakePM) GetByMember(username string) []*models.Project {
return []*models.Project{}
}
// nil implement
func (f *fakePM) Create(*models.Project) (int64, error) {
return 0, fmt.Errorf("not support")
}
// nil implement
func (f *fakePM) Delete(projectIDOrName interface{}) error {
return fmt.Errorf("not support")
}
// nil implement
func (f *fakePM) Update(projectIDOrName interface{}, project *models.Project) error {
return fmt.Errorf("not support")
}
// nil implement
func (f *fakePM) GetAll(owner, name, public, member string, role int, page,
size int64) ([]*models.Project, int64) {
return []*models.Project{}, 0
}
func TestIsAuthenticated(t *testing.T) {

View File

@ -90,7 +90,7 @@ func (d *Deleter) enter() (string, error) {
// delete repository
if len(d.tags) == 0 {
u := url + d.repository + "/tags"
u := url + d.repository
if err := del(u, d.dstUsr, d.dstPwd, d.insecure); err != nil {
if err == errNotFound {
d.logger.Warningf("repository %s does not exist on %s", d.repository, d.dstURL)

View File

@ -19,7 +19,7 @@ import (
"net/http"
"regexp"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
@ -31,10 +31,7 @@ import (
// ProjectAPI handles request to /api/projects/{} /api/projects/{}/logs
type ProjectAPI struct {
api.BaseAPI
userID int
projectID int64
projectName string
BaseController
}
type projectReq struct {
@ -47,35 +44,11 @@ const projectNameMinLen int = 2
const restrictedNameChars = `[a-z0-9]+(?:[._-][a-z0-9]+)*`
const dupProjectPattern = `Duplicate entry '\w+' for key 'name'`
// Prepare validates the URL and the user
func (p *ProjectAPI) Prepare() {
idStr := p.Ctx.Input.Param(":id")
if len(idStr) > 0 {
var err error
p.projectID, err = strconv.ParseInt(idStr, 10, 64)
if err != nil {
log.Errorf("Error parsing project id: %s, error: %v", idStr, err)
p.CustomAbort(http.StatusBadRequest, "invalid project id")
}
project, err := dao.GetProjectByID(p.projectID)
if err != nil {
log.Errorf("failed to get project %d: %v", p.projectID, err)
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if project == nil {
p.CustomAbort(http.StatusNotFound, fmt.Sprintf("project does not exist, id: %v", p.projectID))
}
p.projectName = project.Name
}
}
// Post ...
func (p *ProjectAPI) Post() {
p.userID = p.ValidateUser()
isSysAdmin, err := dao.IsAdminRole(p.userID)
if err != nil {
log.Errorf("Failed to check admin role: %v", err)
if !p.SecurityCtx.IsAuthenticated() {
p.HandleUnauthorized()
return
}
onlyAdmin, err := config.OnlyAdminCreateProject()
@ -83,31 +56,30 @@ func (p *ProjectAPI) Post() {
log.Errorf("failed to determine whether only admin can create projects: %v", err)
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if !isSysAdmin && onlyAdmin {
if onlyAdmin && !p.SecurityCtx.IsSysAdmin() {
log.Errorf("Only sys admin can create project")
p.RenderError(http.StatusForbidden, "Only system admin can create project")
return
}
var req projectReq
p.DecodeJSONReq(&req)
public := req.Public
err = validateProjectReq(req)
var pro projectReq
p.DecodeJSONReq(&pro)
err = validateProjectReq(pro)
if err != nil {
log.Errorf("Invalid project request, error: %v", err)
p.RenderError(http.StatusBadRequest, fmt.Sprintf("invalid request: %v", err))
return
}
projectName := req.ProjectName
exist, err := dao.ProjectExists(projectName)
if err != nil {
log.Errorf("Error happened checking project existence in db, error: %v, project name: %s", err, projectName)
}
if exist {
if p.ProjectMgr.Exist(pro.ProjectName) {
p.RenderError(http.StatusConflict, "")
return
}
project := models.Project{OwnerID: p.userID, Name: projectName, CreationTime: time.Now(), Public: public}
projectID, err := dao.AddProject(project)
projectID, err := p.ProjectMgr.Create(&models.Project{
Name: pro.ProjectName,
Public: pro.Public,
OwnerName: p.SecurityCtx.GetUsername(),
})
if err != nil {
log.Errorf("Failed to add project, error: %v", err)
dup, _ := regexp.MatchString(dupProjectPattern, err.Error())
@ -120,24 +92,15 @@ func (p *ProjectAPI) Post() {
}
go func() {
user, err := dao.GetUser(models.User{
UserID: p.userID,
})
if err != nil {
log.Errorf("failed to get user by ID %d: %v", p.userID, err)
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
accessLog := models.AccessLog{
Username: user.Username,
if err = dao.AddAccessLog(
models.AccessLog{
Username: p.SecurityCtx.GetUsername(),
ProjectID: projectID,
RepoName: project.Name + "/",
RepoName: pro.ProjectName + "/",
RepoTag: "N/A",
GUID: "N/A",
Operation: "create",
OpTime: time.Now(),
}
if err = dao.AddAccessLog(accessLog); err != nil {
}); err != nil {
log.Errorf("failed to add access log: %v", err)
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
@ -148,40 +111,48 @@ func (p *ProjectAPI) Post() {
// Head ...
func (p *ProjectAPI) Head() {
projectName := p.GetString("project_name")
if len(projectName) == 0 {
p.CustomAbort(http.StatusBadRequest, "project_name is needed")
}
project, err := dao.GetProjectByName(projectName)
if err != nil {
log.Errorf("error occurred in GetProjectByName: %v", err)
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
// only public project can be Headed by user without login
if project != nil && project.Public == 1 {
name := p.GetString("project_name")
if len(name) == 0 {
p.HandleBadRequest("project_name is needed")
return
}
_ = p.ValidateUser()
project := p.ProjectMgr.Get(name)
if project == nil {
p.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
p.HandleNotFound(fmt.Sprintf("project %s not found", name))
return
}
}
// Get ...
func (p *ProjectAPI) Get() {
project, err := dao.GetProjectByID(p.projectID)
id, err := p.GetInt64FromPath(":id")
if err != nil || id <= 0 {
text := "invalid project ID: "
if err != nil {
log.Errorf("failed to get project %d: %v", p.projectID, err)
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
text += err.Error()
} else {
text += fmt.Sprintf("%d", id)
}
p.HandleBadRequest(text)
return
}
project := p.ProjectMgr.Get(id)
if project == nil {
p.HandleNotFound(fmt.Sprintf("project %d not found", id))
return
}
if project.Public == 0 {
userID := p.ValidateUser()
if !checkProjectPermission(userID, p.projectID) {
p.CustomAbort(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
if !p.SecurityCtx.IsAuthenticated() {
p.HandleUnauthorized()
return
}
if !p.SecurityCtx.HasReadPerm(id) {
p.HandleForbidden(p.SecurityCtx.GetUsername())
return
}
}
@ -191,52 +162,63 @@ func (p *ProjectAPI) Get() {
// Delete ...
func (p *ProjectAPI) Delete() {
if p.projectID == 0 {
p.CustomAbort(http.StatusBadRequest, "project ID is required")
}
userID := p.ValidateUser()
if !hasProjectAdminRole(userID, p.projectID) {
p.CustomAbort(http.StatusForbidden, "")
}
contains, err := projectContainsRepo(p.projectName)
id, err := p.GetInt64FromPath(":id")
if err != nil || id <= 0 {
text := "invalid project ID: "
if err != nil {
log.Errorf("failed to check whether project %s contains any repository: %v", p.projectName, err)
text += err.Error()
} else {
text += fmt.Sprintf("%d", id)
}
p.HandleBadRequest(text)
return
}
project := p.ProjectMgr.Get(id)
if project == nil {
p.HandleNotFound(fmt.Sprintf("project %d not found", id))
return
}
if !p.SecurityCtx.IsAuthenticated() {
p.HandleUnauthorized()
return
}
if !p.SecurityCtx.HasAllPerm(id) {
p.HandleForbidden(p.SecurityCtx.GetUsername())
return
}
contains, err := projectContainsRepo(project.Name)
if err != nil {
log.Errorf("failed to check whether project %s contains any repository: %v", project.Name, err)
p.CustomAbort(http.StatusInternalServerError, "")
}
if contains {
p.CustomAbort(http.StatusPreconditionFailed, "project contains repositores, can not be deleted")
}
contains, err = projectContainsPolicy(p.projectID)
contains, err = projectContainsPolicy(id)
if err != nil {
log.Errorf("failed to check whether project %s contains any policy: %v", p.projectName, err)
log.Errorf("failed to check whether project %s contains any policy: %v", project.Name, err)
p.CustomAbort(http.StatusInternalServerError, "")
}
if contains {
p.CustomAbort(http.StatusPreconditionFailed, "project contains policies, can not be deleted")
}
if err = dao.DeleteProject(p.projectID); err != nil {
log.Errorf("failed to delete project %d: %v", p.projectID, err)
p.CustomAbort(http.StatusInternalServerError, "")
if err = p.ProjectMgr.Delete(id); err != nil {
p.HandleInternalServerError(
fmt.Sprintf("failed to delete project %d: %v", id, err))
return
}
go func() {
user, err := dao.GetUser(models.User{
UserID: userID,
})
if err != nil {
log.Errorf("failed to get user by ID %d: %v", userID, err)
p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
if err := dao.AddAccessLog(models.AccessLog{
Username: user.Username,
ProjectID: p.projectID,
RepoName: p.projectName + "/",
Username: p.SecurityCtx.GetUsername(),
ProjectID: id,
RepoName: project.Name + "/",
RepoTag: "N/A",
Operation: "delete",
OpTime: time.Now(),
@ -265,139 +247,159 @@ 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() {
var total int64
var public int
var err error
page, pageSize := p.GetPaginationParams()
// query conditions:
var (
owner string // the username of project owner
name string // the project name
public string // the project is public or not
member string // the username of the member
role int // role of the member specified by member parameter
page int64 // pagination
size int64 // pagination
)
var projectList []models.Project
projectName := p.GetString("project_name")
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" {
public = "true"
}
}
isPublic := p.GetString("is_public")
if len(isPublic) > 0 {
public, err = strconv.Atoi(isPublic)
if err != nil {
log.Errorf("Error parsing public property: %v, error: %v", isPublic, err)
p.CustomAbort(http.StatusBadRequest, "invalid project Id")
}
}
isAdmin := false
if public == 1 {
total, err = dao.GetTotalOfProjects(projectName, 1)
if err != nil {
log.Errorf("failed to get total of projects: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")
}
projectList, err = dao.GetProjects(projectName, 1, pageSize, pageSize*(page-1))
if err != nil {
log.Errorf("failed to get projects: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")
}
} else {
page, size = p.GetPaginationParams()
if public != "true" {
//if the request is not for public projects, user must login or provide credential
p.userID = p.ValidateUser()
isAdmin, err = dao.IsAdminRole(p.userID)
if err != nil {
log.Errorf("Error occured in check admin, error: %v", err)
p.CustomAbort(http.StatusInternalServerError, "Internal error.")
}
if isAdmin {
total, err = dao.GetTotalOfProjects(projectName)
if err != nil {
log.Errorf("failed to get total of projects: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")
}
projectList, err = dao.GetProjects(projectName, pageSize, pageSize*(page-1))
if err != nil {
log.Errorf("failed to get projects: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")
}
} else {
total, err = dao.GetTotalOfUserRelevantProjects(p.userID, projectName)
if err != nil {
log.Errorf("failed to get total of projects: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")
}
projectList, err = dao.GetUserRelevantProjects(p.userID, projectName, pageSize, pageSize*(page-1))
if err != nil {
log.Errorf("failed to get projects: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")
if !p.SecurityCtx.IsAuthenticated() {
p.HandleUnauthorized()
return
}
public = ""
member = p.SecurityCtx.GetUsername()
if p.SecurityCtx.IsSysAdmin() {
member = ""
}
}
for i := 0; i < len(projectList); i++ {
if public != 1 {
roles, err := dao.GetUserProjectRoles(p.userID, projectList[i].ProjectID)
if err != nil {
log.Errorf("failed to get user's project role: %v", err)
p.CustomAbort(http.StatusInternalServerError, "")
}
projects, total := p.ProjectMgr.GetAll(owner, name, public, member,
role, page, size)
for _, project := range projects {
if public != "true" {
roles := p.ProjectMgr.GetRoles(p.SecurityCtx.GetUsername(), project.ProjectID)
if len(roles) != 0 {
projectList[i].Role = roles[0].RoleID
project.Role = roles[0]
}
if projectList[i].Role == models.PROJECTADMIN ||
isAdmin {
projectList[i].Togglable = true
if project.Role == common.RoleProjectAdmin ||
p.SecurityCtx.IsSysAdmin() {
project.Togglable = true
}
}
repos, err := dao.GetRepositoryByProjectName(projectList[i].Name)
repos, err := dao.GetRepositoryByProjectName(project.Name)
if err != nil {
log.Errorf("failed to get repositories of project %s: %v", projectList[i].Name, err)
log.Errorf("failed to get repositories of project %s: %v", project.Name, err)
p.CustomAbort(http.StatusInternalServerError, "")
}
projectList[i].RepoCount = len(repos)
project.RepoCount = len(repos)
}
p.SetPaginationHeader(total, page, pageSize)
p.Data["json"] = projectList
p.SetPaginationHeader(total, page, size)
p.Data["json"] = projects
p.ServeJSON()
}
// ToggleProjectPublic ...
func (p *ProjectAPI) ToggleProjectPublic() {
p.userID = p.ValidateUser()
id, err := p.GetInt64FromPath(":id")
if err != nil || id <= 0 {
text := "invalid project ID: "
if err != nil {
text += err.Error()
} else {
text += fmt.Sprintf("%d", id)
}
p.HandleBadRequest(text)
return
}
project := p.ProjectMgr.Get(id)
if project == nil {
p.HandleNotFound(fmt.Sprintf("project %d not found", id))
return
}
if !p.SecurityCtx.IsAuthenticated() {
p.HandleUnauthorized()
return
}
if !p.SecurityCtx.HasAllPerm(id) {
p.HandleForbidden(p.SecurityCtx.GetUsername())
return
}
var req projectReq
projectID, err := strconv.ParseInt(p.Ctx.Input.Param(":id"), 10, 64)
if err != nil {
log.Errorf("Error parsing project id: %d, error: %v", projectID, err)
p.RenderError(http.StatusBadRequest, "invalid project id")
return
}
p.DecodeJSONReq(&req)
public := req.Public
if !isProjectAdmin(p.userID, projectID) {
log.Warningf("Current user, id: %d does not have project admin role for project, id: %d", p.userID, projectID)
p.RenderError(http.StatusForbidden, "")
if req.Public != 0 && req.Public != 1 {
p.HandleBadRequest("public should be 0 or 1")
return
}
err = dao.ToggleProjectPublicity(p.projectID, public)
if err != nil {
log.Errorf("Error while updating project, project id: %d, error: %v", projectID, err)
p.RenderError(http.StatusInternalServerError, "Failed to update project")
if err := p.ProjectMgr.Update(id, &models.Project{
Public: req.Public,
}); err != nil {
p.HandleInternalServerError(fmt.Sprintf("failed to update project %d: %v", id, err))
return
}
}
// FilterAccessLog handles GET to /api/projects/{}/logs
func (p *ProjectAPI) FilterAccessLog() {
p.userID = p.ValidateUser()
id, err := p.GetInt64FromPath(":id")
if err != nil || id <= 0 {
text := "invalid project ID: "
if err != nil {
text += err.Error()
} else {
text += fmt.Sprintf("%d", id)
}
p.HandleBadRequest(text)
return
}
project := p.ProjectMgr.Get(id)
if project == nil {
p.HandleNotFound(fmt.Sprintf("project %d not found", id))
return
}
if !p.SecurityCtx.IsAuthenticated() {
p.HandleUnauthorized()
return
}
if !p.SecurityCtx.HasReadPerm(id) {
p.HandleForbidden(p.SecurityCtx.GetUsername())
return
}
var query models.AccessLog
p.DecodeJSONReq(&query)
if !checkProjectPermission(p.userID, p.projectID) {
log.Warningf("Current user, user id: %d does not have permission to read accesslog of project, id: %d", p.userID, p.projectID)
p.RenderError(http.StatusForbidden, "")
return
}
query.ProjectID = p.projectID
query.ProjectID = id
query.BeginTime = time.Unix(query.BeginTimestamp, 0)
query.EndTime = time.Unix(query.EndTimestamp, 0)
@ -422,34 +424,7 @@ func (p *ProjectAPI) FilterAccessLog() {
p.ServeJSON()
}
func isProjectAdmin(userID int, pid int64) bool {
isSysAdmin, err := dao.IsAdminRole(userID)
if err != nil {
log.Errorf("Error occurred in IsAdminRole, returning false, error: %v", err)
return false
}
if isSysAdmin {
return true
}
rolelist, err := dao.GetUserProjectRoles(userID, pid)
if err != nil {
log.Errorf("Error occurred in GetUserProjectRoles, returning false, error: %v", err)
return false
}
hasProjectAdminRole := false
for _, role := range rolelist {
if role.RoleID == models.PROJECTADMIN {
hasProjectAdminRole = true
break
}
}
return hasProjectAdminRole
}
// TODO move this to package models
func validateProjectReq(req projectReq) error {
pn := req.ProjectName
if isIllegalLength(req.ProjectName, projectNameMinLen, projectNameMaxLen) {

View File

@ -15,11 +15,12 @@ package api
import (
"fmt"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/vmware/harbor/tests/apitests/apilib"
)
var addProject *apilib.ProjectReq
@ -264,18 +265,8 @@ func TestProHead(t *testing.T) {
} else {
assert.Equal(int(404), httpStatusCode, "httpStatusCode should be 404")
}
//----------------------------case 3 : Response Code=401:User need to log in first..----------------------------//
fmt.Println("case 3: respose code:401,User need to log in first..")
httpStatusCode, err = apiTest.ProjectsHead(*unknownUsr, "libra")
if err != nil {
t.Error("Error while search project by proName", err.Error())
t.Log(err)
} else {
assert.Equal(int(401), httpStatusCode, "httpStatusCode should be 401")
}
fmt.Printf("\n")
}
func TestToggleProjectPublicity(t *testing.T) {
@ -313,7 +304,7 @@ func TestToggleProjectPublicity(t *testing.T) {
}
//-------------------case4: Response Code=404 Not found the project------------------------------//
fmt.Println("case 4: respose code:404, Not found the project")
httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "0", 1)
httpStatusCode, err = apiTest.ToggleProjectPublicity(*admin, "1234", 1)
if err != nil {
t.Error("Error while search project by proId", err.Error())
t.Log(err)

View File

@ -33,7 +33,7 @@ type SearchAPI struct {
}
type searchResult struct {
Project []models.Project `json:"project"`
Project []*models.Project `json:"project"`
Repository []map[string]interface{} `json:"repository"`
}
@ -52,10 +52,10 @@ func (s *SearchAPI) Get() {
s.CustomAbort(http.StatusInternalServerError, "internal error")
}
var projects []models.Project
var projects []*models.Project
if isSysAdmin {
projects, err = dao.GetProjects("")
projects, err = dao.GetProjects("", "", "", "", 0, 0, 0)
if err != nil {
log.Errorf("failed to get all projects: %v", err)
s.CustomAbort(http.StatusInternalServerError, "internal error")
@ -70,7 +70,7 @@ func (s *SearchAPI) Get() {
projectSorter := &models.ProjectSorter{Projects: projects}
sort.Sort(projectSorter)
projectResult := []models.Project{}
projectResult := []*models.Project{}
for _, p := range projects {
if len(keyword) > 0 && !strings.Contains(p.Name, keyword) {
continue
@ -113,7 +113,7 @@ func (s *SearchAPI) Get() {
s.ServeJSON()
}
func filterRepositories(projects []models.Project, keyword string) (
func filterRepositories(projects []*models.Project, keyword string) (
[]map[string]interface{}, error) {
repositories, err := dao.GetAllRepositories()

View File

@ -17,9 +17,10 @@ package api
import (
"net/http"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/utils/log"
"github.com/vmware/harbor/src/common/api"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
"github.com/vmware/harbor/src/common/utils/log"
)
const (
@ -52,7 +53,7 @@ func (s *StatisticAPI) Prepare() {
func (s *StatisticAPI) Get() {
statistic := map[string]int64{}
n, err := dao.GetTotalOfProjects("", 1)
n, err := dao.GetTotalOfProjects("", "", "true", "", 0)
if err != nil {
log.Errorf("failed to get total of public projects: %v", err)
s.CustomAbort(http.StatusInternalServerError, "")
@ -73,7 +74,7 @@ func (s *StatisticAPI) Get() {
}
if isAdmin {
n, err := dao.GetTotalOfProjects("")
n, err := dao.GetTotalOfProjects("", "", "", "", 0)
if err != nil {
log.Errorf("failed to get total of projects: %v", err)
s.CustomAbort(http.StatusInternalServerError, "")
@ -89,7 +90,14 @@ func (s *StatisticAPI) Get() {
statistic[MRC] = n
statistic[TRC] = n
} else {
n, err := dao.GetTotalOfUserRelevantProjects(s.userID, "")
user, err := dao.GetUser(models.User{
UserID: s.userID,
})
if err != nil {
log.Errorf("failed to get user %d: %v", s.userID, err)
s.CustomAbort(http.StatusInternalServerError, "")
}
n, err := dao.GetTotalOfProjects("", "", "", user.Username, 0)
if err != nil {
log.Errorf("failed to get total of projects for user %d: %v", s.userID, err)
s.CustomAbort(http.StatusInternalServerError, "")

View File

@ -15,6 +15,9 @@
package db
import (
"fmt"
"time"
"github.com/vmware/harbor/src/common"
"github.com/vmware/harbor/src/common/dao"
"github.com/vmware/harbor/src/common/models"
@ -106,29 +109,107 @@ func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{})
}
// GetPublic returns all public projects
func (p *ProjectManager) GetPublic() []models.Project {
projects, err := dao.GetProjects("", 1)
if err != nil {
log.Errorf("failed to get all public projects: %v", err)
return []models.Project{}
}
return projects
func (p *ProjectManager) GetPublic() []*models.Project {
return filter("", "", "true", "", 0, 0, 0)
}
// GetByMember returns all projects which the user is a member of
func (p *ProjectManager) GetByMember(username string) []models.Project {
func (p *ProjectManager) GetByMember(username string) []*models.Project {
return filter("", "", "", username, 0, 0, 0)
}
// Create ...
func (p *ProjectManager) Create(project *models.Project) (int64, error) {
if project == nil {
return 0, fmt.Errorf("project is nil")
}
if len(project.Name) == 0 {
return 0, fmt.Errorf("project name is nil")
}
if project.OwnerID == 0 {
if len(project.OwnerName) == 0 {
return 0, fmt.Errorf("owner ID and owner name are both nil")
}
user, err := dao.GetUser(models.User{
Username: username,
Username: project.OwnerName,
})
if err != nil {
log.Errorf("failed to get user %s: %v", username, err)
return []models.Project{}
return 0, err
}
projects, err := dao.GetUserRelevantProjects(user.UserID, "")
if user == nil {
return 0, fmt.Errorf("can not get owner whose name is %s", project.OwnerName)
}
project.OwnerID = user.UserID
}
t := time.Now()
pro := &models.Project{
Name: project.Name,
Public: project.Public,
OwnerID: project.OwnerID,
CreationTime: t,
UpdateTime: t,
}
return dao.AddProject(*pro)
}
// Delete ...
func (p *ProjectManager) Delete(projectIDOrName interface{}) error {
id, ok := projectIDOrName.(int64)
if !ok {
project := p.Get(projectIDOrName)
if project == nil {
return fmt.Errorf(fmt.Sprintf("project %v not found", projectIDOrName))
}
id = project.ProjectID
}
return dao.DeleteProject(id)
}
// Update ...
func (p *ProjectManager) Update(projectIDOrName interface{},
project *models.Project) error {
id, ok := projectIDOrName.(int64)
if !ok {
pro := p.Get(projectIDOrName)
if pro == nil {
return fmt.Errorf(fmt.Sprintf("project %v not found", projectIDOrName))
}
id = pro.ProjectID
}
return dao.ToggleProjectPublicity(id, project.Public)
}
// GetAll ...
func (p *ProjectManager) GetAll(owner, name, public, member string,
role int, page, size int64) ([]*models.Project, int64) {
total, err := dao.GetTotalOfProjects(owner, name, public, member, role)
if err != nil {
log.Errorf("failed to get projects of %s: %v", username, err)
return []models.Project{}
log.Errorf("failed to get total of projects: %v", err)
return []*models.Project{}, 0
}
return filter(owner, name, public, member, role, page, size), total
}
func filter(owner, name, public, member string,
role int, page, size int64) []*models.Project {
projects := []*models.Project{}
list, err := dao.GetProjects(owner, name, public, member, role,
page, size)
if err != nil {
log.Errorf("failed to get projects: %v", err)
return projects
}
if len(list) != 0 {
projects = append(projects, list...)
}
return projects

View File

@ -142,3 +142,101 @@ func TestGetByMember(t *testing.T) {
projects := pm.GetByMember("admin")
assert.NotEqual(t, 0, len(projects))
}
func TestCreateAndDelete(t *testing.T) {
pm := &ProjectManager{}
// nil project
_, err := pm.Create(nil)
assert.NotNil(t, err)
// nil project name
_, err = pm.Create(&models.Project{
OwnerID: 1,
})
assert.NotNil(t, err)
// nil owner id and nil owner name
_, err = pm.Create(&models.Project{
Name: "test",
OwnerName: "non_exist_user",
})
assert.NotNil(t, err)
// valid project, owner id
id, err := pm.Create(&models.Project{
Name: "test",
OwnerID: 1,
})
assert.Nil(t, err)
assert.Nil(t, pm.Delete(id))
// valid project, owner name
id, err = pm.Create(&models.Project{
Name: "test",
OwnerName: "admin",
})
assert.Nil(t, err)
assert.Nil(t, pm.Delete(id))
}
func TestUpdate(t *testing.T) {
pm := &ProjectManager{}
id, err := pm.Create(&models.Project{
Name: "test",
OwnerID: 1,
})
assert.Nil(t, err)
defer pm.Delete(id)
project := pm.Get(id)
assert.Equal(t, 0, project.Public)
project.Public = 1
assert.Nil(t, pm.Update(id, project))
project = pm.Get(id)
assert.Equal(t, 1, project.Public)
}
func TestGetAll(t *testing.T) {
pm := &ProjectManager{}
id, err := pm.Create(&models.Project{
Name: "get_all_test",
OwnerID: 1,
Public: 1,
})
assert.Nil(t, err)
defer pm.Delete(id)
// get by name
projects, total := pm.GetAll("", "get_all_test", "", "", 0, 0, 0)
assert.Equal(t, int64(1), total)
assert.Equal(t, id, projects[0].ProjectID)
// get by owner
projects, total = pm.GetAll("admin", "", "", "", 0, 0, 0)
assert.NotEqual(t, 0, total)
exist := false
for _, project := range projects {
if project.ProjectID == id {
exist = true
break
}
}
assert.True(t, exist)
// get by public
projects, total = pm.GetAll("", "", "true", "", 0, 0, 0)
assert.NotEqual(t, 0, total)
exist = false
for _, project := range projects {
if project.ProjectID == id {
exist = true
break
}
}
assert.True(t, exist)
}

View File

@ -26,7 +26,20 @@ type ProjectManager interface {
Exist(projectIDOrName interface{}) bool
GetRoles(username string, projectIDOrName interface{}) []int
// get all public project
GetPublic() []models.Project
GetPublic() []*models.Project
// get projects which the user is a member of
GetByMember(username string) []models.Project
GetByMember(username string) []*models.Project
Create(*models.Project) (int64, error)
Delete(projectIDOrName interface{}) error
Update(projectIDOrName interface{}, project *models.Project) error
// GetAll returns a project list and the total count according to
// the query conditions:
// owner: username of owner
// name: name of project
// public: public or not, can be "true", "false" or ""
// member: username of the member
// role: the role of member specified by member parameter
// page, size: pagination parameters
GetAll(owner, name, public, member string, role int, page,
size int64) ([]*models.Project, int64)
}